Skip to content
All strategies

Strategies

Avellaneda-Lee Single-Pair Sector-ETF Residual s-Score Reversion: XOM vs XLE, Beta-Hedged, Canonical Fixed Thresholds, Flat ~55% of the Time (USEQ DAILY, zero-commission, long-short)

Outcome: Abandoned

XomXleAvellanedaLeeSScorePair

Outcome Summary

The strategy attempted daily XOM/XLE statistical arbitrage by trading ETF-factor residual s-score extremes while maintaining beta hedging. It did not reach a recorded backtest result because the Layer 3 sandbox smoke test exceeded the 300-second limit on approximately 20,000 bars. After three iterations, the verification timeout ended the run and no later stages were reached.

Hypothesis

Distilled from the quant.stackexchange thread 'statistical arbitrage vs factor trading' (Michael, 2017; accepted answer by mperlow) which discusses Avellaneda & Lee (2010) 'Statistical Arbitrage in the US Equities Market'. The one concrete, testable mechanism in the source is the paper's ETF-factor version: regress a stock's daily returns on its sector ETF's returns over a trailing 60-session window, integrate the residual into an auxiliary process X_t, fit an AR(1)/Ornstein-Uhlenbeck model to X, and trade the……Show moreShow less

Distilled from the quant.stackexchange thread 'statistical arbitrage vs factor trading' (Michael, 2017; accepted answer by mperlow) which discusses Avellaneda & Lee (2010) 'Statistical Arbitrage in the US Equities Market'. The one concrete, testable mechanism in the source is the paper's ETF-factor version: regress a stock's daily returns on its sector ETF's returns over a trailing 60-session window, integrate the residual into an auxiliary process X_t, fit an AR(1)/Ornstein-Uhlenbeck model to X, and trade the dimensionless s-score s = (X_t - m)/sigma_eq, holding 1$ of stock against -beta $ of the ETF so the position is factor-neutral (exactly the hedging the accepted answer quotes from p.16). Per L173/L137 we do NOT run the 20-500 name PCA book (the Bybit PCA-residual top-20 timed out; neutral multi-name books die on leg costs). Instead it is ONE stock-vs-its-own-factor pair on the zero-commission USEQ venue, where the second leg costs ~0.02-0.05% rather than a crypto round trip. Instrument choice is a pre-registered rule, not a hand-picked list: the stock is the largest-weight constituent of the sector ETF (XOM has been XLE's top holding for essentially the whole ETF history since 1998). Signal is measured on XOM and XLE and traded on XOM and XLE (no signal-on-A/trade-B). DAILY bars give ~27 years of history. All thresholds are the paper's published values, not tuned: entry |s| > 1.25, exit long when s > -0.50, exit short when s < +0.75, with the paper's mean-reversion-speed filter (only trade when the fitted OU half-life < ~30 sessions, i.e. kappa > 252/30). This is a revise-free, first-principles restatement, not a code port.

Iteration 3 performance fix: removed the per-bar primary and hedge position/order reconciliation lookups from calculate_signal. Hedge management remains tied to entry and signal exits, while the rolling fit stays bounded at 60 observations per bar. This preserves the previously passing static and synthetic layers and removes the expensive cache work that caused the Layer-3 timeout.

verification_loop: sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (2400 days of XOM.USEQ-1-DAY-LAST-EXTERNAL). This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).

Implementation

Trades the Avellaneda-Lee residual s-score between XOM and XLE using a rolling 60-session return regression, cumulative residual OU fit, beta-neutral long/short pair construction, and canonical entry, exit, and half-life thresholds.

Verification Results

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (2400 days of XOM.USEQ-1-DAY-LAST-EXTERNAL). This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).

Outcome Summary

Per-bar calculations should avoid rescanning full supplementary series or repeatedly rebuilding and sorting data; lookups should be precomputed or cached.

The strategy was abandoned after three iterations when verification failed with a sandbox timeout. Later pipeline stages were not reached.

It tried to trade mean-reverting, beta-hedged residuals between XOM and XLE using Avellaneda-Lee ETF-factor s-score thresholds on daily USEQ data.

No backtest metrics were recorded because verification stopped during the sandbox backtest. The smoke test exceeded the 300-second limit on approximately 20,000 bars.

Analysis

statistical arbitrage vs factor trading

Backtest and paper results are hypothetical. Trading involves risk of loss.