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:
- +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. - +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. - 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 > 35on the anchor date, skip the entry. Mirrorsuriel.regimethresholds (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'inoptions_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.pyis the canonical reporter;report_options_grid.pydoes a 20-combo strike × DTE grid that feedsreports/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_tradefromrun_options_backtest.py); adds an entry-time filter oniv_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_tradestable (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.pydeployment (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 (
straddle1.00×/1.00×,strangle_itm0.95×/1.05×,strangle_otm1.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,RULEreused 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.50applied 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_tickerto produce a different cohort of events. Events + outcomes are written to parquet underPennant/ab_test/. Productionpattern_eventsis 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_filteron (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.mdandreports/scaled_exit_backtest.mdwere produced by an unnamed Phase 1 simulator that was apparently subsumed intobacktest_regime_filter.py(which can reproduce the unfiltered variant by setting the regime threshold off — theunfilteredcolumn inregime_filter_backtest.mdmatches 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.pysource. They are not separately tested in this prompt — verify against the script if relying on them. overlappingfield handling in the recent-scan path differs from the charter — seepennant_detection_criteria_2026-05-11.md§7 ¶2. Affects which events historical scripts could see if they usedpattern_eventsdirectly (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.