-
Notifications
You must be signed in to change notification settings - Fork 164
Expand file tree
/
Copy pathcli.py
More file actions
371 lines (332 loc) · 10.8 KB
/
cli.py
File metadata and controls
371 lines (332 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
import click
from .deploy_to_aws_lambda import command as deploy_to_aws_lambda_command
from .deploy_to_azure_function import command as \
deploy_to_azure_function_command
from .initialize_app import command as initialize_app_command
from .validate_backtest_checkpoints import command as \
validate_backtest_checkpoints_command
"""
CLI for Investing Algorithm Framework
This module provides a command-line interface (CLI) for the
Investing Algorithm Framework.
"""
@click.group()
def cli():
"""CLI for Investing Algorithm Framework"""
pass
@click.command()
@click.option(
'--type',
default="default",
help="Type of app to create. "
"Options are: 'default', 'default_web', 'azure_function', 'aws_lambda'."
)
@click.option(
'--path', default=None, help="Path to directory to initialize the app in"
)
@click.option(
'--replace',
is_flag=True,
default=False,
help="If True, duplicate files will be replaced."
"If False, files will not be replaced."
)
def init(type, path, replace):
"""
Command-line tool for creating an app skeleton.
Args:
type (str): Type of app to create. Options are: 'default',
'default-web', 'azure-function'.
path (str): Path to directory to initialize the app in
replace (bool): If True, existing files will be replaced.
If False, existing files will not be replaced.
Returns:
None
"""
initialize_app_command(path=path, app_type=type, replace=replace)
@click.command()
@click.option(
'--resource_group',
required=True,
help='The name of the resource group.',
)
@click.option(
'--subscription_id',
required=False,
help='The subscription ID. If not provided, the default will be used.'
)
@click.option(
'--storage_account_name',
required=False,
help='The name of the storage account.',
)
@click.option(
'--container_name',
required=False,
help='The name of the blob container.',
default='iafcontainer'
)
@click.option(
'--deployment_name',
required=True,
help='The name of the deployment. This will be" + \
"used as the name of the Function App.'
)
@click.option(
'--region',
required=True,
help='The Azure region for the resources.'
)
@click.option(
'--create_resource_group_if_not_exists',
is_flag=True,
help='Flag to create the resource group if it does not exist.'
)
@click.option(
'--skip_login',
is_flag=True,
help='Flag to create the resource group if it does not exist.',
default=False
)
def deploy_azure_function(
resource_group,
subscription_id,
storage_account_name,
container_name,
deployment_name,
region,
create_resource_group_if_not_exists,
skip_login
):
"""
Command-line tool for deploying a trading bot to Azure Function.
Args:
path (str): Path to directory to initialize the app in
resource_group (str): The name of the resource group.
subscription_id (str): The subscription ID. If not provided,
the default will be used.
storage_account_name (str): The name of the storage account.
container_name (str): The name of the blob container.
deployment_name (str): The name of the deployment. This will be
used as the name of the Function App.
region (str): The Azure region for the resources.
create_resource_group_if_not_exists (bool): Flag to create the
resource group if it does not exist.
skip_login (bool): Flag to skip the login process. This is
useful for CI/CD pipelines where the login is handled
separately.
region (str): The Azure region for the resources.
create_resource_group_if_not_exists (bool): Flag to create the
resource group if it does not exist.
skip_login (bool): Flag to skip the login process. This is
useful for CI/CD pipelines where the login is handled
separately.
Returns:
None
"""
crg = create_resource_group_if_not_exists
deploy_to_azure_function_command(
resource_group=resource_group,
subscription_id=subscription_id,
storage_account_name=storage_account_name,
container_name=container_name,
deployment_name=deployment_name,
region=region,
create_resource_group_if_not_exists=crg,
skip_login=skip_login
)
@click.command()
@click.option(
'--lambda_function_name',
required=True,
help='The name of the AWS Lambda function to deploy.'
)
@click.option(
'--region',
required=True,
help='The AWS region where the Lambda function will be deployed.'
)
@click.option(
'--project_dir',
default=None,
help='The path to the project directory containing '
'the Lambda function code.'
)
@click.option(
'--memory_size',
default=3000,
type=int,
help='The memory size for the Lambda function in MB. Default is 3000 MB.'
)
@click.option(
'--env',
'-e',
multiple=True,
nargs=2,
type=str,
help='Environment variables to pass to the Lambda function. '
'Can be used multiple times: -e KEY VALUE -e KEY2 VALUE2'
)
def deploy_aws_lambda(
lambda_function_name,
region,
project_dir=None,
memory_size=3000,
env=None
):
"""
Command-line tool for deploying a trading bot to AWS lambda
Args:
lambda_function_name (str): The name of the AWS Lambda function
to deploy.
region (str): The AWS region where the Lambda function will
be deployed.
project_dir (str): The path to the project directory containing the
Lambda function code. If not provided, it defaults to
the current directory.
memory_size (int): The memory size for the Lambda function in MB.
Default is 3000 MB.
env (tuple): Environment variables as tuples of (KEY, VALUE).
Can be specified multiple times.
Returns:
None
"""
# Convert env tuples to dictionary
env_vars = {}
if env:
for key, value in env:
env_vars[key] = value
deploy_to_aws_lambda_command(
lambda_function_name=lambda_function_name,
region=region,
project_dir=project_dir,
memory_size=memory_size,
env_vars=env_vars
)
cli.add_command(init)
cli.add_command(deploy_azure_function)
cli.add_command(deploy_aws_lambda)
cli.add_command(
validate_backtest_checkpoints_command, name="validate-checkpoints"
)
@click.command()
@click.option(
'--directory', '-d',
required=True,
multiple=True,
help='Path to a backtest batch directory (can be repeated)'
)
def mcp(directory):
"""Start the MCP server for AI-powered backtest analysis.
This lets GitHub Copilot, Claude, and other LLMs query your
backtest data directly in VS Code.
"""
from .mcp_server import main as mcp_main
dirs = list(directory)
mcp_main(directory=dirs if len(dirs) > 1 else dirs[0])
cli.add_command(mcp)
@click.command(name="migrate-backtests")
@click.option(
"--src", "-s",
required=True,
type=click.Path(exists=True, file_okay=False, dir_okay=True),
help="Source directory containing legacy backtest sub-directories.",
)
@click.option(
"--dst", "-d",
required=True,
type=click.Path(file_okay=False, dir_okay=True),
help="Destination directory for the new ``.iafbt`` bundle files.",
)
@click.option(
"--workers", "-w", type=int, default=None,
help="Number of parallel workers (default: min(8, CPU count)).",
)
@click.option(
"--no-index", is_flag=True, default=False,
help="Skip writing index.parquet at the destination.",
)
@click.option(
"--include-ohlcv", is_flag=True, default=False,
help="Include OHLCV data in the destination bundles.",
)
@click.option(
"--no-skip-existing", is_flag=True, default=False,
help="Re-migrate even if the destination bundle already exists.",
)
@click.option(
"--delete-source", is_flag=True, default=False,
help=(
"Delete each source directory/bundle after its destination "
"has been written successfully. Use with care."
),
)
def migrate_backtests_cmd(
src, dst, workers, no_index, include_ohlcv, no_skip_existing,
delete_source,
):
"""Convert a directory of legacy backtest folders into the bundled
binary format introduced in issue #487.
The new ``.iafbt`` format is a single zstd-compressed MessagePack
file per backtest. Loading bundled directories is dramatically
faster than the legacy multi-file layout for large batches.
Migration is streamed (load+save fused per worker) so memory
usage stays roughly constant regardless of source size, and
interrupted runs can be resumed (existing destination bundles
are skipped by default).
"""
from investing_algorithm_framework.domain import migrate_backtests
n = migrate_backtests(
src,
dst,
workers=workers,
show_progress=True,
write_index=not no_index,
include_ohlcv=include_ohlcv,
skip_existing=not no_skip_existing,
delete_source=delete_source,
)
click.echo(f"Migrated {n} backtest(s) from {src} to {dst}")
cli.add_command(migrate_backtests_cmd)
@click.command(name="index")
@click.argument(
"directory",
type=click.Path(exists=True, file_okay=False, dir_okay=True),
)
@click.option(
"--output", "-o",
type=click.Path(file_okay=True, dir_okay=False),
default=None,
help="Path to the SQLite index file (default: <directory>/index.sqlite).",
)
@click.option(
"--absolute-paths", is_flag=True, default=False,
help="Store absolute bundle paths in the index "
"(default: paths relative to <directory>, so the index stays "
"portable when the folder is moved).",
)
@click.option(
"--no-progress", is_flag=True, default=False,
help="Suppress the progress bar.",
)
def index_cmd(directory, output, absolute_paths, no_progress):
"""Build a SQLite Tier-1 index over a folder of ``.iafbt`` bundles.
The resulting ``index.sqlite`` file holds one row per bundle with
identity / provenance / config columns and every scalar
``BacktestSummaryMetrics`` field promoted to its own column, so
analysts can run ad-hoc SQL queries (e.g.
``SELECT bundle_path FROM backtest_index
WHERE summary_sharpe_ratio > 1.0``) without opening any bundle.
Each bundle is opened with ``summary_only=True`` so no Parquet
metric blobs are decoded \u2014 indexing 12,500 bundles is bounded by
msgpack header parsing, not metric reconstruction.
"""
from .index_command import build_index
out = build_index(
directory=directory,
output=output,
relative_paths=not absolute_paths,
show_progress=not no_progress,
)
click.echo(f"Wrote SQLite index to {out}")
cli.add_command(index_cmd)