quantlab.backtest.engine_vectorbt.VectorBtBacktester#

class quantlab.backtest.engine_vectorbt.VectorBtBacktester(config)[source]#

Bases: BaseBacktester

Abstract backtester that simulates target weights with vectorbt.

Subclasses supply config_cls, MARKET and _generate_signals; everything from the weights onward is implemented here. The constructor takes one config argument, an instance of config_cls; see BaseBacktester. Three engine conventions matter to a reader of the results:

  • A weight row formed at bar t fills at bar t + 1 at the market’s fill price (the weights are shifted one bar before they reach Portfolio.from_orders).

  • A target percentage is sized against the price config.sizing_basis names. "fill" (the default, vectorbt’s own) measures it against the portfolio valued at the fill price of the bar it executes on and divides by that price. "valuation" uses the valuation price of the signal bar t instead (vectorbt’s val_price): the portfolio valued at t’s close, divided by t’s close, as an order placed after the close must be sized. Either way the order fills at t + 1’s fill price.

  • No borrow or short-financing cost is modelled, so short-side returns are optimistic.

Execution at the fill bar (ADR 0014): both price columns are forward-filled before they reach vectorbt, which would otherwise keep a NaN-priced holding at its last value and silently skip every later rebalance of the whole group. On each fill bar the engine then does what a market would:

  • A NaN weight keeps the symbol’s holding; a row may mix NaN and finite targets.

  • An order whose raw (not forward-filled) fill price is NaN is a rejected order: the holding is kept and the order expires. It is recorded in SimulationResult.rejected_orders when it would have traded (a non-zero target, or a symbol held before the bar).

  • A symbol the price dataset marks as delisted on bar b (MarketDataset.delisting_bars) is settled on bar b + 1: the holding becomes cash at its last valuation price, with no fee or slippage, whatever the weights ask for it there. Each settlement is recorded in SimulationResult.settlements.

Records name the symbol by symbol (the display name: the ticker the symbol traded under that day when the price store has a ticker lookup file, otherwise the axis label) and axis_symbol (the label on the panel’s symbol axis). A symbol whose prices are NaN at the start of the window is not yet listed rather than delisted, and trades normally once it lists.

Trade statistics use vectorbt’s position view: one trade is one symbol’s round trip from entry to flat, so trimming a holding back to its target weight is not a closed trade. vectorbt’s default exit-trade view counts every trim, and because only winners get trimmed under equal weighting it inflates the win rate. The number of fills is reported separately as Total Orders in the metrics.

Examples

A concrete engine names its config class and market conventions and turns predictions into target weights:

class MyBacktester(VectorBtBacktester):
    config_cls = CrossSectionBacktestConfig
    MARKET = MarketSpec(
        fill_price_column="open",
        valuation_price_column="close",
        trading_days_per_year=252,
        session_minutes_per_day=390,
    )

    def _generate_signals(self, predictions, prices, delisted):
        ...  # a Dataset with ``weight`` on (timestamp, symbol)

result = MyBacktester(config).run()
result.simulation.orders  # one record per fill
STATS_METRICS = ('start', 'end', 'period', 'start_value', 'end_value', 'total_return', 'max_gross_exposure', 'total_fees_paid', 'max_dd', 'max_dd_duration', 'total_trades', 'total_closed_trades', 'total_open_trades', 'open_trade_pnl', 'win_rate', 'best_trade', 'worst_trade', 'avg_winning_trade', 'avg_losing_trade', 'avg_winning_trade_duration', 'avg_losing_trade_duration', 'profit_factor', 'expectancy', 'sharpe_ratio', 'calmar_ratio', 'omega_ratio', 'sortino_ratio')#

The Portfolio.stats metric names reported for the whole window. benchmark_return is left out: vectorbt would compare against the equal-weighted traded universe, not the configured benchmark, which the benchmark and relative metric blocks report instead.

fill_delay_bars = 1#

A weight formed at bar t fills at bar t + 1 (the Execution rules’ FILL_DELAY_BARS); labels must use this delay.