Skip to content

Architecture Overview

Two Backtest Paths

Quant101 offers two mental models for systematic investing, each with its own code path:

Strategy Path ("I Trade")                Pipeline Path ("I Allocate")
─────────────────────────                ────────────────────────────
Indicators per ticker                    Factors for all stocks simultaneously
Row-by-row signal logic                  Cross-sectional ranking
Buy/sell events                          Portfolio weights
Trade list + P&L                         Weight matrix + return series

BBIBOLLStrategy                          AlphaConfig
    → BacktestEngine                         → run_alpha_pipeline()
        → PerformanceAnalyzer                    → WeightBacktester

The pipeline path is the primary research workflow. The strategy path is kept for execution-level analysis and comparison.

Module Map

src/
├── data/              ← Data layer (OHLCV loading, universe)
│   ├── fetcher/       ← Polygon.io S3, FMP, yfinance
│   ├── loader/        ← OHLCV loading, split adjustment, caching
│   └── universe.py    ← Named stock universe registry
├── alpha/             ← Factor research
│   ├── factor_analyzer.py    ← IC, IR, decay, quantile returns
│   ├── preprocessing.py      ← Winsorize, z-score, rank, neutralize
│   ├── combination.py        ← EW, IC-weight, mean-var, risk-parity
│   └── forward_returns.py    ← 1/5/10/20-day forward returns
├── portfolio/         ← Alpha pipeline (the core)
│   ├── alpha_config.py       ← AlphaConfig + FactorConfig dataclasses
│   ├── factors.py            ← Factor function registry
│   ├── pipeline.py           ← 7-stage signal→weights→returns
│   └── walk_forward_runner.py ← Per-fold IS/OOS evaluation
├── risk/              ← Risk measurement & position sizing
│   ├── risk_metrics.py       ← VaR, CVaR, drawdown, tail ratio
│   ├── return_analysis.py    ← Normality tests, QQ-plot, tails
│   └── position_sizing.py    ← EW, inverse-vol, vol-target, signal-weighted
├── execution/         ← Transaction cost modeling
│   ├── cost_model.py         ← Fixed, Spread, SqrtImpact, Composite
│   └── cost_analysis.py      ← Turnover, net returns, breakeven
├── validation/        ← Out-of-sample rigor
│   ├── walk_forward.py       ← Rolling/anchored walk-forward splitter
│   ├── statistical_tests.py  ← Bootstrap CI, PSR, DSR
│   └── multiple_testing.py   ← Bonferroni, Holm, BH corrections
├── backtest/          ← Backtesting engine
│   ├── engine.py             ← Legacy strategy executor
│   ├── weight_backtester.py  ← Pipeline→backtest bridge
│   ├── portfolio_tracker.py  ← Weight×return→equity curve
│   └── result_exporter.py    ← Reports, HTML, CSV export
└── strategy/          ← Trading strategies (legacy path)
    ├── bbiboll_strategy.py   ← BBI + Bollinger strategy
    └── indicators/           ← Technical indicator registry

Data Flow

The pipeline path follows a strict linear flow:

OHLCV Data
compute_daily_returns()        ← close → daily returns
build_factor_pipeline()        ← compute factors, preprocess, combine
    │  uses: FactorConfig.direction, preprocessing params
    │  uses: combination method (EW, IC-weight, etc.)
build_sizing_methods()         ← factor signal → portfolio weights
    │  methods: equal-weight, inverse-vol, vol-target, signal-weighted
resample_weights()             ← daily weights → rebalance frequency
compute_portfolio_return()     ← weights × returns → portfolio return
WeightBacktester               ← analytics, Sharpe, drawdown, export

All stages are wrapped by run_alpha_pipeline(ohlcv, config=AlphaConfig(...)), which returns a dict with portfolio_returns, sharpe, weights, and diagnostic DataFrames.

Configuration: AlphaConfig

The entire pipeline is driven by a single config object:

from portfolio.alpha_config import AlphaConfig, FactorConfig

config = AlphaConfig(
    factor_configs={
        "bbiboll": FactorConfig(direction=-1),
        "vol_ratio": FactorConfig(direction=-1),
    },
    combination_method="ic_weight",
    sizing_method="Equal-Weight",
    n_long=10,
    n_short=10,
    rebal_every_n=5,
)

results = run_alpha_pipeline(ohlcv, config=config)
print(f"Sharpe: {results['sharpe']:.3f}")