Skip to content

Commit 1c3d0ad

Browse files
authored
Merge pull request #420 from coding-kitties/feature/redesign-backtest-report
feat: redesign BacktestReport — unified HTML dashboard
2 parents 1863106 + a76025e commit 1c3d0ad

28 files changed

Lines changed: 7994 additions & 818 deletions

README.md

Lines changed: 171 additions & 438 deletions
Large diffs are not rendered by default.

_verify_fixes.py

Lines changed: 0 additions & 36 deletions
This file was deleted.

codecov.yml

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
---
2+
sidebar_position: 9
3+
---
4+
5+
# Backtest Reports
6+
7+
The framework generates self-contained HTML dashboard reports for analyzing backtest results. Reports work for both single and multi-strategy backtests — no external dependencies required.
8+
9+
## Quick Start
10+
11+
```python
12+
from investing_algorithm_framework import BacktestReport
13+
14+
# Single strategy report
15+
report = BacktestReport(backtest)
16+
report.show() # Opens in browser (or renders inline in Jupyter)
17+
```
18+
19+
## Creating Reports
20+
21+
### From a Backtest Object
22+
23+
After running a backtest, pass the result directly:
24+
25+
```python
26+
backtest = app.run_backtest(
27+
backtest_date_range=backtest_range,
28+
initial_amount=1000
29+
)
30+
31+
report = BacktestReport(backtest)
32+
report.show(browser=True)
33+
```
34+
35+
### From Multiple Backtests
36+
37+
Compare strategies side by side in a single dashboard:
38+
39+
```python
40+
backtest_a = app.run_backtest(...)
41+
backtest_b = app.run_backtest(...)
42+
43+
report = BacktestReport(backtests=[backtest_a, backtest_b])
44+
report.show()
45+
```
46+
47+
This generates a multi-strategy comparison dashboard with:
48+
- Strategy ranking tables (Key Metrics, Trading Activity)
49+
- Return Scenarios projections (Good/Average/Bad/Very Bad Year)
50+
- Normalized equity curves overlay
51+
- Per-strategy detail pages with Summary, Runs, and Performance tabs
52+
- Compare mode with monthly return distribution (Rows/Heatmap × Returns/Growth toggles)
53+
54+
### From Saved Backtests on Disk
55+
56+
Load previously saved backtests from a directory:
57+
58+
```python
59+
report = BacktestReport.open(directory_path="./my_backtests")
60+
report.show()
61+
```
62+
63+
The `open()` method recursively finds all valid backtest directories (containing `algorithm_id.json` and a `runs/` folder) and loads them into a single report.
64+
65+
You can also combine disk and in-memory backtests:
66+
67+
```python
68+
report = BacktestReport.open(
69+
backtests=[my_new_backtest],
70+
directory_path="./saved_backtests"
71+
)
72+
report.show()
73+
```
74+
75+
## Recalculating Metrics
76+
77+
When metric calculations are updated in a newer framework version, previously saved backtests may have stale metrics. Use `recalculate_backtests` to recompute all per-run and summary metrics from the raw portfolio snapshots and trades:
78+
79+
```python
80+
from investing_algorithm_framework import BacktestReport, recalculate_backtests
81+
82+
report = BacktestReport.open(directory_path="./my_backtests")
83+
84+
# Recalculate all metrics for all backtests
85+
recalculate_backtests(report.backtests)
86+
87+
report.show()
88+
```
89+
90+
You can specify a custom risk-free rate (otherwise each backtest's stored rate is used):
91+
92+
```python
93+
recalculate_backtests(report.backtests, risk_free_rate=0.04)
94+
```
95+
96+
Or limit which metrics are recomputed:
97+
98+
```python
99+
recalculate_backtests(
100+
report.backtests,
101+
metrics=["cagr", "sharpe_ratio", "max_drawdown", "win_rate"]
102+
)
103+
```
104+
105+
`recalculate_backtests` works on any list of `Backtest` objects, not just those loaded from disk:
106+
107+
```python
108+
from investing_algorithm_framework import recalculate_backtests
109+
110+
backtests = [backtest_a, backtest_b, backtest_c]
111+
recalculate_backtests(backtests, risk_free_rate=0.027)
112+
```
113+
114+
For each backtest, the function:
115+
1. Recomputes per-run `BacktestMetrics` from raw `portfolio_snapshots` and `trades`
116+
2. Regenerates `BacktestSummaryMetrics` by aggregating the updated per-run metrics
117+
118+
## Saving Reports
119+
120+
Save the report as a standalone HTML file you can share or open later:
121+
122+
```python
123+
report = BacktestReport(backtests=[backtest_a, backtest_b])
124+
report.save("strategy_comparison.html")
125+
```
126+
127+
The output is a single `.html` file with all CSS, JavaScript, and data embedded — no server or internet connection needed to view it.
128+
129+
## Viewing in Jupyter
130+
131+
`show()` automatically detects Jupyter notebooks and renders the dashboard inline:
132+
133+
```python
134+
# In a Jupyter notebook cell:
135+
report = BacktestReport.open(directory_path="./backtests")
136+
report.show() # Renders inline in the notebook
137+
report.show(browser=True) # Also opens in the browser
138+
```
139+
140+
## Dashboard Features
141+
142+
### Overview Page
143+
- **KPI cards**: Best CAGR, best Sharpe, lowest max drawdown (with dual values when a window is selected)
144+
- **Window Coverage**: Strategy × window matrix showing data coverage
145+
- **Key Metrics table**: Sortable ranking with CAGR, Sharpe, Sortino, Calmar, Max DD, Volatility, Recovery Factor, Net Gain %
146+
- **Trading Activity table**: Profit Factor, Win Rate, Trades/yr, Trades/mo, Trades/wk, # Trades, Avg Return, Avg Duration
147+
- **Return Scenarios**: Good/Average/Bad/Very Bad Year projections based on CAGR ± volatility
148+
- **Equity curves**: Normalized percentage growth overlay
149+
- **Collapsible cards**: All chart sections can be collapsed/expanded
150+
151+
### Strategy Pages
152+
Each strategy gets a dedicated page with three tabs:
153+
154+
| Tab | Contents |
155+
|-----|----------|
156+
| **Summary** | Full KPI grid (CAGR, Sharpe, Sortino, Calmar, Max DD, Profit Factor, Win Rate, Volatility, Recovery Factor, etc.) |
157+
| **Runs** | Backtest run comparison table, equity overlay across runs |
158+
| **Performance** | Monthly returns heatmap, yearly returns bar chart, return distribution |
159+
160+
Use the run selector pills to switch between summary view and individual backtest runs.
161+
162+
### Compare Mode (Multi-Strategy)
163+
Open the strategy selection modal to pick strategies for comparison. You can set a challenger strategy for highlighting. The compare page includes:
164+
- **Key Metrics** and **Trading Activity** ranking tables
165+
- **Return Scenarios** projections
166+
- **Monthly Returns** with four view modes (Returns/Growth × Rows/Heatmap), plus a year filter
167+
- Side-by-side equity curves and drawdown overlays
168+
- Metric bar charts (CAGR, Sharpe, Sortino, Calmar, Max DD, Win Rate, Profit Factor)
169+
- Return distribution histograms and correlation matrix
170+
- Rolling Sharpe ratio chart
171+
- Yearly returns bar charts
172+
173+
### Sticky Navigation
174+
The page title bar with the window selector stays visible as you scroll.
175+
176+
### Dark / Light Theme
177+
Toggle between dark and light mode using the sun icon in the top-right corner.
178+
179+
## Example: Full Workflow
180+
181+
```python
182+
from datetime import datetime, timezone
183+
from investing_algorithm_framework import (
184+
create_app, BacktestDateRange, BacktestReport, recalculate_backtests
185+
)
186+
187+
app = create_app()
188+
# ... configure strategies, market, portfolio ...
189+
190+
# Run backtests across multiple time periods
191+
date_ranges = [
192+
BacktestDateRange(
193+
start_date=datetime(2022, 1, 1, tzinfo=timezone.utc),
194+
end_date=datetime(2022, 12, 31, tzinfo=timezone.utc),
195+
name="2022"
196+
),
197+
BacktestDateRange(
198+
start_date=datetime(2023, 1, 1, tzinfo=timezone.utc),
199+
end_date=datetime(2023, 12, 31, tzinfo=timezone.utc),
200+
name="2023"
201+
),
202+
]
203+
204+
backtests = app.run_vector_backtests(
205+
strategies=my_strategies,
206+
backtest_date_ranges=date_ranges,
207+
initial_amount=1000,
208+
backtest_storage_directory="./backtests"
209+
)
210+
211+
# Optional: recalculate metrics with updated calculations
212+
recalculate_backtests(backtests, risk_free_rate=0.04)
213+
214+
# Generate and save the comparison report
215+
report = BacktestReport(backtests=backtests)
216+
report.save("comparison_report.html")
217+
report.show(browser=True)
218+
```
219+
220+
## API Reference
221+
222+
### `BacktestReport`
223+
224+
| Method | Description |
225+
|--------|-------------|
226+
| `BacktestReport(backtests=[...])` | Create a report from one or more Backtest objects |
227+
| `BacktestReport(backtest)` | Create a report from a single Backtest (backward compatible) |
228+
| `BacktestReport.open(directory_path=..., backtests=[...])` | Load backtests from disk and/or combine with in-memory backtests |
229+
| `report.show(browser=False)` | Display the report. In Jupyter: renders inline. Otherwise: opens browser. Set `browser=True` to force browser. |
230+
| `report.save(path)` | Save the report as a self-contained HTML file |
231+
232+
### `recalculate_backtests`
233+
234+
| Parameter | Type | Description |
235+
|-----------|------|-------------|
236+
| `backtests` | `List[Backtest]` | The backtests to recalculate (mutated in place and returned) |
237+
| `risk_free_rate` | `float`, optional | Override risk-free rate. If `None`, uses each backtest's stored rate (falls back to `0.0`) |
238+
| `metrics` | `List[str]`, optional | Specific metrics to compute. If `None`, computes all default metrics |
239+
240+
**Returns:** `List[Backtest]` — the same backtest objects with updated metrics.
241+
242+
Recalculates all per-run `BacktestMetrics` from raw portfolio snapshots and trades, then regenerates `BacktestSummaryMetrics` for each backtest.

docusaurus/docs/Getting Started/backtesting.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ vector_backtest = app.run_vector_backtest(
4646

4747
## Event-Based Backtesting
4848

49-
Event-based backtesting simulates the market environment by processing each price update sequentially, just like live trading,
49+
Event-based backtesting simulates the market environment by processing each price update sequentially, just like live trading,
5050
based on the defined strategy interval (e.g., 1-minute, 5-minute bars).
5151

5252
### Running an Event-Based Backtest
@@ -167,10 +167,24 @@ Generate a visual report of your backtest:
167167
```python
168168
from investing_algorithm_framework import BacktestReport
169169

170+
# Single strategy
170171
report = BacktestReport(backtest)
171172
report.show(browser=True) # Opens in your default browser
173+
174+
# Multi-strategy comparison
175+
report = BacktestReport(backtests=[backtest_a, backtest_b])
176+
report.show()
177+
178+
# Load from saved backtests on disk
179+
report = BacktestReport.open(directory_path="./my_backtests")
180+
report.show()
181+
182+
# Save as a self-contained HTML file
183+
report.save("comparison_report.html")
172184
```
173185

186+
See [Backtest Reports](/docs/Getting%20Started/backtest-reports) for full documentation on the dashboard features, compare mode, and API reference.
187+
174188
### Accessing Metrics
175189

176190
```python
@@ -281,4 +295,3 @@ backtests = app.run_vector_backtests(
281295
- Learn about [Vector Backtesting](/docs/Advanced%20Concepts/vector-backtesting) for advanced optimization
282296
- Explore [Performance Optimization](/docs/Advanced%20Concepts/OPTIMIZATION_GUIDE) for large-scale testing
283297
- Check out [Parallel Processing](/docs/Advanced%20Concepts/PARALLEL_PROCESSING_GUIDE) for multi-core utilization
284-

docusaurus/sidebars.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ const sidebars = {
4141
type: 'doc',
4242
id: 'Getting Started/backtesting',
4343
},
44+
{
45+
type: 'doc',
46+
id: 'Getting Started/backtest-reports',
47+
},
4448
{
4549
type: 'doc',
4650
id: 'Getting Started/deployment',

examples/open.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import os
2+
from investing_algorithm_framework import BacktestReport, recalculate_backtests
3+
4+
5+
batch_one_path = os.path.join("examples", "batch_one")
6+
7+
if __name__ == "__main__":
8+
report = BacktestReport.open(directory_path=batch_one_path)
9+
recalculate_backtests(report.backtests)
10+
report.show()

investing_algorithm_framework/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
get_current_average_trade_loss, get_negative_trades, \
5757
get_positive_trades, get_number_of_trades, get_current_win_rate, \
5858
get_current_win_loss_ratio, create_backtest_metrics_for_backtest, \
59-
TradeTakeProfitService, TradeStopLossService
59+
recalculate_backtests, TradeTakeProfitService, TradeStopLossService
6060

6161

6262
__all__ = [
@@ -201,6 +201,7 @@
201201
"save_backtests_to_directory",
202202
"DataError",
203203
"create_backtest_metrics_for_backtest",
204+
"recalculate_backtests",
204205
"TakeProfitRule",
205206
"StopLossRule",
206207
"TradeStopLossService",

0 commit comments

Comments
 (0)