Skip to content
All strategies

Strategies

XLE/USO energy equity-vs-crude OU log-spread reversion (USEQ daily, dollar-neutral, point-in-time OU fit)

Outcome: Abandoned

XleUsoOuLogSpreadReversion

Outcome Summary

XleUsoOuLogSpreadReversion tried to port a stochastic-spread commodity pairs model to XLE versus USO on 20 years of daily US equity data, trading OU z-score extremes of a beta-hedged log spread. The hypothesis cited a modest pre-study edge (average trade about +0.5%, Sharpe about 0.3) and flagged significant selection bias. Over 3 iterations the code never got through verification: Layer 1.5 rejected each attempt because the optimization_plan fixes leverage at 1.0. The run was abandoned before any backtest, optimization or analyst verdict, so the edge remains untested in the pipeline.

Hypothesis

Translation of Mahajan et al. (2019), 'Stochastic Spread Pairs Trading in the Indian Commodity Market' (arXiv:1907.08397, abstract: cointegrated commodity pairs, single-factor stochastic (OU) model on the LOG spread, trading-rule parameters fitted on training data). MCX spot commodities are not on our venues, so the closest backtestable commodity pair with long history is used: XLE.USEQ (integrated oil and gas equities) against USO.USEQ (front-month WTI crude ETF), both on 1-DAY bars since 2006 (~20 years, ~5,100……Show moreShow less

Translation of Mahajan et al. (2019), 'Stochastic Spread Pairs Trading in the Indian Commodity Market' (arXiv:1907.08397, abstract: cointegrated commodity pairs, single-factor stochastic (OU) model on the LOG spread, trading-rule parameters fitted on training data). MCX spot commodities are not on our venues, so the closest backtestable commodity pair with long history is used: XLE.USEQ (integrated oil and gas equities) against USO.USEQ (front-month WTI crude ETF), both on 1-DAY bars since 2006 (~20 years, ~5,100 aligned sessions). Economic tie: XLE constituents' cash flows are driven by crude, so the log spread log(XLE) - beta*log(USO) is mean-reverting in the medium run. Short-run gaps open from equity-specific flows, crude futures roll and contango shocks, and these close as the equity market re-prices oil exposure. Point-in-time implementation of the paper's model: (1) hedge ratio beta = rolling 252-session OLS slope of daily log returns of XLE on USO, lagged one bar. (2) spread s_t = log(XLE_t) - beta_{t-1}*log(USO_t). (3) the OU parameters (theta = long-run mean, sigma_eq = stationary std, kappa from the AR(1) coefficient) are fitted by AR(1) regression of s on its lag over the last `z_window` sessions, using completed bars only. Closed-form AR(1) is used instead of the paper's differential-evolution fit; it is equivalent for a single-factor OU and adds no compute. (4) z = (s_t - theta)/sigma_eq. Long-short, dollar-neutral, 2 legs, flat most of the time. Crypto alternatives were checked first and rejected on data. Binance/OKX gold and silver perps (XAU/XAG/XPT/COPPER) have only ~7-10 months of 1h history. Gold-token spreads (XAU vs PAXG vs XAUT on Binance, XAG Binance vs OKX) have hourly spread std of 3-6 bps and lost money after fees in every variant (avg trade -0.01% to -0.06%). The pre-study on USEQ daily (rolling beta, 60-session z, entry |z|>1.5-2.0, exit at the mean, 20-session cap, 6 bps cost) gives XLE/USO about 100-158 trades, avg trade +0.51% to +0.58%, win rate ~0.6, Sharpe ~0.30-0.33, positive in 60-70% of years. HONEST CAVEAT: XLE/USO was the best of 21 commodity-ETF pairs scanned (GLD, SLV, GDX, XLE, XOP, USO, UNG). GLD/SLV and GLD/GDX, the 'textbook' commodity pairs, had no edge, so selection bias is material and the expected OOS Sharpe is modest. What the full paper would have to confirm: the exact trading-rule form (entry/exit bands in OU-sigma units, any stop-loss band), whether the bands were fixed or optimized per pair, and whether the reported Sharpe > 1.4 is net of costs. The abstract says the parameters were 'optimized by backtesting on the training period', so the 1.4 is likely an in-sample-tuned figure. source_ref: arXiv:1907.08397 abstract (method paragraph: Johansen cointegration selection + single-factor stochastic spread on log spread + trading rule tuned on the 80% training split).

Under-represented buckets: USEQ venue (4.7%), long_short (15.3% vs a <=55% long-only target), pairs. The crypto translation was tested and is fee-dead, and the crypto metal perps lack history. Equity commodity ETFs carry 20 years of daily data. Only 3 tunables, all economically meaningful. The pre-registered numbers are deliberately modest because of the selection-bias caveat.

verification_loop: Verification failed (Layer 1.5 — hypothesis/config consistency) [class=hypothesis_mismatch]: - optimization_plan fixes leverage at 1.0

Implementation

Dollar-neutral XLE/USO pairs trade on USEQ daily bars. Hedge ratio = rolling 252-session OLS beta of XLE on USO daily log returns, lagged one bar. Spread s = log(XLE) - beta*log(USO). OU parameters come from a closed-form AR(1) fit over the last z_window completed sessions, with every spread in the window rebuilt using the same lagged beta. z = (s - theta)/sigma_eq. Enter long spread at z <= -entry_z and short spread at z >= entry_z, only when the half-life is <= max_half_life and |z| < stop_z. Exit at the mean (exit_z), at the stop band (stop_z), or after max_hold sessions. Each leg is booked as its own position at gross_notional_frac/2 of equity.

Iteration 3 fixes the critical warm-up defect. min_bars_required() now returns 2, so calculate_signal runs from bar 1 and trading is gated only on the internal _beta_ok/_fit_ok flags. Log prices are buffered from bar 1 and the beta needs 252 return pairs, so the first valid z arrives at bar 254 for every z_window in [40,120]. That is inside the registered 380-bar warm-up, so walk-forward windows reset with warm-up no longer lose 15-24 months. The QA beta-mixing warning is also addressed: the OU window now stores……Show moreShow less

Iteration 3 fixes the critical warm-up defect. min_bars_required() now returns 2, so calculate_signal runs from bar 1 and trading is gated only on the internal _beta_ok/_fit_ok flags. Log prices are buffered from bar 1 and the beta needs 252 return pairs, so the first valid z arrives at bar 254 for every z_window in [40,120]. That is inside the registered 380-bar warm-up, so walk-forward windows reset with warm-up no longer lose 15-24 months. The QA beta-mixing warning is also addressed: the OU window now stores (log XLE, log USO) and rebuilds every spread with the current lagged beta, at O(z_window) per bar, so hedge-ratio drift cannot create spurious z excursions. Legs stay equal-dollar because the hypothesis specifies dollar-neutral; this is intended even though beta is usually below 1 (a beta-neutral sizing would be the alternative). The orphaned-hedge cleanup is unchanged; on daily bars the exposure is at most one session. The edge concern (returns concentrated in the stressed-vol tercile, the 2020 USO restructuring, selection bias from 21 scanned pairs) is a hypothesis-level risk for the analyst to judge on the full-history and walk-forward results; no code change addresses it.

Verification Results

Verification failed (Layer 1.5 — hypothesis/config consistency) [class=hypothesis_mismatch]: - optimization_plan fixes leverage at 1.0

Iteration History

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The sandbox ran on 1,653 sessions and produced 44 leg positions (22 pair trades). It returned +3.2%, Sharpe 0.13, PF 1.17 and avg trade +0.29%. Nearly all of the return came from the stressed-volatility tercile (+4.28%); the calm and normal terciles were both slightly negative. The researcher says XLE/USO was the best of 21 pairs scanned, so selection bias is material. USO's 2020 restructuring (switch away from front-month-only……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The sandbox ran on 1,653 sessions and produced 44 leg positions (22 pair trades). It returned +3.2%, Sharpe 0.13, PF 1.17 and avg trade +0.29%. Nearly all of the return came from the stressed-volatility tercile (+4.28%); the calm and normal terciles were both slightly negative. The researcher says XLE/USO was the best of 21 pairs scanned, so selection bias is material. USO's 2020 restructuring (switch away from front-month-only futures, 1:8 reverse split) also changed the crude exposure partway through the sample. Check whether the edge survives outside 2008, 2014-16, 2020 and 2022. - [critical] The real warm-up is about twice the declared one. The override min_bars_required() = beta_window + z_window + 3 (375 at the defaults) makes the base _maybe_process_primary return BEFORE calling calculate_signal for the first 375 bars. During those bars _rets and _spreads get nothing. The comment says the buffers are 'rebuilt from warmup bars after a restart', but no code does this. The rolling state only starts filling at bar ~375. It then needs 252 return pairs before _beta_ok becomes true, plus z_window+1 completed spreads before _fit_ou succeeds. So the first valid z comes at about bar 375 + 253 + 61 ≈ 689 (≈ 870 when z_window = 120). The registered contract declares warmup_bars = 380 with boundary_policy reset_with_warmup, and src/backtesting/warmup.py prepends exactly 380 prior bars to every walk-forward window. The runner's guard only compares min_bars_required() (375) with 380, so it passes, but in substance the contract is violated. Every reset window would then lose its first ~310-490 tradable sessions (15-24 months of daily bars). The adaptive 90-day validation windows would be entirely dead and produce zero trades. Optimization fails, or measures nothing, and the full-history run silently loses ~2.7 years at the start. (line 45) — fix: Pick one: (a) have min_bars_required() return a small number (e.g. 2) and gate trading on the internal _beta_ok/_fit_ok flags only, so the 380 warm-up bars fill the deques; the worst case is 253 + 121 = 374 bars at z_window = 120, inside 380. Or (b) keep the override but, on the first processed bar, rebuild _rets and _spreads from self._bars paired by ts_init with self._extra_bars[USO]. Either way, show that the first non-zero z appears at or before bar 380 for z_window = 120. - [warning] The legs are equal-dollar, as the hypothesis states ('dollar-neutral'), but the signal is the beta-weighted spread log(XLE) - beta*log(USO). The daily-return beta of XLE on USO is usually ~0.3-0.6. The position's P&L therefore tracks r_XLE - r_USO, not r_XLE - beta*r_USO: about half the USO leg is an unhedged directional crude bet. This follows the hypothesis as written, so it does not block, but the traded spread is not the one being z-scored. (line 211) — fix: Consider sizing the USO leg at beta x the XLE notional (beta-neutral), or state explicitly that dollar neutrality is intended despite beta != 1. - [warning] Each stored spread uses the beta that applied when it was computed, so the AR(1)/OU window mixes spreads built with different hedge ratios. log(USO) has a level of about 2-5, so a beta drift of 0.05 over the window moves s by 0.1-0.25. That is comparable to sigma_eq and can create artificial z excursions. It is point-in-time safe (no look-ahead), but noisier than the paper's fixed-beta spread. (line 148) — fix: Optionally recompute the z_window spreads with the current lagged beta, s_i = lx_i - beta_{t-1} * lu_i, keeping the log prices in a deque; this costs O(z_window) per bar. - [warning] Orphaned USO hedge legs are only cleaned up in should_enter (the next bar after the primary goes flat). If the XLE entry is refused after position_size has already submitted the USO order (for example by a base-template gate), USO stays open unhedged for one session. That is acceptable on daily bars, but the hedge is submitted before the primary order is known to be accepted. (line 168) — fix: Submit the hedge leg after the primary order is accepted, or check right away that it was accepted.

Outcome Summary

Before coding, make the optimization plan's leverage setting match the hypothesis and venue declaration (a Reg-T 2x USEQ long-short pair), because a config-consistency mismatch that is not fixed across iterations ends the run before any backtest evidence exists.

After 3 iterations the strategy was abandoned in a verification loop. Each attempt failed Layer 1.5 (hypothesis/config consistency, class hypothesis_mismatch) because the optimization_plan fixes leverage at 1.0. It never reached backtesting, analyst review or risk review.

A dollar-neutral, daily USEQ pairs trade on the log spread log(XLE) - beta*log(USO), with a lagged 252-session rolling hedge ratio and a point-in-time AR(1)/OU fit, entering when the OU z-score is stretched and exiting at the mean (a translation of Mahajan et al. 2019, arXiv:1907.08397).

No pipeline backtest or optimization was produced. The only numbers come from the hypothesis's own pre-study: about 100-158 trades, average trade +0.51% to +0.58%, win rate about 0.6, Sharpe about 0.30-0.33, and positive in 60-70% of years. The hypothesis itself warned that XLE/USO was the best of 21 commodity-ETF pairs scanned.

Analysis

Stochastic Spread Pairs Trading in the Indian Commodity Market

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