quantlab.runs.run_metrics.run_metrics#

quantlab.runs.run_metrics.run_metrics(account, *, engine_rows=None, split=None, benchmark=None, within=None)[source]#

Return the metric blocks of an account, in metrics.json’s layout.

The blocks, in order:

  • whole: over the whole account (within omitted), engine_rows followed by the three turnover rows, Total Orders (the number of fills) and the win rates; over a stretch (within), the slice rows below over that stretch, its first bar’s return included.

  • execution: rejected_order_count and rejected_orders (over a stretch, only those whose fill bar is in it), then, over the whole account, max_target_deviation.

  • in_sample and out_of_sample (when split has slices, slice_ranges): the return statistics, Total Orders, Total Fees Paid and Traded Notional of the fills inside the slice, Total Closed Trades (round trips closed inside it), Total Open Trades (open at each range’s end), the three turnover rows and the win rates; None for a slice without a bar.

  • benchmark (with benchmark): its symbol, axis_symbol and return statistics over whole and each slice.

  • relative (with benchmark): the account against the benchmark, backtest_stats.relative_stats and the win rates against it, over whole and each slice.

  • every key of split, copied as it is.

Turnover counts the fills of account.orders but its settlements, over the decided rebalances of account.rebalances; the win rates’ holding periods start at the decided rebalances’ fill bars. Every row is annualized by account.year_freq over account.bar_interval.

Parameters:
accountAccountRecord

What the account did.

engine_rowsMapping, optional

The engine’s own whole-window rows (vectorbt’s portfolio statistics), which open whole. Required over the whole account, refused with within.

splitMapping, optional

The run’s split keys (training_window, in_sample_range or in_sample_ranges, out_of_sample_ranges); without one there is no slice.

benchmarkBenchmarkRecord, optional

The benchmark on the account’s bars.

within(str, str), optional

Inclusive bar labels of the stretch measured as whole instead of the whole account: a run_cv() fold of its stitched account (ADR 0033).

Returns:
dict

The blocks listed above.

Raises:
ValueError

If engine_rows is missing over the whole account or given with within.

Examples

With the account of AccountRecord’s example: one rebalance decided on the first bar, filled on the second.

>>> bars = pd.bdate_range("2024-01-01", periods=3)
>>> on_bars = dict(dims="timestamp", coords={"timestamp": bars})
>>> account = AccountRecord(
...     value=xr.DataArray([100.0, 110.0, 99.0], **on_bars),
...     returns=xr.DataArray([0.0, 0.1, -0.1], **on_bars),
...     orders=xr.Dataset({
...         "timestamp": ("order", bars[[1]].values), "symbol": ("order", ["AAA"]),
...         "size": ("order", [10.0]), "price": ("order", [10.0]), "fees": ("order", [0.0]),
...     }),
...     rebalances=xr.Dataset({
...         "scheduled": ("timestamp", [True, False, False]),
...         "decided": ("timestamp", [True, False, False]),
...     }, coords={"timestamp": bars}),
...     init_cash=100.0, bar_interval="1D",
...     year_freq=backtest_stats.year_freq("1D", 252, 390),
... )
>>> metrics = run_metrics(account, engine_rows={"End Value": 99.0})
>>> list(metrics)
['whole', 'execution']
>>> list(metrics["whole"])
['End Value', 'Turnover per Rebalance [%]', 'Total Turnover [%]',
 'Annualized Turnover [%]', 'Total Orders', 'Rebalance Win Rate [%]',
 'Monthly Win Rate [%]']
>>> metrics["whole"]["Total Turnover [%]"], metrics["whole"]["Total Orders"]
(100.0, 1)
>>> split = {"training_window": None, "in_sample_range": None,
...          "out_of_sample_ranges": [("2024-01-02", "2024-01-03")]}
>>> stretch = run_metrics(account, split=split, within=("2024-01-02", "2024-01-03"))
>>> round(stretch["out_of_sample"]["Total Return [%]"], 6), stretch["out_of_sample"]["Total Orders"]
(-1.0, 1)