Skip to content

Historical pennant strategy recovery

What this is: a one-pass characterization of every committed backtest script in build_v1/ that bears on the pennant lineage, plus a few adjacent vol-rank / VolGap families that share infrastructure with the pennant scaled-exit work. Each entry pins file path, last-modified date, strategy mechanics (mechanics that are inferable from the source or from the markdown report they wrote), and known headline numbers.

Scope of "pennant lineage": anything that takes pattern_events.pattern_type IN ('bull_pennant', 'pennant') (or the detector's anchor close) as the entry trigger, or that shares the scaled-exit primitive (+15 % Leg 1 / 3 % trail / –7 % stop / day-30 cap). The vol-rank and VolGap call-only family is included because those scripts publish primitives (pick_contracts, reorder_same_date_events, simulate_with_cap, etc.) that the pennant family reuses, and because Strategy C — which IS the current operational baseline (+34 % CAGR, $352K audit anchor) — is not a pennant strategy but is a sibling that the next-phase research will need to compare against.

All paths below are read-only references. Nothing was modified.


Iteration map (by published headline)

These are the three claims El Don's prompt explicitly named:

  1. +654 % / +693.7 % over 20 years (stock, scaled exit). Source: reports/regime_filter_backtest.md (Phase 7). Script: scripts/backtest_regime_filter.py. Scaled-exit on the 1,975 Rule-1/2-filtered pennant set, $2,000/trade ($1k × 2 units), 5 concurrent. The +693.7 % figure is the unfiltered scaled-exit run; +654.4 % is the same simulator with the SPY-200 / VIX-35 regime gate applied. Both are reproducible from the same committed script.
  2. +118.9 % over 10 years (options ITM 0.95 × × 21–35 DTE). Source: reports/options_backtest.md (Phase 2). Script: scripts/run_options_backtest.py (per-event simulator) + scripts/report_options_backtest.py (portfolio aggregator). 932 filtered bull_pennants, $500/trade, 5 concurrent, anchor close entry, real Polygon option prices.
  3. Strategy C audit anchor $373K → $352K (Phase 8–10). Source: reports/phase 9/anchor_restatement_2026-05-10.md + Project_Uriel_Handoff_Notes_v6.md §4. Script: scripts/audit_anchor_115x.py. NOT pennant. This is the vol-rank Strategy C reproducer locked to bb43351 parameters. Included here because the prompt cites it as part of the prior strategy history and because future pennant work will be compared to it.

What follows breaks each script's mechanics out in the per-script table below.


A. Pennant + scaled-exit family

A.1 scripts/backtest_regime_filter.py

  • Path: build_v1/scripts/backtest_regime_filter.py
  • Last modified: 2026-05-08 (touched during Phase 8 cutover; the strategy logic itself dates to Phase 7, ≈late April 2026).
  • Strategy: Stock-only pennant. Two specs run in parallel:
  • Strategy 2: fixed +15 % / –7 % / day-30 hard exit. Single unit.
  • Scaled exit: Leg 1 +15 % sells 1 unit; remaining unit trails 3 % from highest forward close; –7 % hard stop on either leg; day-30 hard exit on whatever remains.
  • Position sizing: Strategy 2: $1,000/trade, max 10 concurrent. Scaled exit: $2,000/trade (2 units × $1k), max 5 concurrent. Starting capital $10,000. Fixed dollar — no compounding.
  • Entry: anchor close of every event in the 20-year filtered set.
  • Exit triggers: see strategy section above.
  • Concurrency / capital: max-concurrent cap as stated; new entry skipped silently if cap is full.
  • Friction: NOT modeled in this script. Backtest is close-to-close on pattern_events.full_forward_series (a 30-element JSON array of fractional returns precomputed by the outcomes profiler).
  • Universe + dates: 1,975 events on the Rule-1/2-filtered bull pennant set, 2007-04 → 2026-04 (20-year OOS sample from Phase 4).
  • Regime gate (the headline lever): if SPY < 200SMA AND VIX > 35 on the anchor date, skip the entry. Mirrors uriel.regime thresholds (the script keeps them inline so it is standalone).
  • Reported headline: scaled exit: +693.7 % unfiltered, +654.4 % filtered ($79,375 vs $75,444 final on $10k start), max DD –9.7 % vs –15.7 %. Strategy 2: +326.1 % / +315.8 %.

A.2 scripts/run_options_backtest.py + scripts/report_options_backtest.py

  • Paths: build_v1/scripts/run_options_backtest.py (per-event simulator), build_v1/scripts/report_options_backtest.py (portfolio aggregator).
  • Last modified: 2026-05-08 (touched during Phase 8 cutover; logic from Phase 2, ≈late April 2026).
  • Strategy: Options scaled exit on bull pennant anchors. Buy 2 calls at the anchor-day option close, ITM (closest strike to 0.95× anchor spot), 21–35 DTE. Walk the underlying day-by-day:
  • Stop: stock ≤ –7 % from anchor → sell both calls.
  • Leg 1 target: stock ≥ +15 % from anchor → sell 1 call.
  • Leg 2 trail (after Leg 1 fires): stock < 0.97 × highest-close since Leg 1 → sell runner.
  • Hard exit: end of 30-day window or contract expiration → sell remainder.
  • Position sizing: $500/trade (2 contracts), max 5 concurrent. Starting capital $10,000. Fixed dollar.
  • Universe + dates: 932 filtered bull_pennants with status='ok' in options_backtest_data (Polygon flat-file coverage), 2017-03-13 → 2026-03-27 (≈10-year window inside Polygon's options coverage).
  • Friction: mid-price option fills; no explicit commissions in the simulator. (Report assumes a 1.6 % round-trip estimate when citing live edge.)
  • Reported headline: stock cohort (same events): +78.3 % total return / $17,834 final on $10k. Options cohort: +118.9 % total return / $21,888 final on $10k. PF 1.46, max DD –10.9 %.
  • Iterations: report_options_backtest.py is the canonical reporter; report_options_grid.py does a 20-combo strike × DTE grid that feeds reports/options_strike_exp_optimization.md.

A.3 scripts/pennant_iv_filtered.py

  • Path: build_v1/scripts/pennant_iv_filtered.py
  • Last modified: 2026-05-08
  • Strategy: Identical mechanics to A.2 above (reuses simulate_option_trade + simulate_stock_trade from run_options_backtest.py); adds an entry-time filter on iv_percentile_90d. Sweep thresholds: None, 25, 30, 40, 50.
  • Universe + dates: same 932 event set, filtered by iv_percentile_90d < threshold.
  • Reported headline: filtering helps moderately but cuts trade count severely. Not adopted in production. See reports/pennant_iv_filtered.md.
  • Relationship to A.2: sibling sweep — same simulator, different cohort. Not an independent strategy variant.

A.4 scripts/pennant_l1_stop_fidelity.py + scripts/pennant_trail_fidelity.py

  • Paths: as above. Both last modified 2026-05-08.
  • Strategy: Not new strategy variants — fidelity studies comparing the close-based backtest to a tick-based (intraday-OHLC) re-simulation of the same scaled-exit ledger. Reads the stock_trades table (the 932-event ledger from A.2) and checks for whipsaws: days where intraday High/Low triggered an exit that the daily close did not.
  • Output: quantifies the close-vs-tick gap so live execution decisions can budget for it. Drives near_close_exits.py deployment (3:55 PM ET close-based exits with a market fallback at 3:59:30 PM ET).
  • Relationship: infrastructure on top of A.2's results, not a separate strategy. Cataloged here because the prompt asks for every pennant-backtest script.

B. Strategy C / vol-rank family (NOT pennant, but adjacent)

These scripts share simulator primitives with the pennant family and are the source of the +373K → +352K headline in the prompt. Important: Strategy C trades vol-rank signals (not pennants). Vol- rank is a different pattern_type in pattern_events. Listed here because the prompt asks about the audit-anchor history.

B.1 scripts/squeeze_strategy_backtest.py

  • Path: build_v1/scripts/squeeze_strategy_backtest.py
  • Last modified: 2026-05-08
  • Strategy: Squeeze-Pro straddle/strangle on squeeze fire events. Three specs (straddle 1.00×/1.00×, strangle_itm 0.95×/1.05×, strangle_otm 1.05×/0.95×); seven exit-rule variants (tgt30_stop40_t14, tgt20_stop40_t14, tgt50_stop40_t14, tgt30_stop30_t14, tgt30_stop50_t14, tgt30_stop40_t7, tgt30_stop40_t21). 21–35 DTE, DTE_MID=28.
  • Position sizing: $500/trade ($250/leg), max 10 concurrent, $10k start.
  • Filters: squeeze fire on a stock with ATR % > 5 %, deepest squeeze tier ∈ {2, 3}, SPY-200 / VIX-35 regime gate, date ≥ 2014-06-02 (Polygon coverage).
  • Reported headline (per reports/squeeze_strategy_backtest.md): squeeze fires alone are weak; OTM strangle is the only profitable spec, ≈mediocre standalone. Superseded by vol-rank.
  • Why it matters here: publishes the canonical pick_contracts(con, events, specs) + load_forward_prices(con, pairs_by_spec) primitives reused by every options-backtest script downstream, including the audit anchor.

B.2 scripts/squeeze_iv_strategy.py

  • Path: as above. 2026-05-08.
  • Strategy: B.1 with IV-percentile entry filter (iv_percentile_90d < threshold ∈ {25, 30, 40, 50}). Restricted to the OTM strangle spec.
  • Reported headline: see reports/squeeze_iv_study.md. Modest improvement, friction-sensitive. Not adopted.

B.3 scripts/vol_rank_strategy.py

  • Path: as above. 2026-05-08.
  • Strategy: OTM strangle (1.05 call + 0.95 put), 21–35 DTE, on vol-rank signals. Standalone canonical exit rule tgt20_stop40_t07 (+20 % / –40 % / 7 calendar days).
  • Position sizing: $500/trade, max 10 concurrent. Fixed dollar.
  • Friction: 1.6 % per round trip (≈$8 on $500).
  • Reported headline (reports/vol_rank_strategy.md): $56,766 final on $10k over 143 months (2014-06 → 2026-04), +15.80 % CAGR, max DD –25.8 %, PF 1.93. The headline vol-rank standalone number.

B.4 scripts/vol_rank_liquidity.py

  • Path: as above. 2026-05-08.
  • Strategy: B.3, swept across 4 liquidity tiers ($1M, $5M, $10M, $25M 20-day avg dollar volume on entry date). Tier D ($1M) selected as the canonical universe.
  • Why it matters: publishes load_signals_with_liquidity, LIQUIDITY_TIERS, SPEC, RULE reused everywhere downstream.

B.5 scripts/vol_rank_regime.py

  • Path: as above. 2026-05-08.
  • Strategy: 5 strategies on the vol-rank signal stream, partitioned by regime (HIGH VOL = month-avg VIX > 20 OR SPY 20-day RV > 60th percentile trailing 2y):
  • A: vol-rank every month
  • B: vol-rank only in HIGH VOL months (cash otherwise)
  • C: vol-rank in HIGH VOL + 100 % SPY in LOW VOL months
  • D: pennant ITM 0.95 × × 21–35 DTE every month (gross friction)
  • E: pennant in LOW VOL + vol-rank in HIGH VOL
  • Sizing: $500/trade fixed. Friction: vol-rank 1.6 %, pennant 1 % (single-leg ITM call).
  • Reported headline: Strategy C wins ($351K–$625K depending on non-determinism). This is the lineage of the audit anchor in B.7.

B.6 scripts/vol_rank_pct_equity.py

  • Path: as above. 2026-05-08.
  • Strategy: B.5 with 5 % of current equity per trade (max 10 concurrent = max 50 % deployed) instead of fixed-$500.
  • Reported headline: Strategy C still wins, with the C/E re-ordering question (the original motivation) FALSIFIED — pennant cannot displace SPY as a low-vol allocator even when both compound.
  • Why it matters: publishes equity_stats(eq, start_capital) reused throughout the call-only family.

B.7 scripts/audit_anchor_115x.py — the canonical reproducer

  • Path: as above. Last modified 2026-05-10 (Phase 10d). Built in Phase 9d.2 (2026-05-09) to reconstruct the never-committed Strategy C audit-anchor build script.
  • Strategy: Strategy C, hard-locked to bb43351 (2026-04-28) parameters:
  • Strike multiplier 1.15× call (pure call-only — no put, no SPY overlay in LOW-vol months)
  • Penny filter call_entry >= $0.50 applied after trade build
  • Max concurrent 10
  • Position size $1,000 fixed dollar
  • Exit rule tgt20_stop40_t07 (+20 % / –40 % / 7 calendar days)
  • Liquidity Tier D (≥ $1M dollar-volume 20-day average)
  • Regime gating: HIGH-vol months only (cash in LOW-vol)
  • Phase 10d CORRUPT_TICKER_EXCLUSIONS = {CVNA, ANAB, IMUX, CUE}
  • Architecture: wraps four primitives in their original locations — pick_contracts (B.1), build_call_only_trades_for_strike (volgap_call_only_revalidate.py), reorder_same_date_events (march_2023_deep_dive.py), simulate_with_cap (volgap_call_only_pure_concurrency_sweep.py).
  • Snapshot-aware: --snapshot <date> flag swaps live Postgres for a frozen parquet snapshot (Phase 9d.3 infrastructure).
  • Reported headlines:
  • Phase 9h.2 (snapshot 2026-05-09b, pre-Phase-10d): 2,544 trades / $373,263 / +34.14 % CAGR / –12.97 % max DD / ratio 2.633. This is the "$373K" cited in the prompt.
  • Phase 10d (snapshot 2026-05-09c, post-corrupt-ticker exclusion, current canonical): 2,526 trades / $352,864 / +33.53 % CAGR / –12.97 % max DD / ratio 2.586. This is the "$352K final equity" cited in the prompt.

C. VolGap call-only sweep family (Phases 5–7, pre-Postgres cutover)

A multi-axis sweep on the Strategy C parameter surface that produced the pre-audit-anchor research. Same simulator family as B; sweeps strike / sizing / concurrency / exit rule. Listed at lower granularity because they are not the pennant strategy line — they exist mainly because volgap_call_only_pure_corrective.py is the published BUG report on the original Phase 5–7 results and the audit anchor relies on the corrective pipeline.

Script Axis Notes
volgap_call_only_pure_baseline.py n/a — canonical Phase 5 ledger Publishes FIXED_DOLLAR=1000, build_daily_from_log, metrics. Reports baseline ratio 1.247 (pre-corrective).
volgap_call_only_pure_strike_sweep.py Strike multiplier 1.00× / 1.05× / 1.10× / 1.15× 1.10× + 1.15× were the deployable candidates.
volgap_call_only_pure_concurrency_sweep.py max concurrent 5 / 10 / 15 / 20 Publishes simulate_with_cap.
volgap_call_only_pure_sizing_sweep.py fixed $ vs % equity No deployable variants.
volgap_call_only_pure_exit_sweep.py exit-rule grid No deployable variants.
volgap_call_only_pure_revalidate.py hand-trace + random-skip on 1.15× and cap-20 Phase 7 re-validation.
volgap_call_only_pure_integrated.py integrated candidate No combinatorial — only strike axis produced winners.
volgap_call_only_pure_corrective.py Phase 8f.4 corrective Bug report + corrected re-run. BUG 1: sliced-window OOS; BUG 2: inverted random-skip gate; BUG 3: 1.15× contradiction. Module docstring is the source of truth for what was wrong with the original Phase 5–7 results.
volgap_call_only_concurrency_sweep.py, _exit_sweep.py, _sizing_sweep.py, _strike_sweep.py, _sweep_integrated.py, _validation.py, _revalidate.py earlier (pre-corrective) iterations of the same axes Superseded by the _pure_* family above. Kept committed for traceability.
volgap_asymmetric_sizing.py win/loss-asymmetric sizing Outputs in reports/volgap_asymmetric_sizing.md.
volgap_combined_filter.py combined entry filters reports/volgap_combined_filter.md.
volgap_defined_risk_synthetic.py synthetic defined-risk structures reports/volgap_defined_risk_synthetic.md.
volgap_directional_variants.py directional variants Publishes simulate_call_only.
volgap_structure_comparison.py structure comparison reports/volgap_structure_comparison.md.
volgap_synthetic_long_clarification.py synthetic long clarification reports/volgap_synthetic_long_clarification.md.
volgap_strategy_decision_chart.py decision-trail chart reports/volgap_strategy_decision_trail.md.

Each script's module docstring is the canonical spec.


D. Validation / sensitivity studies on top of D / Strategy C

Not new strategies — sensitivity studies. Each runs a published strategy at varied parameters and asks whether the change is adoptable.

Script What it tests Output report
validate_cooldown_extension.py extending the 30-day cooldown reports/cooldown_extension_validation.md
validate_day3_early_exit.py day-3 early-exit trigger reports/day3_early_exit_validation.md
validate_early_exit.py generic early-exit reports/early_exit_validation_full.md
validate_gap_filter.py gap-up entry filter reports/gap_filter_validation.md
validate_regime_thresholds.py VIX / SPY 200 thresholds reports/regime_threshold_robustness.md
sizing_sweep_deterministic.py deterministic sizing rerun reports/sizing_sweep_deterministic.md
temporal_analysis.py 20-year temporal stability reports/temporal_analysis.md
post_2020_baseline_analysis.py post-2020 baseline (no separate report)
march_2023_deep_dive.py trade-level diag of Strategy C's worst month reports/march_2023_deep_dive.md

E. Phase 11 — current-session pennant strategy backtest

For completeness alongside the historical scripts.

E.1 Pennant/ab_test/backtest.py (Phase 11b)

  • Path: Pennant/ab_test/backtest.py
  • Last modified: 2026-05-11
  • Strategy: Stock-only pennant scaled exit. Identical mechanics to A.1 but run across the five detection-criteria scenarios produced by Phases 11a / 11a-2 / 11a-3.
  • Position sizing: $500/trade fixed (NOT $1,000 or $2,000), max concurrent = unlimited (cash-only constraint), starting $10,000.
  • Entry / exit: entry at anchor close + 5 bp slippage; Leg 1 +15 % half-exit; 3 % trail on the remainder (only active after Leg 1 fires); –7 % hard stop; 30-trading-day time stop. $0.50 commission per fill, –5 bp slippage on sells.
  • Regime gate: skip entry if SPY < SPY-200-SMA AND VIX > 35 (240 days in the calendar).
  • Universe + dates: five event populations from the cached parquets in Pennant/ab_test/{baseline,variant,variant_v2, variant_v3,variant_v4}_events.parquet, 2007 → 2026-05-11.
  • Reported headlines (pennant_strategy_backtest_2026-05-11.md): Baseline $40,398 (+304 %, 7.5 % CAGR, –38.1 % max DD); V2 $35,651 (+257 %, 6.8 % CAGR, –21.4 % max DD, Sharpe 0.617 — best risk-adjusted).

E.2 Pennant/ab_test/run_ab.py / run_v2.py / run_v3_v4.py (Phases 11a / 11a-2 / 11a-3)

  • Strategy: Not strategy variants — detection-criteria variants. Each mutates the pydantic config in-process, then calls uriel.detect.pennant._detect_for_ticker to produce a different cohort of events. Events + outcomes are written to parquet under Pennant/ab_test/. Production pattern_events is untouched.
  • Universe + dates: full active-ticker universe, 2007-02-15 → 2026-05-08.
  • Variants:
  • Baseline: pennant 5–15, flagpole 1–10 (production).
  • V1 (Phase 11a): pennant 10–20, flagpole 1–5.
  • V2 (Phase 11a-2): pennant 7–17, flagpole 1–5.
  • V3 (Phase 11a-3): pennant 6–17, flagpole 1–3.
  • V4 (Phase 11a-3): pennant 6–17, flagpole 1–2.
  • Other detection params unchanged across variants: pennant.max_retrace_pct = 0.382, flagpole.min_magnitude_pct = 12.0, flagpole.min_atr_multiple = 4.0, flagpole.volume_ratio_min = 1.5, trend_filter on (EMA_55 ≥ 10d prior).

F. Archived (DuckDB-era) scripts of pennant interest

Listed for completeness. All would need Postgres rewrites to run today. The output artifacts remain in build_v1/reports/.

Script Purpose
archive/scripts/loser_pattern_full_backtest.py Win-rate separation across 33 candidate signals on Strategy C's Fixed-$1000 ledger.
archive/scripts/early_exit_exploration.py First-pass early-exit screen.
archive/scripts/early_exit_validation.py Survivorship-bias-corrected validation of early-exit candidates.
archive/scripts/squeeze_profile_outcomes.py Squeeze-fire outcome profiling (Phase 1).
archive/scripts/squeeze_study.py Squeeze tier × direction study.
archive/scripts/question_b_study.py "Do Rule 1/2 features predict 30-day moves without a pennant?" (yes) — feeds reports/question_b_move_prediction.md.
archive/scripts/drawdown_decomposition.py, drawdown_chronology_quick.py Strategy C drawdown decomposition.
archive/scripts/sizing_deep_dive.py Side-by-side fixed-$ vs %-equity sizing.
archive/scripts/run_pilot.py First pilot run (date-bracket unknown).

Gaps / things this recovery cannot answer

  • No Phase 1 / Phase 2 original commit script for the +130.7 % / +272.5 % stock backtests is committed under that exact name. The numbers in reports/strategy_backtest.md and reports/scaled_exit_backtest.md were produced by an unnamed Phase 1 simulator that was apparently subsumed into backtest_regime_filter.py (which can reproduce the unfiltered variant by setting the regime threshold off — the unfiltered column in regime_filter_backtest.md matches the Phase 1 spec). This pattern (script not committed, output preserved) recurred in Phase 9d.1 where the original audit-anchor build script also never made it to git and had to be reconstructed as B.7.
  • The $0.50 penny filter, the bb43351 commit hash, and the +20% / –40% / 7-day exit rule on Strategy C are all hand-pasted from the audit_anchor_115x.py source. They are not separately tested in this prompt — verify against the script if relying on them.
  • overlapping field handling in the recent-scan path differs from the charter — see pennant_detection_criteria_2026-05-11.md §7 ¶2. Affects which events historical scripts could see if they used pattern_events directly (Phase 1–3 scripts predate the divergence and used the full historical scan; the issue only affects live operational scanning).
  • Friction model in A.1 is zero. Real $0.50 / fill commissions
  • slippage applied in E.1 (Phase 11b) lower the reported number vs A.1's published headline. Apples-to-apples comparisons across the historical lineage need the friction column normalized first.

End of recovery.