← Lab
S008

Put-selling on capitulation in an uptrend

put-selling-capitulation-uptrend-v1
failed Stage 1 (Quick-screen) 2026-05-30
Share: Twitter / X LinkedIn
-21.2%-12.8%-4.4%+4.0%+12.5% 7,4120 Per-trade EV (% of notional) — Variant B (primary) EV per trade
Gate +0.40% Observed mean +0.88%
Per-trade EV (% of notional) — Variant B (primary) · n=23,915

ID: S008 Slug: put-selling-capitulation-uptrend-v1 Failed at: Stage 1 (Quick-screen) Fail reason: magnitude Date: 2026-05-30

What we tested

Two distinct "capitulation" triggers, both requiring the stock to be trading above its 200-day moving average (uptrend filter) and at elevated realized volatility (≥ 30th percentile of its own past year):

For every signal, we simulated selling one 45-day put at strike roughly 7% out-of-the-money. The position was held with three exit rules in priority order: take 25% of the premium within the first week, take 50% any day after that, or force-close at day 21. We priced the put using Black-Scholes with the underlying's recent realized volatility (× 1.15 as a standard adjustment for OTM put skew) and subtracted realistic trading costs ($1.30 commission + 5% slippage on the close).

The pre-set criteria — frozen before the test ran — required a positive expected value per trade, high win rate, statistical significance, and crucially no single year contributing more than 30% of total profit (the catastrophic-loss check). For 11 years of data across ~2,100 tickers (top-1,000 US equities monthly PIT + 54 fixed ETFs), the test produced 14,467 Variant-A trades and 23,915 Variant-B trades.

What we found

Both variants failed, but in importantly different ways.

Variant A — intraday capitulation

Criterion We needed We got
Mean per-trade EV ≥ +0.30% −0.15%
Win-rate ≥ 80% 83.1%
Statistically positive (Welch p<0.05, mean>0) yes p=0.0016 but mean negative
Sample size ≥ 500 14,467
Effect in both sample halves both > 0 h1 −0.97%, h2 +0.68%
Single year < 30% of total profit yes 2020 = 322% of net
Single ticker < 5% of signals yes top 0.3% (SMH)

Variant A is unambiguously cut. The signal is statistically distinguishable from zero, but in the wrong direction — the strategy loses money on average. The two halves of the sample produce opposite signs (early years negative, later years positive). 2020 alone lost $4.6M in simulated capital, which is more than 3× the total net loss across all 11 years.

Variant B — multi-day capitulation

Criterion We needed We got
Mean per-trade EV ≥ +0.40% +0.88%
Win-rate ≥ 82% 87.1%
Statistically positive (Welch p<0.05, mean>0) yes p=0.0000, mean +0.88%
Sample size ≥ 400 23,915
Effect in both sample halves both > 0 h1 +0.82%, h2 +0.94%
Single year < 30% of total profit yes 2022 = 210% of net
Single ticker < 5% of signals yes top 0.4% (VXX)

Variant B is the interesting cut: 6 of 7 pre-set criteria pass. Per-trade expected value is positive and statistically significant with overwhelming sample size. Both halves of the sample are positive. The only failure is the year-concentration check — and that one failure is decisive.

Why the concentration check is the binding criterion

The dollar P/L tells the story the per-trade stats hide:

Variant B year-by-year net realized profit
  2016:  +$45,600
  2017: +$120,586
  2018: +$141,248
  2019:  +$74,194
  2020:  +$40,453
  2021: +$185,588
  2022:  −$2,837,344  ← rate-shock year, bonds + cyclicals capitulated
  2023: +$153,621
  2024: +$421,052
  2025: +$119,463
  2026: +$186,009
  ─────────────────
  TOTAL: −$1,349,530

Ten of eleven years are positive. One year (2022) loses more than twice the absolute value of the eleven-year net. The trader running this strategy on real capital ends 2026 with less money than they started with, despite a per-trade win rate of 87%.

This is the textbook short-volatility fat-left-tail signature. Selling out-of-the-money puts is selling insurance against downside; in most years that insurance is overpriced and you collect the premium. In years when capitulations cluster (2020 COVID, 2022 rate-shock), the same trade structure means your losses pile up faster than your winners can compound. The strategy generates positive expected value per trade and a negative realized P/L over time.

Why this matters / what surprised us

The pre-registered gates worked exactly as designed. A naive backtest reporting "87% win rate, +0.88% mean per trade, statistically significant" would have called this a winner and shipped it. The 30%-concentration check is the difference between "looks good" and "actually makes money over a decade including fat-tail years". Future short-vol setups in this lab will inherit this check as a default rather than something we have to remember to add.

Variant B's signal might still have an exploitable form — but not in the structure we tested. Three honest reframings worth a new H-NNN pre-registration:

  1. Sized for tail-risk: same signal but with position sizing that targets a fixed worst-case loss (e.g. notional sized so a 100% gap to zero only loses 1% of capital). The per-trade EV becomes smaller in absolute dollars but the catastrophic year stops being fatal.
  2. Defined-risk structure: replace naked short puts with put credit spreads (sell the 93%-OTM put, buy the 85%-OTM put). The premium drops by maybe a third but the worst-case loss is bounded. Setup becomes: does the residual edge after buying tail protection still clear a tightened gate?
  3. Vol-of-vol gate: only fire the signal when SPY's VIX is below its own trailing median (i.e. avoid selling vol when vol-of-vol is rising). The 2022 disaster occurred during a period of rising vol-of-vol; this gate might filter the worst subset.

These would each be a new pre-registered setup (S009 / S010 / ...), not a modification of S008. The current cut stands.

The RV-rank proxy was probably not the failure point. When we considered Stage 2, we noted that the realized-vol-rank used as a proxy for true IV-rank could itself be the reason a Stage-1 result disappoints. The cross-check at RV-rank ≥ 0.50 (informational only, not pass/fail) produced essentially identical numbers (Variant A mean -0.12% vs -0.15%; Variant B mean +0.98% vs +0.88%). The catastrophic-tail problem is structural to short-vol-into- capitulation, not an artifact of the proxy. A true IV-rank reconstruction would not save this setup.

What this doesn't tell us yet

  1. A defined-risk version isn't ruled out by this test. A put credit spread or a put butterfly caps the worst-case loss and may avoid the 2022-style year-concentration. Worth pre- registering as a separate setup with its own gates.
  2. A position-sized version isn't ruled out either. The simulation here used uniform 1-contract sizing per signal, which means a $30 stock and a $300 stock contribute equally to per-trade EV but very differently to dollar P/L. A notional-weighted or risk-weighted version would change the year-concentration dynamics.
  3. The signal itself (capitulation in an uptrend) is not debunked. The directional intuition — that prices recover after panic flushes in stocks that remain in longer-term uptrends — is supported by 87% win rate and statistically significant positive per-trade EV in Variant B. The cut is on the trade structure, not on the underlying observation.

What happens next

S008 is closed. The strategy is failed at Stage 1.

If you want to pursue the underlying observation further, the suggested follow-ups (in rough order of "most likely to survive the same gates"):

Each would need its own pre-registered gates and a fresh H-NNN.

For the specialist — methodology details (click to expand)

Setup (verbatim from spec)

Selling 45-60-DTE OTM puts on liquid US equities and ETFs at moments of capitulation (intraday or multi-day) while the underlying remains in a longer-term uptrend (close > 200d SMA) and realized-vol-rank is elevated (≥ 30 of trailing 252d) predicts positive expected value per trade after friction over the put's holding period (≤ 24 calendar days), because capitulation events in established uptrends transiently inflate option premiums beyond what subsequent realized volatility justifies.

Test setup

  • Universe: monthly PIT top-1,000 US equities by trailing 60d dollar volume, plus 54 fixed liquid ETFs (src/data/etf_universe.py). Effective per-month membership ~2,000; distinct tickers across full sample 2,048.
  • Period: 2015-01-01 → 2026-05-29 (~11.4 years).
  • Data: Tiingo Power EOD, 12.6M bars in DuckDB after ingest.
  • Features computed per (ticker, day) via trailing-only .shift(1).rolling(N) to enforce point-in-time:
  • ATR-20 (true range, 20d simple average)
  • RV-20 (annualised stdev of log returns, 20d)
  • RV-30 (same, 30d — used as IV proxy in BS pricing)
  • RV-rank-252 (rolling percentile of RV-20 within trailing 252d)
  • SMA-200 (close-based)
  • Cumulative-return-15d (close[t] / close[t-15] − 1)
  • Variant A signal at day-t close: common_pass AND daily_return ≤ −0.02 AND |daily_return × prev_close| ≥ 1.5 × ATR-20.
  • Variant B signal at day-t close: common_pass AND cumret_15d ∈ [−0.15, −0.08] AND mean(|return| last 3 days) < mean(|return| days 4-10) AND NOT also Variant A.
  • Common pass: close > SMA-200 AND RV-rank-252 ≥ 0.30 AND history ≥ 90 trading days.

Trade-simulation engine

src/lab/options_sim.py — new module for this setup, reusable for any future short-vol or long-vol H-NNN: - bs_put_price(spot, strike, dte_days, iv_annual, risk_free) — Black-Scholes European put with closed-form normal CDF (no scipy dependency in the hot loop) - bs_call_price(...) — kept for future short-call setups - simulate_short_put(...) — opening BS price → daily MTM → priority-ordered exit-rule evaluation → realized P/L with friction

BS pricer sanity-checked at build time via put-call parity (C − P = S − K·exp(−rT)) and theta-decay direction. Matches to 4 decimal places.

Opening premium: BS put price with IV = RV-30 × 1.15 (the 1.15 multiplier is the empirical OTM-put skew adjustment). MTM daily with same IV model recomputed off trailing-30d RV ending the prior day (no look-ahead). Exit priority: 1. Quick-take: ≥ 25% of opening premium captured by day 7 → close 2. Profit-take: ≥ 50% captured any day → close 3. Force-close at day 21 (DTE = 24): - If underlying close > strike: close at remaining MTM (winner) - If underlying close ≤ strike: assignment loss = (strike − close) × 100

Friction applied to every trade regardless of exit reason: $1.30 round-trip commission + 5% of remaining premium as slippage.

Pre-registered gate (FROZEN 2026-05-30, all required per variant)

VARIANT A:
  mean_ev_pct_notional   ≥ +0.30%
  win_rate               ≥ 80%
  welch_p_value          < 0.05 AND mean > 0 (direction-aware)
  n_signals              ≥ 500
  half1_mean_ev > 0 AND half2_mean_ev > 0 (split at median date)
  max_year_share         ≤ 30%
  max_ticker_share       ≤ 5%

VARIANT B:
  mean_ev_pct_notional   ≥ +0.40%
  win_rate               ≥ 82%
  welch_p_value          < 0.05 AND mean > 0
  n_signals              ≥ 400
  half1_mean_ev > 0 AND half2_mean_ev > 0
  max_year_share         ≤ 30%
  max_ticker_share       ≤ 5%

CROSS-CHECK (informational only, does NOT affect pass/fail):
  Same metrics with RV_rank ≥ 0.50 threshold

Implementation notes

  • Welch t-test is scipy.stats.ttest_1samp on ev_pct_notional (not on dollar P/L; dollar variance is dominated by ticker-price differences). Initial implementation had this wrong; fixed before any pre-registered gate evaluation.
  • Year concentration is max(|yearly_profit|) / |total_profit| where yearly_profit is summed dollar net P/L per calendar year. When total_profit is small or negative, this metric can exceed 100% — interpret as "a single year's absolute P/L exceeds the net total" which is exactly the catastrophic-concentration signature this check is designed to catch.
  • The realized-vol-rank-as-IV-rank-proxy was acknowledged as a proxy at pre-registration time. The cross-check at RV-rank ≥ 0.50 (informational) produced near-identical results, suggesting the proxy is not the limiting factor for this particular signal.

Detailed numbers — Variant A

  • Trades: 14,467 (across 11 years, 2015-2026)
  • Mean per-trade EV: −0.152%
  • Win rate (positive net P/L): 83.1%
  • Welch p (1-sample EV vs 0): 0.0016 (statistically significant in the negative direction)
  • Half-1 (2015-mid-2020): mean EV −0.971%
  • Half-2 (mid-2020-2026): mean EV +0.676%
  • 2020 dollar loss: −$4,623,171 (322% of |net total|)
  • Top ticker share: SMH at 0.3% of signal-event count

Detailed numbers — Variant B

  • Trades: 23,915 (across 11 years)
  • Mean per-trade EV: +0.877%
  • Win rate: 87.1%
  • Welch p: 0.0000 (statistically significant in positive direction)
  • Half-1: mean EV +0.816%
  • Half-2: mean EV +0.937%
  • 2022 dollar loss: −$2,837,344 (210% of |net total|)
  • Top ticker share: VXX at 0.4% of signal-event count

Look-ahead-bias audit

Input How it's point-in-time Verified
Universe membership (equities) Monthly PIT top-1,000 from trailing 60d $ volume snapshot taken at month-start src/data/universe.py:monthly_top_n_snapshot
Universe membership (ETFs) Fixed list, eligibility per (etf, month) only when ETF has bars in that month src/data/etf_universe.py
ATR-20, RV-20, RV-30, SMA-200, RV-rank-252 All computed via .shift(1).rolling(N) — day-t bar never enters its own window Per-feature in run.py:compute_features
Cumulative return 15d close[t] / close[t-15] − 1 — uses close-of-day-t as the signal-trigger price, which IS the data available at signal close Standard for end-of-day signals
Entry execution Day t+1 open (the morning AFTER signal close) simulate_short_put indexes bars_after_signal[0] which is t+1
Daily MTM BS pricing uses day-t close + trailing-30d RV ending day t-1 + remaining DTE All known at MTM compute time
Exit fill price Close-price of the exit day with slippage applied AS A COST (worsening the fill) Conservative — no look-ahead

Artifacts

  • All Variant A trades: lab/postmortem/put-selling-capitulation-uptrend-v1/trades_variant_a.parquet (14,467 rows)
  • All Variant B trades: lab/postmortem/put-selling-capitulation-uptrend-v1/trades_variant_b.parquet (23,915 rows)
  • Summary JSON (per-variant gate results + yearly P/L breakdown): lab/postmortem/put-selling-capitulation-uptrend-v1/stage1_summary.json
  • Histogram bins (per variant + primary): chart_data_variant_a.json, chart_data_variant_b.json, chart_data.json (all in postmortem folder)
  • Driver script: lab/quickkill/put-selling-capitulation-uptrend-v1/run.py (kept in quickkill folder for re-runs)
  • Trade simulation engine (new, reusable): src/lab/options_sim.py
  • Pre-registered gates: lab/setups/gates.md §put-selling-capitulation-uptrend-v1
  • Setup spec: lab/setups/put-selling-capitulation-uptrend-v1.md

Stage 1 ran 2026-05-30. Pre-registered gates frozen earlier the same day before the test executed. No gates were adjusted post-hoc.

← Olderput-selling-capitulation-uptrend-v1 Newer →put-credit-spread-capitulation-uptrend-v1

Mechaniq provides information, not investment advice. We do not execute trades. Past results are no guarantee for future performance. You are solely responsible for your trading decisions.

Lab · Methodology · Greeks Lab · About · FAQ · Glossary · Privacy · Terms · mechaniq.trade © 2026

07 Jul 2026, 07:06