Skip to content
All strategies

Strategies

QuantRocket GDX–GLD daily Johansen/Bollinger pairs strategy: faithful USEQ translation

Outcome: Abandoned

GdxGldJohansenBollingerPairs

Outcome Summary

GdxGldJohansenBollingerPairs set out to port QuantRocket's GDX/GLD Johansen/Bollinger pairs strategy to the USEQ venue as faithfully as possible, re-implementing statsmodels' coint_johansen in numpy because statsmodels is not available in the sandbox. Across 3 iterations, the Layer 3 sandbox smoke test kept going over its 300-second limit on the ~20,000-bar GDX daily window. The run was abandoned in that verification loop. No backtest, optimization or analyst verdict was ever produced, so it is still unknown whether the strategy has an edge.

Hypothesis

Translate QuantRocket's executable GDX_GLD_Pair subclass, preserving its daily rolling Johansen weights, one-standard-deviation bands, independent persistent long/short signal states, daily target-weight recalculation and next-session-open execution. Use GDX.USEQ as primary and GLD.USEQ as the secondary trading leg, both with 1-DAY-LAST-EXTERNAL bars. Original defaults are LOOKBACK_WINDOW=20, BBAND_STD=1, DB_FIELDS=['Close','Open']; the subclass selects GDX and GLD from usstock-1d. Original USStockCommission charges $0.005/share; replace that commission with platform USEQ zero commissions and modeled spread/impact, explicitly reporting the difference. Do not claim source profitability or add filters, stop-losses, cointegration significance tests or parameter optimization.

The explicit request for a faithful source translation determines the instruments and mechanism. This pair adds long/short, two-instrument equity exposure to a corpus dominated by long-only Binance strategies. It does not fill the Hyperliquid or cross-venue quotas. Corpus concentration was empty, so instrument-specific research shares cannot be assessed. Unlike the prior dual-class-share proposal, this source trades miners versus bullion and recalculates Johansen share weights daily; its economics nevertheless remain unverified.

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 GDX.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

Faithful Stratmill translation of QuantRocket's GDX_GLD_Pair (PairsStrategy, Apache-2.0). Each session it fits a Johansen hedge vector (det_order=0, k_ar_diff=1) on the prior 20 closes of [GLD, GDX]. Today's spread gets 20-session 1-sigma Bollinger bands (ddof=1). Independent persistent long and short spread states enter beyond a band and exit at the mean. Target weights are signal x hedge x close, normalised to 1x gross. Both legs are rebalanced to the target in whole shares one session later.

Iteration 3 fixes the Layer-1.5 failure 'optimization_plan fixes leverage at 1.0'. Iteration 2's config had dropped `leverage` from `parameters`, and the plan validator requires every fixed key to be present there with the same type. `parameters.leverage = 1.0` (float) is restored and validate_plan passes locally against the research plan. The code now reads it as a documented mirror of config.leverage, which sizing uses. The iteration-2 behaviour-preserving tidy is kept: the closes deque has maxlen=lookback and……Show moreShow less

Iteration 3 fixes the Layer-1.5 failure 'optimization_plan fixes leverage at 1.0'. Iteration 2's config had dropped `leverage` from `parameters`, and the plan validator requires every fixed key to be present there with the same type. `parameters.leverage = 1.0` (float) is restored and validate_plan passes locally against the research plan. The code now reads it as a documented mirror of config.leverage, which sizing uses. The iteration-2 behaviour-preserving tidy is kept: the closes deque has maxlen=lookback and is fed straight to the Johansen fit. Signal, state machine, weights and execution are unchanged. Platform adaptations are unchanged: execution at the next-session close instead of the next open (no open-auction fill on daily bars), and USEQ zero commission plus modelled spread/impact instead of USStockCommission's $0.005/share. Iteration 1 ran locally and showed no edge: Sharpe -0.03, PF 0.95, avg trade -0.009%, max DD 29%. That is consistent with the hypothesis's 'do not claim source profitability'. The earlier Layer-3 timeout was diagnosed as catalog-scan and host-load cost, not strategy per-bar work.

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 GDX.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).

Iteration History

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

Outcome Summary

A rolling Johansen calculation that rebuilds matrices and inverts or decomposes them on every bar, in pure numpy, has to be kept cheap per bar (for example by caching per timestamp and avoiding repeated buffer rescans) or it won't fit within the sandbox time limit.

The run was abandoned in a verification loop after 3 iterations. The Layer 3 sandbox backtest went over its 300s limit on a ~20,000-bar window (2400 days of GDX.USEQ daily bars), so backtest review, optimization, analyst review and risk review never ran.

This was a direct port of QuantRocket's GDX/GLD pairs strategy to daily USEQ bars. Each day it computed a hedge ratio from a Johansen test on the last 20 closes, traded the spread between one-standard-deviation Bollinger bands, and rebalanced to target weights one session later, with zero commissions plus modelled spread and impact.

There are no performance numbers: no backtest report, no optimization report and no trade count were produced, because the strategy never got past verification.

Analysis

PairsStrategy

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