Architecture¶
Pipeline¶
QuantLab enforces an ordered backtest pipeline with independently testable
responsibilities. Strategies may call feature functions directly or use the
optional FeaturePipeline helper; the engine does not require that helper.
flowchart LR
A[Market Data Sources] --> A2[Raw Data Inspection]
A2 --> B[Data Cleaning]
B --> C[Final Validation]
C --> E[Strategy using Feature Functions]
E --> F[Portfolio Allocation]
F --> G[Constraints]
G --> H[Execution Model]
H --> I[Backtest Engine]
I --> J[Risk Metrics]
I --> K[Trade Log]
I --> L[Equity Curve]
J --> M[Validation]
K --> N[Reporting]
L --> N
M --> N
N --> O[Dashboard]
Package layout¶
src/quantlab/
├── cli.py # Typer CLI (download / backtest / walk-forward / report / dashboard)
├── config.py # Pydantic ExperimentConfig (loaded from YAML)
├── constants.py # Canonical schema, annualisation factors, paths
├── exceptions.py # QuantLabError hierarchy
├── logging_config.py # Standard-library logging setup
│
├── data/ # Sources, cleaning, validation, Parquet storage, universes
├── features/ # Returns, momentum, volatility, mean reversion, cross-sectional, pipeline
├── strategies/ # BaseStrategy + concrete strategies (signals only)
├── portfolio/ # Allocators, constraints, rebalancing, volatility targeting
├── execution/ # Commission, spread, slippage models
├── backtesting/ # Engine, accounting, trade log, benchmark, result
├── risk/ # Performance & risk metrics, drawdown, VaR/CVaR, exposure, stress
├── validation/ # Chronological splits, walk-forward, sensitivity, bootstrap, robustness
├── reporting/ # Charts, tables, HTML report, research narrative
└── dashboard/ # Streamlit app
Why this separation¶
- Strategies emit bounded position intents (
[-1, 1]per asset per date — a pair's two legs additionally encode their relative hedge-ratio weighting in the signal magnitude, not a pure directional flag). They never compute final portfolio weights, costs, or returns. This makes each strategy trivially unit-testable against a synthetic dataset with an obvious expected signal. - Allocators turn signals into weights and know nothing about execution costs or accounting.
- The execution model computes costs from weight changes, independent of which strategy or allocator produced them.
- Signals, weights, costs and returns are combined in one fixed order,
with the weight-shift step as a hard, tested barrier against look-ahead
bias.
BacktestEngine.run()is that assembly for a single backtest. Known architectural duplication:WalkForwardValidator. _build_oos_result()independently re-assembles the same stitched-OOS- series case from the same underlying functions rather than callingBacktestEngine.run()itself — a fix to that assembly step currently needs applying at both call sites, not one shared entry point.
Data flow shapes¶
- Long format (
timestamp, symbol, open, high, low, close, adjusted_close, volume) is the canonical on-disk/interchange format. - Wide format (
index=dates, columns=symbols) is what features, strategies and the engine operate on internally, built viaquantlab.data.base.pivot_field.