Put-selling on capitulation with a market-vol-regime filter
ID: S010
Slug: put-selling-capitulation-volregime-filter-v1
Failed at: Stage 1 (Quick-screen) — near-miss, 6 of 7 gates passed
Fail reason: robustness
Date: 2026-05-31
Lineage: Third follow-up to S008. S008 failed at year-concentration (2022 disaster). S009 (defined-risk spread) cut on edge inversion. S010 keeps S008's naked-put structure but adds an ex-ante regime filter: only fire when SPY's own 20-day realized vol is below its trailing-252-day median.
What we tested
S008 Variant B's signal (multi-day capitulation in an uptrend) plus S008's exact naked-put trade structure (sell 93%-strike put, 45 DTE, 25%/50% take-profit, force-close day 21, $1.30 commission + 5% slippage). The only addition: a filter that only fires the signal on days when SPY's own 20-day realized volatility is below its trailing 12-month median. The intuition: the disasters that killed S008 (2022 rate-shock, 2020 March crash for Variant A) all occurred during regimes where SPY's vol was rising into elevated territory; those regimes are identifiable in real time from market data alone.
The filter operationalises on realized vol rather than VIX directly (realized vol correlates ~0.95 with VIX, requires no new data ingest, and is by construction point-in-time-clean). Pre-registered gates are identical to S008 Variant B since the trade structure is unchanged — only the signal subset differs.
The filter removed roughly half of the candidate signal days (49.8% of SPY days were "calm-regime" by this metric). S008 Variant B's 24,200 unfiltered signals became 11,704 filtered signals. Sample size remained comfortably above the 400 minimum.
What we found
The strategy was within striking distance of a clean Stage 1 pass.
| Criterion | We needed | We got | |
|---|---|---|---|
| Mean per-trade EV (% of notional) | ≥ +0.40% | +1.16% | ✓ |
| Win rate | ≥ 82% | 87.5% | ✓ |
| Welch p<0.05 AND mean>0 | yes | p=0.0000, mean +1.16% | ✓ |
| Sample size | ≥ 400 | 11,467 | ✓ |
| Effect in both sample halves | both > 0 | h1 +1.36%, h2 +0.97% | ✓ |
| Year concentration | ≤ 30% | 42.0% (2021) | ✗ |
| Ticker concentration | ≤ 5% | 0.4% (AXON) | ✓ |
The dollar P/L tells the comparison-with-prior-runs story:
| Year | S008 (no filter) | S010 (volregime filter) | Delta |
|---|---|---|---|
| 2016 | +$45,600 | +$38,938 | -$6,662 |
| 2017 | +$120,586 | +$88,267 | -$32,319 |
| 2018 | +$141,248 | −$4,099 | -$145,347 |
| 2019 | +$74,194 | +$32,305 | -$41,889 |
| 2020 | +$40,453 | +$48,331 | +$7,878 |
| 2021 | +$185,588 | +$435,075 | +$249,487 |
| 2022 | −$2,837,344 | +$11,430 | +$2,848,774 |
| 2023 | +$153,621 | +$133,477 | -$20,144 |
| 2024 | +$421,052 | +$160,048 | -$261,004 |
| 2025 | +$119,463 | +$70,017 | -$49,446 |
| 2026 | +$186,009 | +$22,855 | -$163,154 |
| Total | −$1,349,530 | +$1,036,644 | +$2,386,174 |
The filter did exactly what it was supposed to do: 2022's $2.8M disaster turned into a $11K small win. The strategy moves from losing $1.3M over 11 years to making $1.0M over the same 11 years — a $2.4M swing entirely on the back of avoiding one bad year.
Why this matters / what surprised us
The filter works. This is the cleanest "the setup-mechanic test worked" result in the S008 series. The intuition "don't sell vol when market vol is rising" was operationalised into a simple, PIT-clean filter, applied to a known-positive-edge signal, and the result is positive realized capital with strong per-trade statistics. The 2022 disaster was foreseeable from market data alone.
But year-concentration shifted, didn't disappear. Removing 2022's big loss naturally makes other years more important to the total. 2021 produced $435K of the $1.04M total — 42%, just over the 30% pre-registered gate. The mechanism is mathematical (when the catastrophic year is removed, the remaining years account for a larger share each) rather than a strategy failure.
The 30% gate may be too strict for filter-based setups. When the filter's whole purpose is to exclude bad years, the surviving year-by-year distribution will naturally be top-heavy in the remaining good years. A 30% gate is appropriate for unfiltered strategies where every year is a fair sample; it may be over-strict for filter-based setups. This is a pre-registration-design observation, not a basis for waiving the gate — the gate is FROZEN and S010 is formally cut. But it informs the design of future filter-based pre-registrations.
Variant A wasn't tested under this filter. S008 Variant A (intraday capitulation) failed on structural grounds (sign-flip across halves). The volregime filter might rehabilitate it — most of Variant A's 2020 disaster ($4.6M loss) would be filtered out (March-April 2020 was extreme high-vol regime). If you want to explore that, it would be a new pre-registration (S012+).
Half-2 is positive but slightly weaker than half-1. h1 +1.36%, h2 +0.97%. The strategy's edge is real in both periods but the later years are slightly less generous. Could be coincidence (small-sample variation) or could indicate the underlying short-vol premium is decaying as more retail flow enters short-vol strategies post-2020. Worth watching in Stage 2 if a v2 of this setup is pre-registered.
What this doesn't tell us yet
- A v2 with a tighter calm-regime threshold could pass. If we
required
spy_rv_20d < 0.85 × trailing_median(15% inside the median) instead of strictly below the median, fewer signals would fire but they'd be in genuinely-calm regimes. Could redistribute year-by-year more evenly. Would require new pre-registration. - A v2 combined with risk-weighted sizing might pass. S011 (running next in this session) tests notional sizing that caps per-trade worst-case loss. Combining the volregime filter with risk-weighted sizing would address two failure modes of S008 simultaneously. If both S010 and S011 show partial improvement, the combination is a natural S012 candidate.
- A different concentration metric would change the read. If the gate were "no single year > 50% of net" instead of 30%, S010 passes. That's a gate-design point for future filter-based setups; for this setup the 30% gate was pre-registered and stands.
What happens next
S010 is formally cut. The setup is closed under this pre-registration.
The qualitative read is the most important takeaway from the S008 series so far: the underlying signal (multi-day capitulation in uptrends) + market-vol-regime filter produces positive realized capital over 11 years with strong per-trade statistics. The single 30% concentration check is what's keeping it from being a Stage 1 survivor.
Three plausible follow-ups (each a new H-NNN pre-registration):
- S012 candidate:
put-selling-capitulation-volregime-filter-v2with tightened calm threshold (e.g.spy_rv_20d < 0.85 × trailing_median) - S013 candidate: combine S010's filter with S011's risk-weighted sizing in a single trade structure
- S014 candidate: S010 with a different concentration metric pre-registered (e.g. "no single year > 50% of positive years' sum") — explicit gate-design revision for filter-based setups
The right move depends on how much runway the lab gives this lineage vs. moving to a different idea entirely.
For the specialist — methodology details (click to expand)
Filter definition (computed once on SPY, applied per signal day)
spy_log_ret[t] = log(close_SPY[t] / close_SPY[t-1])
spy_rv_20d[t] = std(spy_log_ret[t-20:t-1]) × √252 (trailing, exclusive of day t)
spy_rv_med_252d[t] = median(spy_rv_20d[t-252:t-1]) (trailing 12-month median, exclusive)
volregime_calm[t] = spy_rv_20d[t] < spy_rv_med_252d[t]
Signal: S008 Variant B + filter
- All S008 Variant B conditions (close > sma_200, rv_rank_252 ≥ 0.30, cumret_15d ∈ [−0.15, −0.08], decelerating, NOT also Variant A, ≥ 90d history, PIT-eligible)
- AND
volregime_calm[t] == True
Trade structure: identical to S008
Naked short put, strike = entry × 0.93, 45 DTE, BS-priced at IV = RV-30 × 1.15. Exit priority: quick-take 25% by day 7 → profit-take 50% any day → force-close day 21 (assignment if underlying ≤ strike). Friction: $1.30 round-trip + 5% slippage on remaining premium.
Pre-registered gate (FROZEN 2026-05-31, same as S008 Variant B since trade structure is identical)
mean_ev_pct_notional ≥ +0.40%
win_rate ≥ 82%
welch_p<0.05 AND mean>0 (direction-aware, on per-trade EV % notional)
n_signals ≥ 400
half1_mean > 0 AND half2_mean > 0 (split at median signal date)
max_year_share ≤ 30%
max_ticker_share ≤ 5%
Detailed numbers
- SPY days with both rv_20d and rv_med_252d computed: 2,867
- Calm-regime days (volregime_calm = True): 1,428 (49.8% of SPY days)
- S008B unfiltered signals: 24,200 → filtered to 11,704 (48.4% retention)
- Trades simulated: 11,467 (after dropping insufficient-forward-history)
- Mean per-trade EV: +1.165%
- Win rate: 87.5%
- Welch 1-sample p (EV vs 0): 0.0000, mean positive
- Half-1: mean EV +1.362% (n ~ 5,700)
- Half-2: mean EV +0.965% (n ~ 5,700)
- 2022 dollar P/L: +$11,430 (vs S008's −$2,837,344) — filter worked
- 2021 dollar P/L: +$435,075 (42.0% of total) — new concentration point
- Net total realized dollar P/L: +$1,036,644 (vs S008's −$1,349,530)
- Top ticker share: AXON at 0.4%
- Cross-check at RV-rank ≥ 0.50: n=8,156, mean EV +1.362%, win 88.1% — stricter vol-rank gives larger per-trade edge as expected
Look-ahead-bias audit
Identical to S008. Filter-specific check: SPY rv_20d uses
.shift(1).rolling(20) (day t never in own window); 252d median
uses .shift(1).rolling(252) (today's regime determination based
only on past data through yesterday's close). PIT-clean by
construction.
Artifacts
- Trades:
lab/postmortem/put-selling-capitulation-volregime-filter-v1/trades.parquet(11,467 rows) - Summary JSON:
stage1_summary.jsonin same directory - Histogram bins:
chart_data.jsonin same directory - Driver script:
lab/quickkill/put-selling-capitulation-volregime-filter-v1/run.py - Pre-registered gates:
lab/setups/gates.md§put-selling-capitulation-volregime-filter-v1 - Setup spec:
lab/setups/put-selling-capitulation-volregime-filter-v1.md
Stage 1 ran 2026-05-31. Pre-registered gates frozen earlier the same day before the test executed. No gates were adjusted post-hoc despite the 6-of-7-pass near-miss.