quantlab.runs.backtest_report.write_backtest_report#

quantlab.runs.backtest_report.write_backtest_report(account, path, *, title, metrics=None, weights=None, config=None, benchmark=None, folds=None, extras=None)[source]#

Write the HTML report of one backtest run to path, drawn from its record.

The page opens with a header (the run, its window and benchmark), a sidebar of sections and a row of headline numbers. Each section holds its charts beside the tables that explain them: Overview (the windows timeline, in-sample against out-of-sample when the run has an in-sample part, the Performance chart of NAV, drawdown, monthly returns and the monthly heatmap, and the strategy against the benchmark by group), Excess (cumulative excess return with a log / arithmetic toggle, the excess drawdown, and the excess over the benchmark), Rolling (one-year panels), Portfolio (turnover, holdings and exposure per rebalance, and trading), Holdings (each bar’s targets, holdings and cash, when the account has holdings), Attribution and Factor attribution (from extras) and Setup & notes. Every headline number, metric row and chart explains itself on hover. The headline numbers and the main tables are the out-of-sample slice when the run has an in-sample part, else the whole window. Drawdown is value / running max - 1, negative below a peak, in every table, card and chart. A section, card or table whose input is missing is left off the page, and a metric the page does not know is still shown, in an “Other” table.

Every chart input is derived from the arguments: the in-sample range (shaded) and the notes from the metrics; the turnover per rebalance fill bar (backtest_stats.turnover of the account’s fills, its settlements left out); the decided rebalances from its rebalance record; the Rolling window (year_freq over the bar interval); the deepest drawdown, marked from its valley to its recovery (backtest_stats.drawdown_span of the value); the timeline; and the benchmark’s curve.

Parameters:
accountAccountRecord

What the account did (quantlab.runs.run_metrics): its value (drawn as is, so the page and the persisted equity carry the same numbers), returns, fills, settlements, rebalance record, initial cash, bar interval and year, and its holdings (each symbol’s value at each bar’s close over the book’s value), which give the page a Holdings section.

pathstr | Path

The report.html to write.

titlestr

Page title, typically the run directory name.

metricsMapping, optional

The run’s metrics, as metrics.json holds them: the slices whole, in_sample and out_of_sample, benchmark and relative, execution and portfolio_construction, the split keys (in_sample_range is shaded), trained_checkpoint and notes (listed in the Notes section). A run_cv() run’s mapping (stitched, folds, notes) is read through stitched, and its folds are the timeline’s rows.

weightsxarray.DataArray, optional

Target weights on (timestamp, symbol), an all-NaN row on a bar that holds; the Portfolio section draws the holdings and exposure of the decided rebalances’ rows (a NaN cell counted at that symbol’s last target) and the Holdings section the targets in force.

configMapping, optional

The run’s config mapping (BaseBacktester.get_config(), a run directory’s config.json), read for the Setup lines: Bar interval; Benchmark with its source; the deepest drawdown; Model mode (or Signal for a run without a model); Rebalance every; Portfolio construction (the rule’s repr), or Top N and Direction; Fees; Trained checkpoint. Every key is read with .get(). Without it the page has only the lines of extras.setup.

benchmarkBenchmarkRecord, optional

The benchmark (quantlab.runs.run_metrics), drawn beside the portfolio and named by the metrics’ benchmark symbol (else its own symbol, else “benchmark”); its curve is its value, or init_cash * cumprod(1 + returns) (bought and held from the account’s capital) when it has none. A benchmark that does not cover the account’s bars is left off.

foldssequence of Mapping, optional

The timeline’s rows of a run traded by several models, each with fold (its number), training_window, traded (its first and last traded bar label) and in_sample_range. Default: the rows of metrics["folds"] (a run_cv() run), else one “model” row when the metrics have a training_window, else none.

extrasReportExtras, optional

What only some runs have (attribution, extra tables, Setup lines, the Holdings section’s names and prices).

Examples

>>> import pandas as pd, xarray as xr
>>> from quantlab.runs.run_metrics import AccountRecord
>>> bars = pd.bdate_range("2024-01-01", periods=5)
>>> on_bars = dict(dims="timestamp", coords={"timestamp": bars})
>>> account = AccountRecord(
...     value=xr.DataArray([100.0, 104.0, 98.0, 103.0, 110.0], **on_bars),
...     returns=xr.DataArray([0.0, 0.04, -0.0577, 0.051, 0.068], **on_bars),
...     orders=xr.Dataset(),
...     rebalances=xr.Dataset({"scheduled": ("timestamp", [False] * 5),
...                            "decided": ("timestamp", [False] * 5)}, coords={"timestamp": bars}),
...     init_cash=100.0, bar_interval="1D", year_freq=pd.Timedelta(days=252),
... )
>>> write_backtest_report(
...     account, "demo.html", title="demo_run",
...     metrics={"whole": {"Total Return [%]": 10.0}, "notes": ["No borrow cost is modelled."]},
...     config={"rebalance_periods": 5, "fees": 0.001},
... )
>>> page = open("demo.html").read()
>>> "<h1>demo_run</h1>" in page, "<td>5 bars</td>" in page
(True, True)