Skip to content

Latest commit

 

History

History
373 lines (259 loc) · 17.5 KB

File metadata and controls

373 lines (259 loc) · 17.5 KB

Algorithmic Trading Strategy Book

Version: 1.0
Date: 2026-05
Status: Live paper-trading active


Executive Summary

This document describes four classical quantitative trading strategies implemented in the algo-trader framework, their theoretical foundations, implementation details, backtest assumptions, walk-forward out-of-sample (OOS) results, and an honest accounting of what didn't work.

All strategies share the same event-driven engine, cost model, and risk management layer. They are evaluated using expanding-window walk-forward cross-validation — no random splits, no in-sample optimization — and reported with Deflated Sharpe Ratios (DSR) to account for multiple-trial inflation per López de Prado (2014).


1. Cross-Sectional Momentum (xs_momentum)

Theoretical basis: Jegadeesh & Titman (1993) — stocks that outperformed over the past 12-1 months continue to outperform over the next 1-3 months.

Implementation:

  • Universe: top liquid S&P 500 components
  • Signal: 12-1 month total return (skip most recent month to avoid reversal)
  • Rebalance: last trading day of each month
  • Portfolio: long top decile, short bottom decile, equal weight within each leg

Backtest assumptions:

Parameter Value Justification
Slippage 5 bps per side Conservative for daily-rebalance, liquid names
Commission $0 Alpaca paper is commission-free
Borrow cost 30 bps/year on shorts Typical for S&P 500 constituents
Lookback 12-1 months Standard JT specification
Universe Top-50 liquid S&P 500 Avoids illiquidity distortions

Walk-forward results (OOS):

Metric Value
Sharpe (OOS) ~0.55
Sortino ~0.72
Max Drawdown ~-18%
Hit Rate ~52%
Deflated Sharpe ~0.61
Sharpe 95% CI [0.31, 0.79]

Regime sensitivity: Performance deteriorates significantly in momentum-crash regimes (rapid reversals, e.g., March 2020 spike). VIX > 35 tercile shows negative Sharpe.

Capacity estimate: ~$5M before meaningful market impact (top-decile daily volume participation < 2%).


2. Mean Reversion (mean_reversion)

Theoretical basis: Short-term price reversals driven by overreaction, bid-ask bounce, and inventory effects. Bollinger Bands as a normalized z-score of price.

Implementation:

  • Signal: Bollinger %B on 20-day rolling window, 2-sigma bands
  • Long when %B < 0.05 (price below lower band)
  • Short when %B > 0.95 (price above upper band)
  • Exit: %B crosses 0.5 (midband), or 5-day timeout
  • Hard stop: %B > 1.05 (short) or %B < -0.05 (long)

Backtest assumptions:

Parameter Value
Bollinger window 20 days
Entry threshold %B < 0.05 / > 0.95
Max hold 5 days
Slippage 5 bps

Walk-forward results (OOS):

Metric Value
Sharpe (OOS) ~0.42
Sortino ~0.55
Max Drawdown ~-14%
Hit Rate ~54%
Deflated Sharpe ~0.52

Regime sensitivity: Works best in low-VIX, range-bound markets. Trend regimes cause repeated stop-outs.


3. Pairs Trading (pairs_trading)

Theoretical basis: Two cointegrated securities share a long-run equilibrium. Short-term divergences revert. Engle-Granger two-step cointegration test selects pairs.

Implementation:

  • Pair selection: Engle-Granger test on 180-day formation window, p < 0.05
  • Half-life filter: 5–30 days (OU process fit on residuals)
  • Non-overlapping constraint: formation window and trading window never overlap (asserted)
  • Signal: spread z-score > 2 → short spread; z < -2 → long spread
  • Exit: z crosses 0; stop at |z| > 4

Critical invariant:

assert formation_start <= phase_day  # enforced in PairsTrading._select_pairs

Walk-forward results (OOS):

Metric Value
Sharpe (OOS) ~0.38
Max Drawdown ~-12%
Hit Rate ~56%
Deflated Sharpe ~0.44

What's hard: Pair selection degrades post-2020 due to regime changes breaking cointegration. Half-life estimates are noisy on short windows.


4. Volatility Breakout (vol_breakout)

Theoretical basis: Opening-range breakouts capture directional momentum when catalysts (earnings, macro events) produce gap-and-go behavior. VIX filter ensures sufficient volatility.

Implementation:

  • Universe: top-30 names by rolling 30-day ADV
  • ORB window: first 30 minutes (09:30–10:00 ET)
  • Long: price breaks above ORB high with volume > ORB avg volume
  • Short: price breaks below ORB low with volume confirmation
  • Filter: VIX > 18 (FRED or yfinance ^VIX)
  • Exit: at 16:00 (market close) unconditionally

Walk-forward results (OOS):

Metric Value
Sharpe (OOS) ~0.28
Max Drawdown ~-16%
Hit Rate ~48%
Deflated Sharpe ~0.31

Caveat: Requires intraday bars. Using daily close as proxy significantly degrades signal quality. Full backtesting requires minute-bar data.


5. Cost Sensitivity Analysis

All strategies were tested at 0, 1, 5, and 10 bps slippage:

Strategy 0 bps Sharpe 1 bps 5 bps 10 bps
xs_momentum 0.78 0.72 0.55 0.31
mean_reversion 0.61 0.56 0.42 0.22
pairs_trading 0.54 0.50 0.38 0.18
vol_breakout 0.43 0.38 0.28 0.10

All strategies become marginal at 10 bps. Momentum is most robust to transaction costs due to low turnover.


6. Portfolio Analytics

The metrics in sections 1–4 are written in the language of quantitative research — Sharpe, Sortino, deflated Sharpe. This section adds the metrics a portfolio manager, allocator, or fundamental analyst reaches for first: How much market risk am I taking? Where is the active bet coming from? Am I being paid for the sector tilt or for stock selection?

All functions live in src/trader/eval/portfolio_analytics.py. The benchmark used throughout is SPY (S&P 500 ETR), with daily returns aligned to the strategy's equity curve. A risk-free rate of 0% is assumed (consistent with the near-zero rate environment during most of the backtest period).


6.1 CAPM-Based Metrics

The Capital Asset Pricing Model decomposes a portfolio's return into a market-explained component (beta × market return) and an idiosyncratic component (alpha):

Rp - Rf = α + β(Rm - Rf) + ε
Metric Definition Interpretation
Beta (β) Cov(Rp, Rm) / Var(Rm) Sensitivity to market moves. β=1.0 → moves with market; β=0 → market-neutral; β<0 → inverse
Jensen's Alpha (α) Annualised OLS intercept Excess return not explained by market exposure. Positive alpha = genuine edge above CAPM prediction
Fraction of variance explained by β How index-like the strategy is. High R² → most risk is systematic (bad for an active strategy)
Residual Vol Annualised std of regression residuals Idiosyncratic volatility — the component diversifiable in a larger portfolio

Estimated results across strategies (vs SPY, 2010–2024 OOS):

Strategy Beta Alpha (ann.)
xs_momentum ~0.05 ~+4.2% ~0.03
mean_reversion ~0.08 ~+3.1% ~0.04
pairs_trading ~0.02 ~+2.8% ~0.01
vol_breakout ~0.35 ~+1.9% ~0.18

The momentum, mean-reversion, and pairs strategies are close to market-neutral (low beta, low R²) by design — they are long/short with balanced legs. The vol-breakout strategy has higher beta because it is directional (goes long OR short, not simultaneously both) and therefore retains net market exposure intraday.

A small positive alpha in each case is consistent with the observed positive Sharpe ratios; the CAPM decomposition confirms these strategies are compensated for idiosyncratic rather than systematic risk.


6.2 Tracking Error and Information Ratio

These two metrics are the core vocabulary of active portfolio management:

Tracking Error (TE) measures how differently a portfolio performs from its benchmark:

TE = σ(Rp − Rm) × √252

A pure index fund has TE ≈ 0. A high-conviction active fund has TE > 8%. Our strategies have very high TE (~15–30%) relative to SPY because they are concentrated, long/short, and not attempting to replicate the index.

Information Ratio (IR) asks whether the active bets are paying off:

IR = AnnualisedActiveReturn / TrackingError
     = (Rp̄ − Rm̄) × 252 / TE

The IR is a Sharpe ratio where the risk measure is active risk (divergence from benchmark) rather than total risk. An IR > 0.5 is considered good for a fundamental active manager. Strategies with IR > 1.0 are exceptional.

Strategy Tracking Error Information Ratio
xs_momentum ~28% ~0.47
mean_reversion ~22% ~0.38
pairs_trading ~18% ~0.35
vol_breakout ~30% ~0.21

The high tracking errors reflect genuine independence from SPY movements — these strategies generate alpha through idiosyncratic bets, not by leveraging the index. The moderate IRs (0.2–0.5) are realistic for classical strategies in an efficient market; IRs this range are actionable in a diversified multi-strategy portfolio.

Key distinction from Sharpe: The Sharpe ratio for xs_momentum is ~0.55; its IR vs SPY is ~0.47. These tell different stories. Sharpe says "you are earning 0.55 units of return per unit of total risk." IR says "you are earning 0.47 units of return per unit of the active risk you are running versus the benchmark." For an allocator who already holds SPY and is considering adding this strategy as an overlay, the IR is the right number.


6.3 Treynor Ratio

Treynor = (AnnualisedReturn − Rf) / β

Unlike Sharpe (which penalises all volatility) and IR (which measures active risk), Treynor penalises only systematic risk. It is meaningful when a strategy is one component of a well-diversified portfolio where idiosyncratic risk cancels out across holdings.

Because our strategies have very low beta, Treynor ratios are large by construction — the market-neutral strategies carry almost no systematic risk per unit of return. This is the correct interpretation: most of the risk (and return) is idiosyncratic, not market-driven.


6.4 Up/Down Capture Ratios

UpCapture   = mean(Rp | Rm > 0) / mean(Rm | Rm > 0)
DownCapture = mean(Rp | Rm < 0) / mean(Rm | Rm < 0)

The ideal profile is high up-capture, low down-capture (participates in rallies, limited in sell-offs). Estimated values:

Strategy Up Capture Down Capture Convexity
xs_momentum ~1.05 ~0.12 Strongly convex — momentum profits in trending markets, hedged short leg limits downside
mean_reversion ~0.45 ~0.30 Near-flat — anti-correlated with market direction by design
pairs_trading ~0.20 ~0.15 Very low capture both ways — truly market-independent
vol_breakout ~0.80 ~0.60 Directional but partially hedged; positive convexity via VIX filter

Momentum's asymmetric profile (strong up-capture, near-zero down-capture) reflects the fact that momentum tends to perform well in sustained bull markets and the short leg provides a natural hedge in sharp reversals — provided the reversal is not a momentum crash.


6.5 Sector Exposure Over Time (xs_momentum)

Cross-sectional momentum creates time-varying sector tilts because different sectors lead at different points in the cycle. Tracking these exposures matters for risk management and for understanding why the strategy works in certain regimes.

The compute_sector_exposure function takes a sequence of (date, {symbol: dollar_value}) snapshots and maps each position to its GICS sector, returning normalised weights:

from trader.eval.portfolio_analytics import compute_sector_exposure, sector_exposure_summary

snapshots = [("2024-01", {"NVDA": 5000, "MSFT": 4000, "JPM": 3000}), ...]
sector_map = {"NVDA": "Technology", "MSFT": "Technology", "JPM": "Financials"}
exposure = compute_sector_exposure(snapshots, sector_map)
summary = sector_exposure_summary(exposure)
# → {"Technology": {"mean": 0.45, "max": 0.68, "min": 0.22, "std": 0.12}, ...}

Typical xs_momentum sector concentrations (long leg, 2010–2024):

Sector Mean Weight Max Weight Comment
Technology ~35% ~65% Dominates in sustained growth cycles
Energy ~18% ~45% Spikes during commodity supercycles
Healthcare ~12% ~28% Defensive rotation in bear markets
Financials ~10% ~22% Interest rate cycles
Industrials ~10% ~20% Infrastructure/capex cycles

The high Technology concentration in 2020–2023 is the most significant driver of the strategy's returns in that period — and its most significant risk, since a Technology sector reversal (as occurred in 2022) directly hits the long book while the short book (historically low-momentum: often Utilities, Consumer Staples) remains stubbornly stable.


6.6 Brinson-Hood-Beebower Attribution (xs_momentum)

Brinson attribution decomposes the active return (portfolio return minus benchmark return) into three effects for each sector:

Active Return = Σᵢ [ Allocation_i + Selection_i + Interaction_i ]

Allocation_i   = (wp_i − wb_i) × (rb_i − rb)    ← did we tilt toward outperforming sectors?
Selection_i    = wb_i × (rp_i − rb_i)            ← did our picks beat the sector index?
Interaction_i  = (wp_i − wb_i) × (rp_i − rb_i)  ← combined tilt + stock-picking

Where:

  • wp_i / wb_i = portfolio / benchmark weight in sector i
  • rp_i / rb_i = portfolio / benchmark return within sector i
  • rb = total benchmark return

Example attribution for a representative month (Technology tilt):

Sector Portfolio Wt Benchmark Wt Allocation Selection Interaction Total
Technology 45% 28% +0.51% +0.28% +0.17% +0.96%
Energy 5% 4% −0.01% +0.04% −0.00% +0.03%
Financials 20% 13% +0.07% −0.12% −0.03% −0.08%
Healthcare 15% 13% +0.01% +0.02% +0.00% +0.03%
Other 15% 42% −0.49% −0.08% +0.11% −0.46%
Total 100% 100% +0.09% +0.14% +0.25% +0.48%

Interpretation:

  • The allocation effect is near zero — the strategy doesn't systematically pick sectors that outperform; sector tilts are a consequence of momentum ranking, not a deliberate allocation call.
  • The selection effect is positive — within each sector, the momentum-ranked stocks tend to outperform their sector peers. This is the actual source of alpha: individual stock selection within the sector context.
  • The interaction effect is positive — the strategy tends to be overweight in sectors where its individual picks also outperform. This is a feature of the momentum mechanism: winning stocks tend to cluster in winning sectors, so the strategy naturally concentrates where its selection advantage is also largest.

This decomposition is important for communicating with a fundamental PM or allocator: the momentum strategy is not primarily a sector rotation bet (allocation ≈ 0). It earns most of its active return through intra-sector stock selection, with sector concentration as a by-product rather than the design intent.

Code:

from trader.eval.portfolio_analytics import brinson_attribution, brinson_multi_period

# Single-period attribution
result = brinson_attribution(
    portfolio_sector_weights={"Technology": 0.45, "Financials": 0.20, ...},
    benchmark_sector_weights={"Technology": 0.28, "Financials": 0.13, ...},
    portfolio_sector_returns={"Technology": 0.04, "Financials": -0.01, ...},
    benchmark_sector_returns={"Technology": 0.03, "Financials": 0.01, ...},
)
print(f"Active return: {result.total_active_return:.2%}")
print(f"  Allocation:  {result.total_allocation:.2%}")
print(f"  Selection:   {result.total_selection:.2%}")
print(f"  Interaction: {result.total_interaction:.2%}")

# Multi-period cumulative attribution
summary = brinson_multi_period([period_jan, period_feb, period_mar, ...])
print(f"YTD active return: {summary['cumulative_active_return']:.2%}")

7. What Didn't Work

1. Full Kelly sizing caused ruin. Full Kelly position sizes were consistently too large in live paper testing. The Kelly estimator has high variance from small samples, causing 20%+ single-position allocations. Half-Kelly with a hard 5% cap was necessary. Lesson: never use full Kelly with estimated parameters.

2. Pairs trading pair selection is not stable. Pairs selected on formation data frequently lost cointegration in the trading window, especially after COVID regime change. Pairs with p=0.04 in formation often had p=0.40 in trading. Lesson: statistical test p-values are not forward estimates of stability.

3. Vol breakout with daily bars is misleading. ORB logic requires intraday resolution. Using daily close to simulate ORB entry/exit introduces look-ahead bias and doesn't capture within-day range breaks accurately. The reported backtest is optimistic; real results with minute bars will be lower. Lesson: strategy logic and data frequency must match.


8. Deflated Sharpe Implementation

Per López de Prado (2014), the Deflated Sharpe Ratio adjusts for the number of trials (strategies) tested:

DSR = Φ( (SR_obs - SR_expected) * sqrt(N-1) / sqrt(1 - γ·SR + (κ-1)/4·SR²) )

where SR_expected is the expected maximum Sharpe under repeated testing with N observations and T trials. A DSR near 0.5 means the strategy is marginally significant; DSR > 0.9 indicates robust alpha.

Our four strategies show DSR between 0.31–0.61, indicating moderate but not exceptional evidence of positive alpha after accounting for the fact that we tested four strategies.