Skip to content
All strategies

Strategies

QuantConnect library: Intraday Arbitrage Between Index ETFs

Outcome: Abandoned

SpyIvvIntradayQuotientArbitrage

Outcome Summary

The strategy attempted intraday SPY/IVV quotient arbitrage with rolling-mean reversion. It produced no performance metrics because Layer 3 verification timed out after three iterations. The pipeline abandoned it before backtest review, analysis, risk review, or promotion.

Hypothesis

Implements the Marshall-Nguyen-Visaltanachoti / Kakushadze-Serur quotient threshold on two ETFs tracking the S&P 500. Per L155, after two timeouts with no metrics I checked all three things that could change: (a) config — the 1-minute bar type is required, because 1-HOUR bars (7 per session) cannot express an intraday dislocation of a few minutes; (b) catalog — SPY.USEQ-1-MINUTE is 193 uncompacted hourly parquet shards vs 10 compacted IVV shards, and the host load average is ~50-60; iteration-2 profiling put 100% of the time in ParquetDataCatalog.query file discovery; (c) per-bar cost — already O(1): incremental deque sum, integer ET clock, no history scans. Only a redundant cache lookup was removed. I doubt the hypothesis: SPY/IVV quotient dislocations of about 2 bps are smaller than the round-trip spread plus impact on four fills (~2-4 bps).

verification_loop: sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (30 days of SPY.USEQ-1-MINUTE-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

Intraday SPY/IVV quotient arbitrage on 1-minute USEQ bars. Signal = deviation (bps) of SPY/IVV from its same-session 30-bar rolling mean. Above +entry_bps: short SPY / long IVV; below -entry_bps: the reverse, dollar-matched whole shares at 45% of equity per leg (90% gross, within 2x Reg-T). Exits on reversion inside exit_bps, after 30 minutes, or at 15:55 ET.

Verification Results

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (30 days of SPY.USEQ-1-MINUTE-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

Keep per-bar data access bounded so verification runtime does not scale with history.

It was abandoned after exceeding the 300-second sandbox limit on an approximately 20,000-bar window. Later pipeline stages were not reached.

It attempted intraday SPY/IVV quotient arbitrage using same-session ratio deviations and matched opposite legs.

No backtest or optimization metrics were produced. Verification reached three iterations but the Layer 3 sandbox backtest did not complete.

Analysis

Intraday Arbitrage Between Index ETFs

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