8. Backtesting engine and performance (design perspective)

This chapter explains, from the perspective of architecture and implementation, how qteasy’s backtesting engine uses vectorization and Numba, and how it differs from frameworks such as VectorBT, for readers who need a deeper understanding or are making technical choices. For usage-level explanations, see 6. Backtesting Engine and Performance in “Backtest and Evaluate Trading Strategies”.

8.1. 1. 回测引擎在整体中的位置

The workflow of backtest mode (mode=1) is summarized in Backtesting, Live Trading, and Optimization: prepare historical data → build the Backtester → run the Operator step by step according to group_timing_table to generate signals → parse signals and simulate fills → evaluate and output. Among these, “parse signals and simulate fills” is handled by the Numba functions in the backtest module, achieving “sequential along the time dimension, vectorized along the instrument dimension”.

8.2. 2. Numba 使用点与向量化结构

2.1 Core functions

In qteasy/backtest.py:

  • backtest_step: @njit(nogil=True, cache=True). Within a single step, based on the signals and the current positions, cash, and settlement queue, it computes buy/sell quantities, fees, position updates, and settlement-queue updates; it performs array operations over the entire batch of instruments.

  • calculate_trade_results: @njit, used for calculating trade results and cash changes.

  • backtest_batch_steps: @njit(nogil=True, cache=True). It loops inside Numba by signal_count (number of time steps); at each step it calls backtest_step to complete the whole backtest segment, achieving “sequential along the time dimension, vectorized along the instrument dimension”.

  • backtest_flash_steps: @njit(nogil=True, cache=True). Similar to backtest_batch_steps, but it does not retain intermediate cash and position series—only the final state is kept—to save memory during the optimization phase when backtesting multiple parameter sets.

In qteasy/blender.py, some operators used for signal blending (such as op_sum, op_floor, etc.) also use @njit for acceleration.

2.2 Time-Dimension Ordering and Vectorization Along the Instrument Dimension

  • The time dimension must be sequential: because there are states such as the settlement queue, T+1, MOQ, etc., the next trade depends on the current positions and settlement state, so it cannot be fully parallelized along the time axis or matrixized all at once.

  • Instrument-dimension vectorization: within each time step, use NumPy arrays and inlined operations inside Numba for all instruments’ signals, positions, buy/sell quantities, fees, etc., avoiding Python-level loops.

Therefore, a single backtest can be understood as: a time-step loop inside one Numba layer + vectorized computation within each step.

2.3 Optimization stage

In qteasy/optimization.py, multiple parameter sets run backtests in parallel via multiprocessing (e.g., ProcessPoolExecutor); within each set it uses paths such as backtest_flash_steps, keeping only the final cash/positions to reduce memory usage and increase throughput.

8.3. 3. 与 VectorBT 的架构差异

Dimension

qteasy

VectorBT

Time dimension

Sequential stepping (for-loop steps), vectorized within each step

One-shot matrix operations over the full timeline, with no explicit time loop

Asset dimension

At each step, perform array operations on all assets (1D arrays)

The instrument and time participate in broadcasting together; multiple strategies/multiple parameter sets can be broadcast in one go.

Core acceleration

backtest_step, backtest_batch_steps, backtest_flash_steps, etc. are @njit; signal parsing, the settlement/delivery queue, etc. also use Numba.

NumPy + Numba, with strategies expressed as pure array operations

States and constraints

Explicitly maintains cash, positions, and a settlement/delivery queue (T+1, etc.); the state depends on the next trade, so the time dimension must be sequential.

Mostly a simplified equity curve, without emphasizing T+1/settlement/MOQ, making it easy to build a full matrix.

Conclusion: qteasy is not “lacking vectorization/Numba”; rather, on the premise of ensuring state correctness (T+1, settlement, MOQ, multi-strategy merging, etc.), it chose a compromise of “time-ordered + instrument-dimension vectorization + Numba per-step”; it and VectorBT’s “full-matrix broadcasting” each have their own applicable scenarios (qteasy leans toward rigorous rules and consistency with live trading, while VectorBT leans toward rapid screening over massive parameter grids).

8.4. 4. 相关文档