quantlab.api.backtest#
- quantlab.api.backtest(prices, *, weights=None, scores=None, top_n=None, direction='long_only', rebalance_periods=1, fill='open', valuation='close', market='equity', trading_days_per_year=None, session_minutes_per_day=None, fees=0.0005, slippage=0.0005, init_cash=1000000.0, benchmark=None, output_dir=None, columns=None)[source]#
Backtest target weights, or the top-N names of a score, on a price frame.
Exactly one signal is given.
weightsare simulated as given;scoresbecome equal weights on thetop_nhighest-scoring names (and, fordirection= "long_short", minus thetop_nlowest) everyrebalance_periodsbars, a name being picked only when it has a score and a fill price at that bar, and a held name without one keeping its weight. A weight formed at bar t fills at bar t+1’sfillprice and the portfolio is valued at thevaluationprice. The run covers every bar ofprices; the bar interval is the most common spacing of its timestamps. There is no training window, so the metrics cover the whole window. Nothing is written and no W&B run is started, unlessoutput_diris given.Weights follow the target-weight contract: on a bar that has weights every symbol has a finite weight and the gross exposure (sum of absolute weights) is at most 1; a bar without weights holds the current positions. A weight frame may leave things out:
a symbol without a weight on a bar that has weights for other symbols gets weight 0: a row left out of a long frame, or a NaN cell of a wide frame, so a sparse long frame listing only the names held and its pivot mean the same;
a bar without any weight is a hold: absent from a long frame, or an all-NaN row of a wide frame. A frame listing only the rebalance bars is therefore enough; to go flat on a bar, give it explicit zeros;
a NaN written in a long frame beside finite weights on the same bar keeps that symbol’s holding there, untraded; a bar whose weights have a gross exposure above 1 raises.
Timestamps in a time zone are converted to UTC and naive ones are taken as UTC; when the weights’, scores’ or benchmark’s bars miss the prices’ and the inputs came in different zones, the error names both zones.
- Parameters:
- pricespandas.DataFrame, polars.DataFrame or xarray.Dataset
Bars in long form, one row per
timestampandsymbol, holding thefillandvaluationcolumns. The report’s frames are of this library.- weightsDataFrame, optional
Target weights, long (
timestamp,symboland one value column, of any name) or wide (timestamps in atimestampcolumn or in a pandasDatetimeIndexof any name; one column per symbol).- scoresDataFrame, optional
Scores ranking the symbols, higher is better, long or wide like
weights. A symbol without a score on a bar (a left-out row, a NaN cell) is not selected.- top_nint, optional
Names held per side; required with
scores, refused withweights.- direction{“long_only”, “long_short”}, default “long_only”
The selection side, with
scoresonly.- rebalance_periodsint, default 1
With
scores, rebalance every this many bars. Withweights, which are traded as given, it is recorded with the run only: the turnover and win-rate metrics count the bars that have weights, which are the rebalances.- fill, valuationstr, default “open”, “close”
The price columns orders fill at and the portfolio is valued at.
- market{“equity”, “crypto”}, default “equity”
The annualization: 252 trading days of 390 minutes, or 365 days of 1440.
- trading_days_per_year, session_minutes_per_dayint, optional
Override one half of
market’s annualization.- fees, slippagefloat, default 0.0005
Proportional cost per trade, as in
BacktestConfig.- init_cashfloat, default 1_000_000.0
Starting cash.
- benchmarkDataFrame, optional
One symbol’s bars, with the
fillandvaluationcolumns, bought and held on the same bars for comparison; adds"benchmark"and"relative"(excess return and drawdown) to the metrics.- output_dirstr or Path, optional
Also write the library’s run directory under this directory, input panels included, so the run rebuilds from it (see
BacktestReport.save).- columnsmapping of str to str, optional
Renames caller columns onto the canonical names,
{"date": "timestamp"}. The prices must have every key; the other frames are renamed where they have one.
- Returns:
- BacktestReport
Equity, returns, weights, orders and trades as frames, the metrics as a dict, the benchmark curve,
plot(),save()and the library result asraw.
- Raises:
- ValueError
If both or neither of
weightsandscoresare given,top_nis missing with scores or given with weights,directionis set with weights,marketis unknown, a price column is missing (named), the prices have fewer than two bars, the weights or scores name a bar or symbol the prices lack, or a weight row breaks the contract (naming the bar).- TypeError
If a frame is not a pandas or polars DataFrame (or an xarray panel).
Examples
>>> import numpy as np >>> import pandas as pd >>> import quantlab.api as qa >>> rng = np.random.default_rng(0) >>> bars = pd.bdate_range("2024-01-01", periods=60) >>> symbols = ["AAA", "BBB", "CCC", "DDD"] >>> close = 100 * np.exp(rng.normal(0, 0.01, (60, 4)).cumsum(axis=0)) >>> prices = pd.DataFrame({ ... "timestamp": np.repeat(bars, 4), "symbol": symbols * 60, ... "open": (close * 0.999).ravel(), "close": close.ravel(), ... })
Hold
AAAandBBBhalf and half from the first bar:>>> weights = pd.DataFrame({"timestamp": bars[0], "symbol": ["AAA", "BBB"], ... "weight": [0.5, 0.5]}) >>> report = qa.backtest(prices, weights=weights) >>> report BacktestReport(60 bars x 4 symbols, total return 0.86%) >>> report.orders[["timestamp", "symbol", "side"]] timestamp symbol side 0 2024-01-02 AAA Buy 1 2024-01-02 BBB Buy
Or hold the two names with the highest past 5-bar return, rebalanced every five bars (the first rebalance has no scores yet, so it stays flat):
>>> momentum = prices.assign( ... score=prices.groupby("symbol")["close"].pct_change(5) ... )[["timestamp", "symbol", "score"]] >>> report = qa.backtest(prices, scores=momentum, top_n=2, rebalance_periods=5) >>> sorted(report.metrics) ['execution', 'notes', 'whole'] >>> report.weights[report.weights["timestamp"] == bars[5]] timestamp symbol weight 20 2024-01-08 AAA 0.0 21 2024-01-08 BBB 0.5 22 2024-01-08 CCC 0.0 23 2024-01-08 DDD 0.5