quantlab.backtest.approximate#

The approximate backtest: every rebalance decided at once, the window simulated as arrays.

A backtest run (BaseBacktester.run()) decides its rebalances one bar at a time with the holdings replayed under the Execution rules, and pays a fixed cost for its report and run directory. The approximate backtest (ADR 0045) is a second backtest for parameter sweeps: built once over a price dataset (its window’s prices are read once), it is run many times on (predictions, rule) pairs and returns each account in memory, writing nothing.

Kept from a backtest run: the rebalance schedule (every rebalance_periods-th bar from the window’s first bar, never its last), a weight decided at t filling at t + 1’s fill price, holdings marked at the forward-filled valuation price and drifting with prices between fills, tradability judged at the decision bar. Given up: rejected orders, locked positions, delisting settlements (a delisted holding stays at its last valuation, never turned into cash), the cash cap on buys and min_trade; fees and slippage are one cost on traded weight. A fill price missing at a fill bar falls back to the previous bar’s valuation.

The approximate backtest decides with its own implementations of the rules it runs (quantlab.portfolio.predefined.approximate, chosen through the portfolio layer’s quantlab.portfolio.approximate.implementation): TopNConstructor, MeanVarianceOptimizer and ActiveRiskOptimizer, chosen by the rule’s exact class and reading its config through a field table, with a LedoitWolfEstimator or a FactorRiskStoreEstimator as the covariance estimator; any other rule or estimator, a subclass of one, or a config field it does not handle is refused before any data is read. The rules’ own classes decide one bar at a time for a backtest run and know nothing of those implementations. The decision inputs are read through quantlab.portfolio.decision_inputs.DecisionInputs.panel, so the point-in-time reads stay in one module. The window is cut into chunks of chunk_bars bars, each decided once (the implementation’s decide), in time order: a chunk is handed the book filled before it and each decision bar’s valuation and fill prices (PanelPrices), so a rule whose decision reads the holdings (a turnover penalty) drifts them and solves its bars forward, each once, and a rule that does not solves them at once. An iterative solver stops a bar when its residual falls to tolerance, or after max_iterations, with a warning (Stopping). The simulation and the drift are the execution layer’s (quantlab.execution.approximate), compiled; this module only reads the prices and the schedule, calls the implementations and the simulation, and computes the statistics.

The account is an account record (quantlab.runs.run_metrics.AccountRecord), as a backtest run’s is: its fills, rebalance record and value. Its metric blocks are run_metrics of it and its attribution block quantlab.runs.backtest_attribution.attribution_metrics of it, the functions a backtest run assembles its own blocks with, so the numbers mean what a run’s metrics.json rows mean (ADR 0034). Given a benchmark_dataset, the account is measured against the benchmark bought and held.

Classes

ApproximateAccount

The account an approximate backtest simulated: ApproximateBacktest.run's result.

ApproximateBacktest

Decide every rebalance of a prediction panel at once and simulate the window as arrays.