S-050 — gdx-streak-reversion-v1 (consecutive up/down streaks as a mean-reversion proxy)
ID: S050
Slug: gdx-streak-reversion-v1
Card title: GDX up/down streaks as a mean-reversion proxy
Type: Swing pillar — short-term mean-reversion exploration (sibling of S027 RSI-2 reversion).
Date registered: 2026-07-15
Status: REGISTERED / ONGOING exploration — NOT frozen, NOT counted, NOT live (CLAUDE.md §2A). A home for
the streak charts; the [CALIBRATE] gate awaits an owner freeze only if the idea survives.
Motivation: the owner's live GDX auto-execution round-trip succeeded (2026-07-14, the engine), and
wants a simple, tradeable mean-reversion idea on GDX to build on.
Idea (owner)
Tag each day with its running streak length: the 3rd consecutive up day = +3, the 2nd consecutive down day = −2, etc. Hypothesis: a long streak precedes reversal — the streak length is a simple proxy for short-term mean reversion (fade extended up-runs, buy extended down-runs). This first pass only sees the data (top-10 streak tables + a signed run-length histogram + a first look at the reversion tell). No signal, no verdict.
Definitions (this exploration)
- Up day = adj_close > prior adj_close (close-to-close, dividend/split-adjusted). Down = <. A flat day (0% move) breaks the run.
- A run = a maximal sequence of consecutive same-direction days; its length = number of same-direction moves. In the tables, start = the pivot (last close before the run) and end = the last close of the run, so begin/end price spans the whole move.
Data (Tiingo, from inception)
ohlcv_dailytickerGDX, adj_close, 2006-05-22 → current (refetched live via Tiingo; 5,067 days as of 2026-07-14). 2,692 runs (1,349 up / 1,343 down); mean run length 1.87 each way; max up 10, down 9.
Finding so far (2026-07-15 — ONGOING, no verdict)
- Top-10 longest streaks (up & down) computed with start/end date + start/end price + % move
(the engine; longest up = 10 days, +16.5%, Jun-2019; longest down = 9 days, −6.8%, Feb-2012). Figure:
streaks_hist.png(signed run-length histogram, up = green / down = red). - The mean-reversion tell holds in the raw data (next-day return conditional on the current streak):
- after +3/+4/+5 up-streak → next-day mean −0.15% / −0.21% / −0.39%, %up 46/47/48% (fades, stronger with length);
- after −3/−4/−5 down-streak → next-day mean −0.01% / +0.01% / +0.42%, %up 53/55/55% (bounces, clearest at −5).
- Honest caveats: effect is stronger on the up side (fade) than the down side (bounce is weak until −5); GDX carries a mild negative drift over parts of the window that biases next-day means down; no frictions, no multiple-testing deflation, in-sample. It's a promising raw signal, not an edge yet.
Backtest with friction + holdout (2026-07-15 — the honest read)
Traded the streak as a rule: at streak ±k, enter (fade = short an up-streak, bounce = long a down-streak) at that close, exit h days later; one position at a time; conservative GDX friction ~0.10-0.12% round-trip (real is ~0.03-0.05%); holdout split at 2020-01-01. Swept k∈{3,4,5}, h∈{1,2,3}, both sides (the test suite).
- The FADE does NOT survive out-of-sample. The strong raw tell was in-sample-flattered: fade k=5 h=1 = +0.49%/trade pre-2020 → −0.09%/trade after; every fade config is flat-to-negative OOS. Shorting up-streaks is not a tradeable edge here.
- The naive combined book (k=3, h=1) LOSES net of friction (−25% total, −46% drawdown) — the k=3 conditional-mean "tell" is not tradeable once you charge the spread.
- The one thing that survives: the BOUNCE after a deep down-streak. bounce k=5, h=2 = +1.22%/trade, hit 60%, IS +1.20% / OOS +1.24%; k=5 h=3 = +1.51%/trade, IS +1.17% / OOS +2.02%, maxDD −15/−19%. Consistent in and out of sample.
- Heavy caveats (why this is a candidate, not an edge): it is the best of 18 swept configs
(multiple-testing — not yet deflated), on only ~60 trades, half of them OOS; GDX's directional drift and a
gold-specific regime could be doing the work; no tail/CVaR read yet. Figure:
fade_equity.png(the fade dying vs the bounce holding, net friction, IS→OOS split).
Redirect (then retracted — see below): the bounce looked like the survivor, so it went to confirmation-hardening.
Confirmation-hardening (2026-07-15) — the bounce does NOT hold up either
Put the deep-down bounce through deflation + tail + cross-ETF (the engine): - Fails deflation. Bounce k5h3 per-trade SR 0.216, t=1.68, naive p=0.094 (not even significant before adjustment) on n=60. Best of 18 swept configs → expected max SR under the null 0.166; the observed 0.216 barely clears it → Deflated Sharpe = 0.69 (needs >0.95). It's within selection noise. - Ugly tail. Worst trade −14.5% (h2) / −18.7% (h3), CVaR₅ ≈ −10%; returns are right-skewed (skew +3.0, kurt 21) — the high mean is a few +36/+42% winners over many small losses (buying a falling knife). - Does NOT generalize. Same bounce on peers: GLD (gold itself) is negative (−0.5%), SLV reverses hard OOS (−1.8 to −2.3%), XLE/EEM/SPY are weak and mostly don't survive OOS. GDX is a lone outlier — and the fact that GLD (what GDX tracks) shows nothing is the tell that GDX's result is regime/small-sample luck, not a real reversion edge.
Honest verdict: the streak-reversion idea, once charged friction + a holdout + deflation + a tail read + a
generality check, does not clear the bar. The raw conditional-mean "tell" was a real statistic but not a
tradeable edge. This is an exhausted exploration (failed), not a candidate to promote — exactly the outcome
the discipline exists to surface. Figure: cross_etf.png.
Engine
the exploration workspace — adjusted series (Tiingo refetch, duckdb fallback), runs, the
top-10 tables + conditional next-day tell, streaks_hist.png.
the exploration workspace — the friction + holdout sweep (fade/bounce × k × h),
fade_equity.png.
Where next (owner to direct)
The full loop (friction → holdout → deflation → tail → generality) has been run and the simple streak-reversion
idea did not survive. Options: (a) close it as an exhausted exploration (recommended read); (b) try a
genuinely different angle only if there's an economic "because" (e.g. a miner-specific overreaction to gold
moves — but that's a new idea/slug, pre-specified, not fitted to these GDX runs); (c) reuse the plumbing
(tasty_execution live GDX round-trip works) for a different, better-motivated setup. No promotion, no live
cell — the idea as stated does not clear the bar.
Gate — [CALIBRATE] (not set; owner to freeze only if it survives)
Not set. A tradeable version would need: net edge after honest GDX frictions, survival on an unseen block, and a tail-aware read (worst trade / CVaR), not just the mean tell.
Registered pre-freeze; ONGOING exploration. Do NOT freeze, count, or fill [CALIBRATE] until the owner says go.
Sibling of S027 (RSI-2 reversion); part of the swing pillar.