The mathematics of Drift
Full derivations for every formula. Each section explains the problem, derives the solution from first principles, and notes how it maps to source code.
Data — caching and provider abstraction
Why Parquet?
Parquet is a columnar format. For time-series data where you frequently slice by date range or select specific columns, columnar storage means only the requested columns are read off disk. A row-based format reads every column even when you want one. For a 500-ticker, 5-year OHLCV dataset this difference is 10–50×.
Snappy compression decompresses at ~500 MB/s — 3× faster than gzip at only ~10% size penalty. On repeated pipeline runs this matters more than cold storage size.
Cache key scheme
Each file is addressed by a content hash of (dataset, ticker, start, end, interval). This prevents stale reads across date ranges. The deliberate choice NOT to support partial-range reuse avoids a class of subtle bugs where a cached wide range silently returns stale data for a sub-range request.
Factor engine — cross-sectional signal construction
The rank transform — derivation from first principles
We want a function mapping ranks r ∈ {1..N} linearly to scores s ∈ {−1,+1}. Write the linear form s = a·r + b. Two boundary conditions pin the two unknowns:
Subtract the first equation from the second — b cancels:
Substitute back to find b, then combine over a common denominator:
The seven factors
Fama-French 5 factors: Market beta (systematic risk), Size (−log mktcap), Value (B/M ratio), Profitability (ROE), Investment (−asset_growth). Plus Momentum (12-1 month return, skip most recent month per Jegadeesh & Titman 1993) and Quality (composite z-score of ROE, low leverage, low earnings volatility per Asness et al. 2019).
HMM regime detection — the forward algorithm
The computational problem
For K=3 states and T=252 trading days, brute-force enumeration of all state sequences requires K^T = 3^252 ≈ 10^120 operations. The forward algorithm reduces this to O(K²T) via dynamic programming.
The forward variable
Define α_t(k) = P(o₁,...,o_t, s_t = k | λ) — the probability of seeing observations up to t AND being in state k at t.
Why StandardScaler is mandatory
log_return, realised_vol, vol_of_vol differ in scale by ~10×. Without scaling, Baum-Welch EM collapses all observations into one state. That state's covariance is well-estimated; the minority states have near-zero observations, their covariance matrices become rank-deficient, and the Cholesky decomposition inside the Gaussian emission fails.
WST features — Cauchy wavelet scattering
Why the modulus is essential
A complex wavelet ψ_j is a bandpass filter. The raw convolution x★ψ_j oscillates at the centre frequency with zero mean — averaging gives nothing. The modulus |x★ψ_j| strips the oscillation and gives the instantaneous amplitude envelope — slowly varying, non-negative, convolvable at the next scale.
Without modulus: S₂ = x★ψ_j₁★ψ_j₂ is just another bandpass filter (composition of linear operators = linear operator). With modulus: S₂ captures how energy at scale j₁ modulates energy at scale j₂ — a genuinely non-linear feature.
Cauchy vs Morlet
Financial returns are fat-tailed. A Morlet wavelet damps a 10σ return the same as a 3σ return at the same frequency. A Cauchy wavelet retains sensitivity to the 10σ event — exactly the information that matters for systematic risk.
The 37-dimensional coefficient vector
Alpha engine — IC, ICIR, and signal combination
Why Spearman, not Pearson
A single earnings shock (+30% on one stock, ±2% everywhere else) produces a near-zero Pearson IC even if the factor correctly ranked all other stocks. Spearman rank correlation is Pearson correlation of the rank vectors — a single outlier gets rank N, not a 15σ influence on the correlation.
ICIR as signal Sharpe ratio
IC-weighted combination
Black-Litterman — Bayesian posterior portfolios
Step 1 — Reverse optimisation
CAPM says market-cap weights w_mkt are mean-variance optimal. Running the formula backwards gives the implied equilibrium expected returns:
Step 2 — Views from IC-weighted signals
Step 3 — Posterior (conjugate Gaussian update)
Hierarchical Risk Parity — clustering-based allocation
Step 1 — Distance matrix
Step 2 — Ward linkage and quasi-diagonalisation
Ward linkage minimises within-cluster variance at each merge. The dendrogram leaf order (quasi-diagonalisation) reorders the covariance matrix so similar assets are adjacent.
Step 3 — Recursive bisection
CVaR — the Rockafellar-Uryasev linear programme
The non-smooth problem
The max() makes this non-smooth. Standard gradient methods fail. Rockafellar-Uryasev (2000) showed it reformulates as a linear programme:
Risk model — Ledoit-Wolf shrinkage and BARRA decomposition
Why sample covariance fails
An N×N covariance matrix has N(N+1)/2 unique parameters. When T approaches N, the condition number (max/min eigenvalue) explodes. Inverting such a matrix amplifies noise by the condition number — 10³ or more for a 100-asset portfolio with 2 years of daily data.
Ledoit-Wolf shrinkage
BARRA decomposition
Backtest — PSR, DSR, and walk-forward
Sharpe ratio variance under non-normality
Probabilistic Sharpe Ratio
Deflated Sharpe Ratio
When N strategies are tested and the best is selected, E[max SR] under the null rises with N. DSR adjusts the benchmark SR* to account for selection bias: