Skip to content

Commit 91c87b5

Browse files
committed
feat: redesign BacktestReport — unified single & multi-strategy HTML dashboard
- Replace old Plotly-based report with self-contained HTML dashboard - Jinja2 template with canvas-based charts, zero external dependencies - Separate CSS/JS/HTML template files for maintainability - Multi-strategy comparison: ranking table, equity curves, metric bars - Strategy selection filters equity curves and metric comparison charts - Pagination with Top 5/10/20/50/All for ranking table - Backtest window dropdown to view per-window metrics - Windows page with dedicated backtest window analysis table - System theme detection (prefers-color-scheme) with manual toggle - Overview KPIs: CAGR, Sharpe, Sortino, Calmar, Win Rate, Max DD - Fix _is_backtest() to match actual saved format - Fix tuple unpacking for all time-series data - Add example script and documentation - 23 unit tests for HTML report generation Closes #401
1 parent 1863106 commit 91c87b5

10 files changed

Lines changed: 2802 additions & 314 deletions

File tree

README.md

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<div align="center">
22
<h1>⚡ Investing Algorithm Framework</h1>
3-
3+
44
<p style="font-size: 18px; font-weight: 600; margin: 15px 0;">
55
🚀 <b>Build. Backtest. Deploy.</b> Quantitative Trading Strategies at Scale
66
</p>
7-
7+
88
<p style="font-size: 14px; color: #666; margin-bottom: 25px;">
99
The fastest way to go from trading idea to production-ready trading bot
1010
</p>
@@ -47,10 +47,10 @@
4747

4848
Stop wasting time on boilerplate. The **Investing Algorithm Framework** handles all the heavy lifting:
4949

50-
**From Idea to Production** — Write your strategy once, deploy everywhere
51-
📊 **Accurate Backtesting** — Event-driven and vectorized engines for realistic results
52-
**Lightning Fast** — Optimized for speed and efficiency
53-
🔧 **Extensible** — Connect any exchange, broker, or data source
50+
**From Idea to Production** — Write your strategy once, deploy everywhere
51+
📊 **Accurate Backtesting** — Event-driven and vectorized engines for realistic results
52+
**Lightning Fast** — Optimized for speed and efficiency
53+
🔧 **Extensible** — Connect any exchange, broker, or data source
5454
📈 **Production Ready** — Built for real money trading
5555

5656
## Sponsors
@@ -126,8 +126,8 @@ This creates:
126126
127127
## 📈 Example: A Simple Trading Bot
128128
The following example trading bot implements a simple moving average strategy.
129-
The strategy will use data from bitvavo exchange and will calculate
130-
the 20, 50 and 100 period exponential moving averages (EMA) and the
129+
The strategy will use data from bitvavo exchange and will calculate
130+
the 20, 50 and 100 period exponential moving averages (EMA) and the
131131
14 period relative strength index (RSI).
132132

133133
> This example uses [PyIndicators](https://github.com/coding-kitties/pyindicators) for technical analysis.
@@ -242,8 +242,8 @@ class RSIEMACrossoverStrategy(TradingStrategy):
242242
)
243243

244244
super().__init__(
245-
data_sources=data_sources,
246-
time_unit=time_unit,
245+
data_sources=data_sources,
246+
time_unit=time_unit,
247247
interval=interval
248248
)
249249

@@ -253,7 +253,7 @@ class RSIEMACrossoverStrategy(TradingStrategy):
253253
ema_data
254254
):
255255
"""
256-
Helper function to prepare the indicators
256+
Helper function to prepare the indicators
257257
for the strategy. The indicators are calculated
258258
using the pyindicators library: https://github.com/coding-kitties/PyIndicators
259259
"""
@@ -404,6 +404,27 @@ if __name__ == "__main__":
404404
report.show(backtest_date_range=backtest_range, browser=True)
405405
```
406406

407+
### 📊 Opening Backtest Reports
408+
409+
You can open and view previously saved backtest reports directly from disk:
410+
411+
```python
412+
from investing_algorithm_framework import BacktestReport
413+
414+
# Load all backtests from a directory (recursively finds all saved backtests)
415+
report = BacktestReport.open(directory_path="path/to/saved/backtests")
416+
report.show() # Opens in browser (or renders inline in Jupyter)
417+
418+
# Compare multiple backtests side by side
419+
report = BacktestReport.open(
420+
backtests=[backtest_a, backtest_b, backtest_c]
421+
)
422+
report.show()
423+
424+
# Save the report as a self-contained HTML file
425+
report.save("my_report.html")
426+
```
427+
407428
> You can find more examples [here](./examples) folder.
408429
409430
## 📚 Documentation
@@ -437,7 +458,7 @@ python -m unittest tests.services.test_trade_service.TestTradeService
437458

438459
🚨 **Use at Your Own Risk**
439460

440-
If you use this framework for your investments, **do not risk money which you are afraid to lose** until you have a clear understanding of how the framework works.
461+
If you use this framework for your investments, **do not risk money which you are afraid to lose** until you have a clear understanding of how the framework works.
441462

442463
**BEFORE YOU START USING MONEY WITH THE FRAMEWORK:**
443464
- ✅ Test your strategies thoroughly with backtesting
@@ -492,8 +513,8 @@ If you discover a bug in the framework, please [search our issue tracker](https:
492513

493514
<div align="center">
494515
<p>
495-
<a href="https://github.com/coding-kitties/investing-algorithm-framework/stargazers">⭐ Star us on GitHub</a> ·
496-
<a href="https://discord.gg/dQsRmGZP">💬 Join Discord</a> ·
516+
<a href="https://github.com/coding-kitties/investing-algorithm-framework/stargazers">⭐ Star us on GitHub</a> ·
517+
<a href="https://discord.gg/dQsRmGZP">💬 Join Discord</a> ·
497518
<a href="https://github.com/coding-kitties/investing-algorithm-framework/issues/new">🐛 Report Bug</a>
498519
</p>
499520
</div>
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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 table (sortable by CAGR, Sharpe, Max DD, etc.)
49+
- Normalized equity curves overlay
50+
- Per-strategy detail pages with Summary, Runs, and Performance tabs
51+
- Compare mode for selected strategies
52+
53+
### From Saved Backtests on Disk
54+
55+
Load previously saved backtests from a directory:
56+
57+
```python
58+
report = BacktestReport.open(directory_path="./my_backtests")
59+
report.show()
60+
```
61+
62+
The `open()` method recursively finds all valid backtest directories (containing `results.json` and `metrics.json`) and loads them into a single report.
63+
64+
You can also combine disk and in-memory backtests:
65+
66+
```python
67+
report = BacktestReport.open(
68+
backtests=[my_new_backtest],
69+
directory_path="./saved_backtests"
70+
)
71+
report.show()
72+
```
73+
74+
## Saving Reports
75+
76+
Save the report as a standalone HTML file you can share or open later:
77+
78+
```python
79+
report = BacktestReport(backtests=[backtest_a, backtest_b])
80+
report.save("strategy_comparison.html")
81+
```
82+
83+
The output is a single `.html` file with all CSS, JavaScript, and data embedded — no server or internet connection needed to view it.
84+
85+
## Viewing in Jupyter
86+
87+
`show()` automatically detects Jupyter notebooks and renders the dashboard inline:
88+
89+
```python
90+
# In a Jupyter notebook cell:
91+
report = BacktestReport.open(directory_path="./backtests")
92+
report.show() # Renders inline in the notebook
93+
report.show(browser=True) # Also opens in the browser
94+
```
95+
96+
## Dashboard Features
97+
98+
### Overview Page
99+
- **KPI cards**: Strategies count, backtest windows, best CAGR, best Sharpe, lowest max drawdown
100+
- **Backtest windows table**: Date ranges, duration, number of strategies per window
101+
- **Strategy ranking table**: Sortable by any metric, with best-in-class highlighting
102+
- **Equity curves**: Normalized percentage growth overlay for all strategies
103+
104+
### Strategy Pages
105+
Each strategy gets a dedicated page with three tabs:
106+
107+
| Tab | Contents |
108+
|-----|----------|
109+
| **Summary** | Full KPI grid (CAGR, Sharpe, Sortino, Calmar, Max DD, Profit Factor, Win Rate, Volatility, etc.) |
110+
| **Runs** | Backtest run comparison table, equity overlay across runs |
111+
| **Performance** | Monthly returns heatmap, yearly returns bar chart |
112+
113+
Use the run selector pills to switch between summary view and individual backtest runs.
114+
115+
### Compare Mode (Multi-Strategy)
116+
Select strategies via checkboxes in the ranking table, then click **Compare Selected** to see:
117+
- Side-by-side equity curves
118+
- Metric bar charts (CAGR, Sharpe, Max DD, Win Rate)
119+
- Monthly and yearly return comparisons
120+
121+
### Dark / Light Theme
122+
Toggle between dark and light mode using the sun icon in the top-right corner.
123+
124+
## Example: Full Workflow
125+
126+
```python
127+
from datetime import datetime, timezone
128+
from investing_algorithm_framework import (
129+
create_app, BacktestDateRange, BacktestReport
130+
)
131+
132+
app = create_app()
133+
# ... configure strategies, market, portfolio ...
134+
135+
# Run backtests across multiple time periods
136+
date_ranges = [
137+
BacktestDateRange(
138+
start_date=datetime(2022, 1, 1, tzinfo=timezone.utc),
139+
end_date=datetime(2022, 12, 31, tzinfo=timezone.utc),
140+
name="2022"
141+
),
142+
BacktestDateRange(
143+
start_date=datetime(2023, 1, 1, tzinfo=timezone.utc),
144+
end_date=datetime(2023, 12, 31, tzinfo=timezone.utc),
145+
name="2023"
146+
),
147+
]
148+
149+
backtests = app.run_vector_backtests(
150+
strategies=my_strategies,
151+
backtest_date_ranges=date_ranges,
152+
initial_amount=1000,
153+
backtest_storage_directory="./backtests"
154+
)
155+
156+
# Generate and save the comparison report
157+
report = BacktestReport(backtests=backtests)
158+
report.save("comparison_report.html")
159+
report.show(browser=True)
160+
```
161+
162+
## API Reference
163+
164+
### `BacktestReport`
165+
166+
| Method | Description |
167+
|--------|-------------|
168+
| `BacktestReport(backtests=[...])` | Create a report from one or more Backtest objects |
169+
| `BacktestReport(backtest)` | Create a report from a single Backtest (backward compatible) |
170+
| `BacktestReport.open(directory_path=..., backtests=[...])` | Load backtests from disk and/or combine with in-memory backtests |
171+
| `report.show(browser=False)` | Display the report. In Jupyter: renders inline. Otherwise: opens browser. Set `browser=True` to force browser. |
172+
| `report.save(path)` | Save the report as a self-contained HTML file |

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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import os
2+
from investing_algorithm_framework import BacktestReport
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+
report.show()

0 commit comments

Comments
 (0)