Skip to content

Commit f9abebb

Browse files
committed
Merge dev into main: download filename, multi-dir MCP, dashboard enhancements
2 parents f83df32 + a09e70b commit f9abebb

9 files changed

Lines changed: 453 additions & 70 deletions

File tree

examples/open.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from investing_algorithm_framework import BacktestReport, recalculate_backtests, retag_backtests
33

44

5-
batch_one_path = os.path.join("examples", "batch_one")
5+
batch_one_path = os.path.join(os.path.dirname(__file__), "batch_one")
66

77
if __name__ == "__main__":
88
retag_backtests("batch_one", directory_path=batch_one_path)

examples/tutorial/notebooks/01_data_exploration.ipynb

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,12 @@
114114
"id": "7",
115115
"metadata": {},
116116
"source": [
117-
"## Data downloading\n"
117+
"## Custom filenames\n",
118+
"\n",
119+
"By default, downloaded CSV files are named using the pattern\n",
120+
"`OHLCV_SYMBOL_MARKET_TIMEFRAME_START_END.csv`. You can override this with the\n",
121+
"`filename` parameter to use your own naming convention. The `.csv` extension is\n",
122+
"appended automatically if not present."
118123
]
119124
},
120125
{
@@ -123,6 +128,28 @@
123128
"id": "8",
124129
"metadata": {},
125130
"outputs": [],
131+
"source": [
132+
"# Example: download with a custom filename\n",
133+
"# result = download_v2(\n",
134+
"# symbol=\"BTC/EUR\",\n",
135+
"# market=MARKET,\n",
136+
"# time_frame=\"1d\",\n",
137+
"# data_type=\"ohlcv\",\n",
138+
"# start_date=backtest_window_date_range.start_date,\n",
139+
"# end_date=backtest_window_date_range.end_date,\n",
140+
"# save=True,\n",
141+
"# storage_path=str(data_storage_path),\n",
142+
"# filename=\"btc_daily_data\", # saves as btc_daily_data.csv\n",
143+
"# )\n",
144+
"# print(f\"Saved to: {result.path}\")"
145+
]
146+
},
147+
{
148+
"cell_type": "code",
149+
"execution_count": null,
150+
"id": "9",
151+
"metadata": {},
152+
"outputs": [],
126153
"source": [
127154
"from investing_algorithm_framework import download_v2, TimeFrame, tqdm\n",
128155
"in_sample_data = {}\n",
@@ -143,7 +170,9 @@
143170
" start_date=backtest_window_date_range.start_date,\n",
144171
" end_date=backtest_window_date_range.end_date,\n",
145172
" save=True,\n",
146-
" storage_path=str(data_storage_path)\n",
173+
" storage_path=str(data_storage_path),\n",
174+
" # Optional: provide a custom filename instead of the auto-generated one\n",
175+
" # filename=f\"{symbol}_{time_frame}_in_sample\",\n",
147176
" )\n",
148177
" in_sample_data[symbol][time_frame] = {\n",
149178
" \"data\": result.data,\n",
@@ -170,7 +199,9 @@
170199
" start_date=backtest_window_date_range.start_date,\n",
171200
" end_date=backtest_window_date_range.end_date,\n",
172201
" save=True,\n",
173-
" storage_path=str(data_storage_path)\n",
202+
" storage_path=str(data_storage_path),\n",
203+
" # Optional: provide a custom filename instead of the auto-generated one\n",
204+
" # filename=f\"{symbol}_{time_frame}_out_sample\",\n",
174205
" )\n",
175206
" out_sample_data[symbol][time_frame] = {\n",
176207
" \"data\": result.data,\n",
@@ -179,12 +210,12 @@
179210
" first_date = result.data.index[0]\n",
180211
"\n",
181212
" if first_date > backtest_window_date_range.start_date:\n",
182-
" print(f\"Warning: Data for {symbol_pair} starts on {first_date} which is after the requested start date of {backtest_window_date_range.start_date}.\")\n"
213+
" print(f\"Warning: Data for {symbol_pair} starts on {first_date} which is after the requested start date of {backtest_window_date_range.start_date}.\")"
183214
]
184215
},
185216
{
186217
"cell_type": "markdown",
187-
"id": "9",
218+
"id": "10",
188219
"metadata": {},
189220
"source": [
190221
"## Check data completeness and fill missing timestamps"
@@ -193,7 +224,7 @@
193224
{
194225
"cell_type": "code",
195226
"execution_count": null,
196-
"id": "10",
227+
"id": "11",
197228
"metadata": {},
198229
"outputs": [],
199230
"source": [
@@ -232,7 +263,7 @@
232263
},
233264
{
234265
"cell_type": "markdown",
235-
"id": "11",
266+
"id": "12",
236267
"metadata": {},
237268
"source": [
238269
"## Analysis on the Backtest Windows"
@@ -241,7 +272,7 @@
241272
{
242273
"cell_type": "code",
243274
"execution_count": null,
244-
"id": "12",
275+
"id": "13",
245276
"metadata": {},
246277
"outputs": [],
247278
"source": [

examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb

Lines changed: 131 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,114 @@
7575
"cell_type": "markdown",
7676
"id": "3",
7777
"metadata": {},
78+
"source": [
79+
"## Step 1b — Tag Your Backtests\n",
80+
"\n",
81+
"Tags let you label and group strategies — for example by batch name, experiment,\n",
82+
"or configuration variant. Tags are **persisted** in a `tag.json` file inside\n",
83+
"each backtest directory, so they survive across subsequent loads.\n",
84+
"\n",
85+
"### How tags get assigned\n",
86+
"\n",
87+
"There are three ways a backtest gets a tag:\n",
88+
"\n",
89+
"**1. During the backtest run** — Set `tag` in the strategy metadata:\n",
90+
"```python\n",
91+
"strategy = TradingStrategy(\n",
92+
" algorithm_id=\"momentum_v3\",\n",
93+
" metadata={\"tag\": \"experiment_A\"},\n",
94+
" ...\n",
95+
")\n",
96+
"```\n",
97+
"The tag is automatically extracted and saved with the backtest.\n",
98+
"\n",
99+
"**2. After the fact with `retag_backtests()`** — Retag all backtests in a\n",
100+
"directory (or filter by strategy_id):\n",
101+
"```python\n",
102+
"from investing_algorithm_framework import retag_backtests\n",
103+
"\n",
104+
"# Tag everything in a directory\n",
105+
"retag_backtests(\"experiment_A\", directory_path=\"my_batch\")\n",
106+
"\n",
107+
"# Tag only a specific strategy\n",
108+
"retag_backtests(\"experiment_B\", directory_path=\"my_batch\", strategy_id=\"momentum_v3\")\n",
109+
"```\n",
110+
"\n",
111+
"**3. Multi-directory loading** — When you load from multiple directories,\n",
112+
"each directory name is used as the default tag (unless a persisted tag exists):\n",
113+
"```python\n",
114+
"report = BacktestReport.open(directory_path=[\"batch_A\", \"batch_B\"])\n",
115+
"# Strategies from batch_A get tag \"batch_A\", etc.\n",
116+
"```\n",
117+
"\n",
118+
"### What tags do in the dashboard\n",
119+
"\n",
120+
"- **Filter bar** — When 2+ tags exist, a chip-based filter bar appears at the top\n",
121+
"- **Tag badges** — Every strategy name shows its tag badge in all tables\n",
122+
"- **Collapsible sidebar groups** — Strategies are grouped by tag with collapsible headers\n",
123+
"- **MCP server** — The `list_strategies` and `get_strategy_details` tools include tag info"
124+
]
125+
},
126+
{
127+
"cell_type": "code",
128+
"execution_count": null,
129+
"id": "4",
130+
"metadata": {},
131+
"outputs": [],
132+
"source": [
133+
"from investing_algorithm_framework import retag_backtests\n",
134+
"\n",
135+
"# Retag all backtests in a directory\n",
136+
"count = retag_backtests(\"experiment_A\", directory_path=batch_dir)\n",
137+
"print(f\"Retagged {count} backtests with tag 'experiment_A'\")\n",
138+
"\n",
139+
"# Retag a single strategy by its algorithm_id\n",
140+
"count = retag_backtests(\n",
141+
" \"experiment_B\",\n",
142+
" directory_path=batch_dir,\n",
143+
" strategy_id=\"momentum_v3\"\n",
144+
")\n",
145+
"print(f\"Retagged {count} backtests with tag 'experiment_B'\")\n",
146+
"\n",
147+
"# Reload to see the tags\n",
148+
"report = BacktestReport.open(directory_path=batch_dir)\n",
149+
"for bt in report.backtests:\n",
150+
" print(f\" • {bt.algorithm_id}: tag={bt.tag}\")"
151+
]
152+
},
153+
{
154+
"cell_type": "markdown",
155+
"id": "5",
156+
"metadata": {},
157+
"source": [
158+
"### Multi-directory loading\n",
159+
"\n",
160+
"You can also load backtests from **multiple directories** at once by passing a list.\n",
161+
"Each directory name becomes the default tag for its strategies (unless they already\n",
162+
"have a persisted tag)."
163+
]
164+
},
165+
{
166+
"cell_type": "code",
167+
"execution_count": null,
168+
"id": "6",
169+
"metadata": {},
170+
"outputs": [],
171+
"source": [
172+
"# Load from multiple directories — each dir name becomes the tag\n",
173+
"report = BacktestReport.open(directory_path=[\"batch_A\", \"batch_B\"])\n",
174+
"\n",
175+
"for bt in report.backtests:\n",
176+
" print(f\" • {bt.algorithm_id}: tag={bt.tag}\")\n",
177+
"\n",
178+
"# Open the dashboard — strategies will be grouped by tag in the sidebar\n",
179+
"report.show()"
180+
]
181+
},
182+
{
183+
"cell_type": "markdown",
184+
"id": "7",
185+
"metadata": {},
78186
"source": [
79187
"## Step 2 — Open the Interactive Dashboard\n",
80188
"\n",
@@ -91,15 +199,15 @@
91199
{
92200
"cell_type": "code",
93201
"execution_count": null,
94-
"id": "4",
202+
"id": "8",
95203
"metadata": {},
96204
"outputs": [],
97205
"source": []
98206
},
99207
{
100208
"cell_type": "code",
101209
"execution_count": null,
102-
"id": "5",
210+
"id": "9",
103211
"metadata": {},
104212
"outputs": [],
105213
"source": [
@@ -109,7 +217,7 @@
109217
},
110218
{
111219
"cell_type": "markdown",
112-
"id": "6",
220+
"id": "10",
113221
"metadata": {},
114222
"source": [
115223
"## Step 3 — Start the MCP Server (Connect Your LLM)\n",
@@ -123,13 +231,20 @@
123231
"### Start the server from your terminal\n",
124232
"\n",
125233
"```bash\n",
234+
"# Single directory\n",
126235
"iaf mcp -d your_batch_directory\n",
236+
"\n",
237+
"# Multiple directories — repeat -d for each\n",
238+
"iaf mcp -d batch_one -d batch_two -d batch_three\n",
127239
"```\n",
128240
"\n",
129241
"Or if you haven't installed the CLI:\n",
130242
"\n",
131243
"```bash\n",
132244
"python -m investing_algorithm_framework.cli.mcp_server -d your_batch_directory\n",
245+
"\n",
246+
"# Multiple directories\n",
247+
"python -m investing_algorithm_framework.cli.mcp_server -d batch_one -d batch_two\n",
133248
"```\n",
134249
"\n",
135250
"### Configure VS Code to use it\n",
@@ -142,7 +257,7 @@
142257
" \"servers\": {\n",
143258
" \"backtest-analysis\": {\n",
144259
" \"command\": \"iaf\",\n",
145-
" \"args\": [\"mcp\", \"-d\", \"your_batch_directory\"]\n",
260+
" \"args\": [\"mcp\", \"-d\", \"batch_one\", \"-d\", \"batch_two\"]\n",
146261
" }\n",
147262
" }\n",
148263
" }\n",
@@ -155,7 +270,7 @@
155270
},
156271
{
157272
"cell_type": "markdown",
158-
"id": "7",
273+
"id": "11",
159274
"metadata": {},
160275
"source": [
161276
"## Step 4 — The LLM Toolkit (23 MCP Tools)\n",
@@ -204,7 +319,7 @@
204319
},
205320
{
206321
"cell_type": "markdown",
207-
"id": "8",
322+
"id": "12",
208323
"metadata": {},
209324
"source": [
210325
"## Step 5 — Ask the LLM to Analyze Your Strategies\n",
@@ -265,7 +380,7 @@
265380
},
266381
{
267382
"cell_type": "markdown",
268-
"id": "9",
383+
"id": "13",
269384
"metadata": {},
270385
"source": [
271386
"## Step 6 — The Iterative Workflow\n",
@@ -320,7 +435,7 @@
320435
},
321436
{
322437
"cell_type": "markdown",
323-
"id": "10",
438+
"id": "14",
324439
"metadata": {},
325440
"source": [
326441
"## Step 7 — Notes as Self-Contained Analysis Documents\n",
@@ -360,7 +475,7 @@
360475
},
361476
{
362477
"cell_type": "markdown",
363-
"id": "11",
478+
"id": "15",
364479
"metadata": {},
365480
"source": [
366481
"## Step 8 — Example Conversation with the LLM\n",
@@ -419,7 +534,7 @@
419534
},
420535
{
421536
"cell_type": "markdown",
422-
"id": "12",
537+
"id": "16",
423538
"metadata": {},
424539
"source": [
425540
"## Step 9 — Apply Selections & Export\n",
@@ -456,7 +571,7 @@
456571
},
457572
{
458573
"cell_type": "markdown",
459-
"id": "13",
574+
"id": "17",
460575
"metadata": {},
461576
"source": [
462577
"## Step 10 — You Can Also Save / Reload Programmatically\n",
@@ -469,7 +584,7 @@
469584
{
470585
"cell_type": "code",
471586
"execution_count": null,
472-
"id": "14",
587+
"id": "18",
473588
"metadata": {},
474589
"outputs": [],
475590
"source": [
@@ -480,7 +595,7 @@
480595
},
481596
{
482597
"cell_type": "markdown",
483-
"id": "15",
598+
"id": "19",
484599
"metadata": {},
485600
"source": [
486601
"## TL;DR — The Complete Workflow\n",
@@ -499,6 +614,9 @@
499614
"```bash\n",
500615
"# 3. Start the MCP server (in a separate terminal)\n",
501616
"iaf mcp -d my_batch\n",
617+
"\n",
618+
"# Or load multiple batch directories at once\n",
619+
"iaf mcp -d batch_one -d batch_two\n",
502620
"```\n",
503621
"\n",
504622
"```\n",

investing_algorithm_framework/app/reporting/templates/dashboard.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ body { font-family:'Inter',-apple-system,sans-serif; background:var(--bg); color
7474
.challenger-btn.active { background:var(--accent); color:#fff; border-color:var(--accent); }
7575
.challenger-row { background:rgba(34,211,238,0.06) !important; }
7676
.challenger-row td { border-bottom:1px solid var(--accent) !important; }
77+
.challenger-col { background:rgba(34,211,238,0.06) !important; border-bottom:2px solid var(--accent) !important; }
7778
.sb-challenger { margin-left:auto; font-size:11px; color:var(--accent); }
7879
.sb-item .sb-cb { width:13px; height:13px; accent-color:var(--accent); cursor:pointer; flex-shrink:0; margin:0; }
7980
.sb-item .sb-chal-btn { background:none; border:1px solid var(--border); border-radius:3px; cursor:pointer; font-size:11px; color:var(--text-dim); padding:0 3px; margin-left:auto; line-height:1.2; transition:all 0.15s; flex-shrink:0; }

0 commit comments

Comments
 (0)