← Back to Dashboard

Strategy Analysis Architecture

Status: Permanent reference doc. Created Apr 26, 2026 as Phase 0 of the Strategy Analysis Revamp. Phase 1 shipped Apr 26, 2026 (see §0.4 + §0.9). Phase 2 shipped Apr 26, 2026 — see §0.10 for the equity 1m bars infrastructure changelog and revised Phase 3 gate criteria.

Purpose: Map every layer of strategy analysis the bot relies on (operative rules + production data + analysis scripts + dashboard surfaces), catalogue every documented assumption with its current validation status, identify integrity bugs and doc inconsistencies, and decide whether unified-simulation work is worth committing to.

Do NOT use this doc for daily decisions — for that, use: - docs/STRATEGY_PLAYBOOK.md §1 (current verdict) + §2 (Action Queue) - The dashboard 🎯 Action Queue tab - docs/FINDINGS.md (canonical narratives, per memory §J)

Do use this doc when: - Onboarding to the strategy-analysis surface area for the first time - Asking "do we have the data to answer this?" before designing a new study - Deciding whether to keep / archive / rewrite an existing analysis script - Sequencing the post-Phase-0 work (Phases 1–5 below)

Read order

  1. §0.1 — the four layers (skim to orient)
  2. §0.2 — assumption status table (the single most useful section)
  3. §0.4 — bug regressions (these block several analyses today)
  4. §0.5 — data gap matrix (what we can/cannot answer)
  5. §0.6 — decision-usefulness verdict (is unified simulation worth building?)
  6. §0.3 — doc consolidation map
  7. §0.7 — phased roadmap

0.0 Why this doc exists (framing)

Strategy analysis on this bot lives across 22 dashboard widgets, ~25 scripts, 8 production tables, and a dozen docs. Each surface answers a different question; none is canonical. Several quietly contradict each other (see §0.3 doc inconsistencies). Several are silently broken by data-integrity bugs (see §0.4). The fragmentation is why "I'm not sure if Universe View is a trade simulator" is a reasonable question — it isn't, but neither is the Finding-A simulator on its own, and there's no single canonical engine.

This doc is the architectural baseline before any unification work. Phases 1–5 in §0.7 build on top of it; each phase has its own approval gate so no implementation direction is committed prematurely.


0.1 The four layers

0.1.1 Layer 0 — Operative rules and original assumptions

These docs define what the bot is supposed to do. They predate most of the live trading data and contain assumptions that have not all been re-validated against the 116-trade live history.

Doc Role Contains Status
docs/STRATEGY.md Operative spec §1 Q3 2025 Gamma metrics + 70% decision tree · §2 LONG 70-90% / SHORT ≤30% / 90%+ skip / $1k volume gate (Finding #311, was $2k) · §3 0-8 priority score (Finding #313: base 0-6 + volume bump) · §4 TESTING_PROD + 30/22/15% sizing + 3/4/5% stops + flex · §5 BMO 10:32 / AMC 15:57 + weekend-hold + long-weekend 50% · §6 circuit breakers · §7 execution + reconciliation · §8 leverage A-E rollout · §9 Phase 1 +50.6% / Phase 2 +166.2% projections · §10 Q3 backtest detail Active but contains conflicts (see §0.3)
docs/FINANCIAL_PROJECTIONS_REVISED_87_TRADES.md Mar 9 milestone snapshot #202 before/after, conviction-bucket P&L at trade 87, projected vs actual targets Historical — superseded for forward queue by Playbook
docs/FINANCIAL_PROJECTIONS.md Pre-live model Original Phase 1/2 ROI, Monte Carlo at 50% sizing (now 100%) Superseded — should move to archive
docs/POSITION_SIZING_ANALYSIS.md Jan 11 batch flow Old 25/18/10% sizing, fractional shares narrative (now whole shares) Superseded by #211
docs/BMO_AMC_TIMING_DEEP_DIVE.md Feb 15 cohort backtest BMO/AMC decision matrix using old 2/3/4% stops Stale stops — recommendation: re-run with 3/4/5% or label as historical-only
docs/SYSTEM_REVIEW_2026_01_06.md Pre-trading "all green" References old 25/18/10 + 2/3/4% stops, 0 trades in DB Pure stale artifact — should move to archive
docs/EDGE_CASES.md Operative edge cases AMC 3:57 buffer, holiday calendar, 3+ day weekend 50% (conflict RESOLVED → canonical 50%, #248), #228 stop policy Active — long-weekend sizing reconciled to STRATEGY.md §5.2.1 50% (#248)
docs/HOLISTIC_REVIEW_PROMPT.md Review checklist References stale 3-4% stops and $2.5k capital Stale — update or archive
docs/PAPER_TRADING_TEST_PLAN.md Pre-live test plan Says "STOP" not "STP-LMT" (pre-#179) Stale — update or archive
docs/archive/EARNINGS_STRATEGY_COMPLETE.md, docs/archive/stocks_earnings/EARNINGS_TRADING_STRATEGY.md, docs/archive/stocks_earnings/FINANCIAL_ASSUMPTIONS_REVIEW_v2.md v1.0 / v2.x master narratives Old 70% cap, 90% AMC tag, no STP-LMT, no volume threshold Already archived; cross-reference only

Original-assumption surfaces that have NOT been re-validated against live 116-trade data:

0.1.2 Layer 1 — Forward-looking source of truth

These docs are the operational source for what to ship next and what we believe is true given live data. Per memory §J + §N these are canonical for strategy improvements; per §K they are mirrored across build_action_queue() + dashboard + Playbook §2.

Doc Role Status
docs/STRATEGY_PLAYBOOK.md Forward SoT — verdict, Action Queue, verified rules, retracted rules, gauntlet, implementation traces, deferred questions (per memory §N) Active SoT
docs/STRATEGY_AUDIT_v3.md Audit trail — every hypothesis tested + bias gauntlet pass/fail + 5 sections of post-audit work (§10 Apr 25 retraction, §11–§13 universe + backfill, §14 Apr 25 LATE re-validation, §15 deferred) Active audit reference
docs/FINDINGS.md Canonical narrative for every numbered finding (#1–#233+) per memory §J Active SoT for finding text
.cursor/memory.md Process discipline — §K (4-check + 5-outlier + true-simulator), §N (forward-plan routing), §M (cross-interface gates) Active process SoT
docs/AGENT_HANDOFF.md Onboarding index + §11 current state snapshot Active onboarding
docs/STRATEGY_IMPROVEMENTS.md Already-deprecated redirect Archive candidate

Key discipline rules baked into Layer 1:

0.1.3 Layer 2 — Production data tables

The ground truth for every live and counterfactual analysis. NULL rates and row counts pulled from prod on Apr 26, 2026. **(Snapshot superseded — see

321: actual_beat coverage is now ~95-99.8%, and the conviction_at_exit

/ regime_at_entry write-path bugs were fixed #234-#236. The Apr-26 counts below are retained as the point-in-time audit baseline.)**

Table Rows Captures Key analytical fields NULL rates / known issues
trade_audit_log 117 (116 closed) Every executed equity trade realized_pnl, exit_reason, prediction_correct, regime_at_entry, vix_at_entry, mcap_tier, conviction_at_entry, stop_price, hold_duration_hours stop_triggered 2/116 true vs exit_reason ILIKE '%stop%' 57/116 (Finding #233 — broken). conviction_at_exit 116/116 NULL (column exists, write path missing — see §0.4). sp500_change_at_entry 116/116 NULL. spread_at_entry 116/116 NULL. regime_at_entry 5/116 NULL (regression risk: orchestrator INSERT path doesn't set it — see §0.4). vix_at_entry 7/116 NULL. mcap_tier 4/116 NULL. volume_24h_at_entry 75/116 NULL.
trading_positions 117 Operational position state mirrored by bot entry_conviction, final_conviction, actual_earnings_time, instant_exit_triggered, prediction_wrong final_conviction 75/116 populated. Distinct from trade_audit_log; for "conviction at exit" use this OR conviction_history.
earnings_markets 900 One Polymarket earnings market prob_24h_before (and 12h/6h/1h/30min), actual_beat, eps_actual, stock_return_day_close, stock_price_day_before_close, market_cap_category, earnings_date, earnings_time prob_24h_before 699/900 (78%). actual_beat 528/900 (59%) (superseded — see #321; now ~95-99.8%). eps_actual 210/900 (23%). stock_return_day_close 713/900 (79%). 223 past markets unresolved (earnings_date < CURRENT_DATE - 2 AND actual_beat IS NULL).
markets 5,706 Polymarket market metadata (Gamma sync) volume_total, volume_24h, condition_id, winning_outcome, status Volume eventually consistent (~15 min lag); volume_total was silently 0 on resolved markets pre-Finding #229.
price_snapshots ~395k rows last 7 days; 5.68 GB hypertable TimescaleDB time series of Polymarket YES/NO prices + volume + bid/ask time, condition_id, yes_price, no_price, volume_24h, best_bid, best_ask 1-year retention. Coverage uneven by condition_id — only markets in collector's active set get dense series.
conviction_history ~65k Sample of Polymarket conviction over time per position/market conviction, recorded_at, volume_24h, spread, bid_depth, ask_depth Sparse by design. NOT 1:1 with trade lifecycle.
polymarket_opportunity_log 1,064 Every signal evaluation (traded + skipped) evaluated_at, skip_stage, skip_reason, conviction, direction_would_be, volume_total, priority_score, intended_position_value, intended_stop_price, regime_at_entry, vix_at_entry, sp500_change_at_entry, hypothetical_pnl, hypothetical_stop_hit, snapshot (JSONB) hypothetical_pnl 416/1064 (39%) despite counterfactual_computed_at set on all. Regime/VIX/SP500 544/1064 (51%) — pre-#227 rows lack them.
skipped_signals 395 Signals not traded with optional counterfactual P&L skip_reason, threshold_value, signal_time, intended_*, regime_at_entry, vix_at_entry, hypothetical_pnl, would_have_won counterfactual_computed_at 314/395 (79%); hypothetical_pnl 309/395 (78%). regime_at_entry 82/395 (21%) — bulk of skips predate #227 capture.

Cross-table joins for analysis:

flowchart LR
  TAL[trade_audit_log<br/>117 rows] -->|condition_id| EM[earnings_markets<br/>900 rows]
  EM -->|condition_id| MK[markets<br/>5706 rows]
  EM -->|condition_id| PS[price_snapshots<br/>Timescale]
  PMOL[polymarket_opportunity_log<br/>1064 rows] -->|condition_id| EM
  SS[skipped_signals<br/>395 rows] -->|condition_id| EM
  CH[conviction_history<br/>65k rows] -->|condition_id| EM
  CH -.position_id.-> TP[trading_positions<br/>117 rows]
  TAL -.trade_id / shared keys.-> TP

Critical data NOT in our DB:

Gap What we lose Cost to fix
Equity 1m bars Cannot reconstruct intraday price path on past markets without re-fetching Polygon every analysis run. analyze_finding_a_full.py does this ad-hoc; ~600 markets × ~5 days × ~1140 bars/day ≈ 3.4M rows/quarter ≈ ~500MB/year if cached. Phase 2 — new TimescaleDB hypertable + collector
Press-release exact timestamps For non-traded markets actual_earnings_time often NULL → simulator falls back to BMO/AMC defaults (07:30 / 16:15 ET), losing ±90 min release-window precision Phase 2 — extend resolver to capture from SEC EDGAR + Yahoo + Finnhub
Pre-market / after-market equity volume Cannot detect when extended-hours liquidity is too thin for a stop to fill cleanly Phase 2 — collector pulls Polygon agg + bars
L2 equity book over time Cannot model true slippage at fill Out of scope (vendor cost)
Counterfactual P&L on all opportunity_log rows 39% complete today → 61% of skipped/non-traded signals lack a hypothetical Phase 1 — re-run compute_counterfactual_pnl.py over the 648 missing rows

0.1.4 Layer 3 — Analysis scripts

Scripts that read Layer 2 + compute analytical artifacts. Producing files flow into Layer 4 (dashboard) or stdout. Heavy fragmentation here — multiple scripts for similar questions, several stale.

Master orchestrator (regenerates scripts/strategy_analysis_report.json every run; consumed by dashboard):

scripts/strategy_performance_analysis.py:run_analysis() runs ~20 sub-functions: - calculate_metrics → clean_trades_metrics / all_trades_metrics - compare_with_projections → projected_vs_actual - analyze_ibkr_constraints → ibkr_constraints - analyze_bug_impact → bug_impact - analyze_direction_timing_combo → direction_timing_combo - analyze_stop_losses → stop_loss_analysis - analyze_stop_reversals → stop_reversal_analysis (uses cached stop_reversal_actual_prices.json) - compute_position_sizing_quintiles → position_sizing_quintiles - analyze_skipped_cost → skipped_signal_cost (reads polymarket_opportunity_log) - analyze_volume_gate_counterfactual → volume_gate_counterfactual (reads skipped_signals) - analyze_performance_by_regime_vix → regime_vix_performance - analyze_finding_a_simulator → finding_a_simulator (reads finding_a_full.csv, does NOT re-simulate) - build_action_queue → action_queue (single-source for queue) - analyze_universe_hit_rate → universe_hit_rate (the largest section) - four_check_gauntlet_doc → bias_gauntlet - analyze_top_trades, analyze_prediction_accuracy, analyze_weekly_performance, compute_hold_durations, generate_cumulative_pnl, check_red_flags, estimate_roi

Simulators (true counterfactual replay):

Script Purpose Population Source of truth
scripts/analyze_finding_a_full.py 1m Polygon bar replay of Finding A + Rules B/D/G/F on each closed trade. Models gap-through, halt detection, earnings_detector cap. Default 116 closed trades; can narrow with --population stopped-only Active — re-run for any new stop-loss rule
scripts/compute_counterfactual_pnl.py Backfills hypothetical_pnl on polymarket_opportunity_log rows using counterfactual_pricing.py (Polygon 1m or yfinance daily fallback) Pending rows in opp log Active but only 39% of rows have hypothetical_pnl populated — should be re-run regularly

Backtest scripts (archived data, not live):

Script Purpose Population Status
scripts/strategy_backtest_constrained.py Re-run Q3 2025 cohort with live-like constraints 370 archived markets Active — feeds dashboard backtest block
scripts/strategy_backtest_bmo_amc.py BMO vs AMC cohort + decision matrix Q3 2025 cohort Active — feeds dashboard bmo_amc_timing block
scripts/strategy_entry_timing_bmo_amc.py Entry-timing proxy (24h vs 1h prob) Q3 2025 cohort Research, NOT in dashboard — outputs a file no one reads
scripts/earnings_timing_5y_api_study.py + earnings_timing_5y_hourly_polygon.py 5y EV grid from Alpha Vantage / Polygon S&P 500 Research — separate ecosystem

Legacy / one-off (kept for reference, not actively run):

Script Replaced by / status
scripts/analyze_stop_counterfactual.py v2 audit. Superseded by analyze_finding_a_full.py
scripts/analyze_atr_prescreen.py One-off — killed v2 H-rule. Reference only
scripts/deep_strategy_analysis.py Reads static trade_data_full.json. Ad-hoc
scripts/stop_reversal_analysis.py Reads static JSON. Superseded by analyze_stop_reversals in master
scripts/stop_loss_deep_dive.py Reads static JSON. Ad-hoc
scripts/pull_trade_data.py Exports JSON for the 3 above. Ad-hoc pipeline
scripts/fetch_scheduled_exit_prices.py Caches Polygon prices into stop_reversal_actual_prices.json for "if held" math. Tied to a fixed trade set; must be re-run manually to refresh
scripts/position_sizing_analysis.py Upcoming-2-weeks sizing report. Not in dashboard, ad-hoc
scripts/hl_earnings_analysis.py Hyperliquid comparison. Separate asset class
scripts/leverage_live_validation_autorun.py Leverage rollout validator. Operational, not strategy alpha
scripts/backfill_prediction_correct.py Hardcoded symbol→beat map for legacy rows. Deprecated per Finding entry; resolver supersedes

Backfill / ETL (feeds analysis tables):

Script Output Used by
scripts/backfill_learning_loop.py Fills resolver/counterfactual fields on opportunity_log + skipped_signals + earnings_markets + trade_audit_log All Layer 3 + dashboard
scripts/backfill_regime_for_audit_log.py Fills regime_at_entry + VIX + SPY change on historical trades regime_vix_performance
scripts/backfill_markets_volume.py Recovers markets.volume_total for resolved markets (Finding #229) Universe view
scripts/backfill_conviction_from_trades.py Reconstructs prob_*_before from Polymarket data-api /trades (Finding #230) Universe view

src/earnings/ modules contributing to analysis:

Module Role
src/earnings/counterfactual_pricing.py Polygon 1m + yfinance fallback; NOTIONAL_USD = 10_000; fetch_polygon_minute_bars shared helper
src/earnings/market_regime_provider.py SSoT for live regime + VIX + SPY change; IBKR primary, yfinance fallback, 60s cache
src/earnings/volume_policy.py effective_cumulative_volume_usd = max(DB, Gamma) (Finding #282 — Apr 30, 2026; previously "DB else Gamma" per Findings #222-#226 but stale-DB-low silently rejected tradeable markets)
src/data_tracker/collectors/outcome_resolver.py Daily fill of actual_beat, eps_actual, stock_price_*, prediction_correct
src/data_tracker/collectors/conviction_tracker.py Hourly conviction sampling + --backfill mode

0.1.5 Layer 4 — Dashboard sections (Strategy Analysis tab)

Single endpoint /api/strategy-analysis returns the full report (the report JSON IS the schema). The Strategy tab is rendered by renderStrategyAnalysis in earnings_dashboard/templates/dashboard.html ~line 11171, which dispatches to ~24 sub-renderers.

Twenty-three sections in tab order:

# Section JSON key Producer Notes
1 "How to read this tab" (static) n/a static copy Explains top-to-bottom trust order — but DOM has Bias Gauntlet before Action Queue while copy says opposite
2 🛡️ Bias Gauntlet analysis.bias_gauntlet four_check_gauntlet_doc() OK
3 🎯 Action Queue analysis.action_queue build_action_queue() Single SoT for what to ship
4 🔬 Finding-A Simulator analysis.finding_a_simulator analyze_finding_a_simulator() (reads finding_a_full.csv) Aggregates pre-computed CSV; does not re-simulate in Python
5 🌐 Universe View analysis.universe_hit_rate analyze_universe_hit_rate() Largest single section; volume-aware mcap splits, Finding C drift, PM accuracy
6 "Implications for the Action Queue" (static) n/a static copy Narrative duplicates queue history
7 Market Regime card data.current_regime (root, NOT under analysis) _get_quick_regime() in app.py + regime_cache from report Different object from §11 below
8 Performance vs Projections analysis.projected_vs_actual compare_with_projections() Targets are PROJECTIONS constant baked into script
9 Key Performance Metrics analysis.clean_trades_metrics + analysis.roi_estimate calculate_metrics, estimate_roi OK
10 Position Sizing Quintiles analysis.position_sizing_quintiles compute_position_sizing_quintiles Finding #211; OK
11 Learning Loop (#227) analysis.skipped_signal_cost + analysis.volume_gate_counterfactual + analysis.regime_vix_performance analyze_skipped_cost, analyze_volume_gate_counterfactual, analyze_performance_by_regime_vix Different "regime" object from §7 card — operator may confuse
12 Bug Impact Summary analysis.bug_impact analyze_bug_impact OK
13 Live Performance Snapshot (3 cards) analysis.clean_trades_metrics + analysis.stop_loss_analysis calculate_metrics + analyze_stop_losses Duplicates slices of breakdowns + scorecard
14 Direction + Timing Matrix analysis.direction_timing_combo analyze_direction_timing_combo Duplicates parts of §13
15 Stop Loss Analysis analysis.stop_loss_analysis analyze_stop_losses Aggregates only, not counterfactual — operator should prefer §4 simulator
16 Stop Reversal Analysis analysis.stop_reversal_analysis analyze_stop_reversals Wider-stop sub-table parent is display: none, but JS still fills tbody (dead UI work)
17 Top Trades analysis.top_trades analyze_top_trades OK
18 Prediction Accuracy analysis.prediction_accuracy analyze_prediction_accuracy Second PM-accuracy panel — universe view also has one (pm_prediction_accuracy); operator may confuse live-trade vs universe
19 Performance Breakdowns (5 sub-views) analysis.clean_trades_metrics (by_*) calculate_metrics Same metrics shown 3 ways across §13/§14/§19
20 Backtest Scenario Comparison data.backtest.scenarios (root) scripts/strategy_backtest_constrained.py (separate JSON) Q3 2025 archived; explicitly historical, not predictive
21 BMO vs AMC Timing Matrix data.bmo_amc_timing (root) scripts/strategy_backtest_bmo_amc.py Same Q3 2025 archive base + cohort
22 IBKR Constraints Impact analysis.ibkr_constraints analyze_ibkr_constraints OK
23 Statistical Confidence analysis.statistical_confidence inline in run_analysis OK

Dead / hidden / broken UI:

API:


0.2 Documented assumption status table

The single most useful section of this doc. Every documented strategy assumption, where it lives, when it was last validated, and what to do.

Validation legend: - ✅ validated — live data confirms within current sample - ⚠️ unvalidated — no live test against current data - 🛑 superseded — replaced by a numbered Finding - ❌ stale — contradicted by live data, no formal retraction - 🔁 conflict — two docs disagree

# Assumption Source Status Notes
1 LONG conviction threshold = 70% STRATEGY.md §2.1 ⚠️ unvalidated at threshold edge Universe view uses 0.60 as LONG floor; bot uses 0.70 at signal time. Cross-doc drift; not a bug per se but worth documenting
2 SHORT conviction threshold = 30% STRATEGY.md §2.3 ⚠️ unvalidated Universe uses 0.30; aligned. Live SHORT n=18 below gauntlet floor
3 Skip all LONGs at conviction ≥ 90% STRATEGY.md §2 + Finding #202 ✅ validated Live + universe both confirm
4 Mid-cap LONG ≥ 80% skip (queue v3 D) Finding #232 ⏸️ ON-HOLD pending RuleB See Playbook §2
5 Position sizing: Large 30% / Mid 22% / Small 15% STRATEGY.md §4 + Finding #211 ✅ validated Current TESTING_PROD profile
6 Stop distance: Large 3% / Mid 4% / Small 5% STRATEGY.md §4.1 + Finding #202 ✅ validated Mar 9 widened from 2/2/3%
7 All stops are STP-LMT with 2% buffer STRATEGY.md §5.4 + Finding #179 ✅ validated Hardware carries outsideRth=True; this is why Finding A retraction was forced
8 BMO entry 15:58 ET prior trading day STRATEGY.md §5.1 ✅ validated Operative
9 BMO exit 10:32 AM ET earnings day STRATEGY.md §5.1 ✅ validated Feb 5 timing analysis; Playbook INFO row says "near-optimal"
10 AMC entry 15:34 ET earnings day STRATEGY.md §5.1 ✅ validated Operative
11 AMC exit 15:57 ET next trading day STRATEGY.md §5.1 ⚠️ unvalidated against current live data Playbook INFO row notes AMC is ~0% EV at all horizons; rule kept "least bad"
12 $1K cumulative volume gate (Finding #311; was $2K) STRATEGY.md §2.4 + Findings #222–#226 + #311 ✅ validated Production gate lowered $2k → $1k per #311 (volume-collapse retune); the Apr-26 sweep had $2K near-optimal before the collapse
13 Effective volume = max(DB volume_total, Gamma cumulative) Findings #224 + #282 (Apr 30, 2026) ✅ validated Single shared module volume_policy.py; trader path also goes through helper (no short-circuit per Finding #282 §M consolidation)
14 Weekend-hold "trade with full exposure" STRATEGY.md §5.2 ⚠️ unvalidated Note in doc says "quarterly validation"; not formally re-validated
15 Long-weekend (3+ day) sizing = 50% STRATEGY.md §5.2.1 + Finding #248 ✅ validated RESOLVED Apr 26 (#248): EDGE_CASES.md Cat-2.1 reconciled to 50% (canonical)
16 Trade-count Phase 1→2 transition at 100 trades STRATEGY.md §4 (one bullet) 🔁 conflict Same doc §4 (other bullet) says 150 trades
17 Daily 3-stop session breaker (queue #3) STRATEGY_PLAYBOOK.md §2 ✅ validated (8 historical sessions per Finding #232) Was previously believed defensive-only
18 Skip LONGs in downtrend regime (queue #1) v3 audit Finding B + Finding #232 ✅ validated Bias check stronger after Apr 25 re-validation
19 RuleB ±90 min no-stop window (queue #2) Finding #228 simulator ✅ validated +$123/100 across 51 stops
20 BMO + VIX-bucket sizing (1.2/0.6/1.0×) v3 Finding F 🛑 retracted Apr 25 (Finding #232) 1.2× scale-up arm fails ex-top-3
21 Block all stops outside RTH (Finding A) v3 audit 🛑 retracted Apr 25 (Finding #228) FA_soft inert; FA_hard −$418/100
22 Drift filter: skip LONGs at drift ≥ 2% v3 Finding C 🛑 retracted Apr 25 evening Universe view weakened bias check
23 Phase 1 capital $25K → +50.6% projected ROI STRATEGY.md §9 + FINANCIAL_PROJECTIONS.md ❌ stale Live run-rate is +$1.44/trade (~+21%/year on current capital). §9 is model baseline, not expected return
24 Phase 2 capital $110K → +166.2% projected ROI STRATEGY.md §9 ❌ stale Same as #23
25 Original Q3 2025 conviction-bucket stats STRATEGY.md §10 ⚠️ partially unvalidated v2/v3 audits retracted several; many remain reference-only
26 "70-80% LONG is the best bucket" priority weight STRATEGY.md §3 (priority score) 🛑 retracted v3 audit Was 82% SWBI-driven
27 Leverage = 10× STRATEGY.md §8 🛑 superseded IBKR portfolio margin actually ~4×; corrected in code; doc still mentions 10×
28 Order type = "STOP" PAPER_TRADING_TEST_PLAN.md 🛑 superseded by #179 Should update or archive doc
29 Old sizing 25/18/10% POSITION_SIZING_ANALYSIS.md, SYSTEM_REVIEW_2026_01_06.md, BMO_AMC_TIMING_DEEP_DIVE.md 🛑 superseded by #211 Multiple stale docs; consolidate or archive
30 Old stops 2/3/4% Same docs as #29 🛑 superseded by #202 Same
31 Pre-live $2.5k capital reference in review checklist HOLISTIC_REVIEW_PROMPT.md ❌ stale Update with current ~$2.4k live, ~$27k Net Liq
32 Earnings_time DB rule (NASDAQ-explicit BMO/AMC + DEFAULT_TIMINGS only) STRATEGY.md §7.8 + Finding #220 ✅ validated Operative
33 MCAP DB-first enrichment Finding #219 ✅ validated Operative
34 Earnings_detector instant exit on wrong prediction STRATEGY.md §6.2 ✅ validated 82% / ~0.5% type stats
35 Single-source-of-truth rule (live tables canonical) Audit_v3 §13.6 ✅ validated process rule Memory §K
36 True-simulator rule (Finding #228 lesson) Memory §K ✅ validated process rule Added Apr 25
37 4-check bias gauntlet Memory §K + Audit_v3 §1 ✅ validated process rule Apr 25 H-rule retraction validated the gauntlet
38 5-outlier rule (GPRO+DELL+SWBI+TXN+STUB) Memory §K + Finding #232 ✅ validated diagnostic Renamed Apr 25 from 3-outlier

Summary count: 15 ✅ · 5 ⚠️ unvalidated · 11 🛑 superseded · 4 ❌ stale · 1 🔁 conflict (row 15 long-weekend resolved per #248)


0.3 Doc consolidation map

What to do with each strategy-analysis doc as Phase 1 work.

Doc Recommended action Justification
docs/STRATEGY.md Keep + fix conflicts in Phase 1 Operative spec. Fix items #15, #16, #27 in §0.2 table
docs/STRATEGY_PLAYBOOK.md Keep as primary forward SoT Per memory §N
docs/STRATEGY_AUDIT_v3.md Keep as audit trail Per memory §N
docs/FINDINGS.md Keep as canonical narrative Per memory §J
docs/AGENT_HANDOFF.md Keep + refresh §11 after every major analysis Onboarding index
.cursor/memory.md Keep as process SoT §K + §N + §M
docs/EDGE_CASES.md Keep + fix conflict with STRATEGY §5.2.1 long-weekend sizing Operative edge-case reference
docs/FINANCIAL_PROJECTIONS_REVISED_87_TRADES.md Keep + add stale-warning header "Historical milestone — superseded for forward queue by Playbook" Useful as point-in-time snapshot
docs/FINANCIAL_PROJECTIONS.md Move to docs/archive/ Pre-live (50% sizing), fully superseded
docs/POSITION_SIZING_ANALYSIS.md Move to docs/archive/ Pre-#211 sizing, fractional shares story stale
docs/BMO_AMC_TIMING_DEEP_DIVE.md Add stale-stops warning header OR re-run with current 3/4/5% stops Decision matrix uses old 2/3/4%
docs/SYSTEM_REVIEW_2026_01_06.md Move to docs/archive/ Pre-trading "all green" with old 25/18/10 + 2/3/4% stops
docs/STRATEGY_IMPROVEMENTS.md Move to docs/archive/ Already deprecated redirect
docs/HOLISTIC_REVIEW_PROMPT.md Update or archive Stale stop % + capital references
docs/PAPER_TRADING_TEST_PLAN.md Update or archive Says "STOP" not "STP-LMT"
docs/OPS_TIMING_VERIFICATION.md Keep Active ops verification
docs/DATA_INTEGRITY.md Keep Pipeline / gap-detection design
docs/DATA_ARCHITECTURE.md Keep Schema reference
docs/CONTEXT.md Keep + refresh Onboarding context
docs/OPERATIONS.md Keep Ops runbook
docs/IBKR_CONTINUITY.md, IBKR_SETUP.md, STATE_MACHINES.md, POSTGRES_OPERATIONS.md, API_REFERENCE.md, DATABASE_DIRECTIVES.md, BMO_AMC_TIMING_DEEP_DIVE.md, HYPERLIQUID_API.md Keep Operational/reference; not in scope of strategy analysis revamp
docs/AUDIT_FIX_QUEUE.md, docs/TESTING_FINDINGS_PENDING.md Already deprecated redirects per memory §J Move to archive in Phase 1
docs/archive/* Leave Cross-reference only

Net Phase 1 doc actions: 4 archives, 4 stale-warning headers, 3 active fixes (STRATEGY conflicts #15/#16/#27, EDGE_CASES sizing, HOLISTIC_REVIEW_PROMPT refresh), 0 rewrites.


0.4 Bug regressions discovered during this audit

Five concrete bugs surfaced during Phase 0 mapping. Listed in fix-priority order. None require trade-execution review (memory §J on prohibition does not apply — these are dashboard / persistence / write-path fixes).

Bug #1 — Dashboard renderRecommendations ReferenceError on Strategy tab load

Bug #2 — TradingOrchestrator._persist_position_open omits regime/VIX/SP500

Bug #3 — trade_audit_log.conviction_at_exit never set on close path

Bug #4 — trade_audit_log.sp500_change_at_entry never populated

Bug #5 — trade_audit_log.stop_triggered undercounts by 28× (Finding #233 known)

Bug #6 — polymarket_opportunity_log.hypothetical_pnl only 39% populated

Summary (resolution status added Apr 26, 2026 after Phase 1):

Bug Priority Effort Status (Apr 26) Finding #
#1 Dashboard ReferenceError HIGH trivial ✅ FIXED #235
#2 Orchestrator missing regime/VIX HIGH small ✅ FIXED + backfilled (regime 100%, VIX 98%) #234
#3 conviction_at_exit never set MEDIUM small ✅ FIXED (forward-only — cannot reconstruct historical) #237
#4 sp500_change_at_entry never set LOW medium ✅ FORMALIZED (forward-only post-Bug-#2 fix; historical formally NULL) (folded into #234)
#5 stop_triggered broken MEDIUM medium ✅ FIXED + 55 historical rows backfilled (writer derives from exit_reason) #236
#6 hypothetical_pnl 39% MEDIUM small ✅ TRIAGED (61% legitimately non-simulatable; weekly self-heal added) #238

0.5 Data gap matrix (decision-usefulness inputs)

For each major analytical question we might ask, do we have the data to answer it reliably today?

Question trade_audit_log trading_positions opportunity_log skipped_signals earnings_markets price_snapshots conviction_history external (Polygon) Verdict
Win rate / P&L on executed trades ✅ canonical partial n/a n/a n/a n/a n/a n/a ✅ ready
Stop hit attribution on executed trades ✅ via exit_reason (NOT stop_triggered) n/a n/a n/a n/a n/a n/a n/a ✅ ready (Finding #233 workaround)
Realistic slippage / commission ✅ recorded n/a n/a n/a n/a n/a n/a n/a ✅ ready
Direction signal quality (Polymarket conviction → actual beat) partial partial n/a n/a ✅ at universe scale (78% prob, 59% beat) n/a n/a n/a ✅ ready (universe view); ⚠️ live n=116 still small
Regime/VIX at entry × performance (executed trades) ✅ Bug #2 FIXED Apr 26 (#234); regime 116/116, VIX ~98% n/a n/a n/a n/a n/a n/a n/a ✅ ready (Bug #2 fixed #234; no longer a blocker)
SP500 intraday change at entry × performance Bug #4 FORMALIZED Apr 26 (forward-only, folded into #234); historical formally NULL n/a partial 51% partial n/a n/a n/a n/a ✅ forward-capture fixed (#234); use opportunity_log for historical
Conviction-at-exit drop as stop predictor (H8) Bug #3 FIXED Apr 26 (#237, forward-only); historical un-reconstructable partial 75/116 final_conviction n/a n/a n/a n/a sparse n/a ✅ forward-capture fixed (#237); historical remains NULL
Counterfactual P&L on skipped signals n/a n/a ⚠️ 39% complete (Bug #6) ⚠️ 78% complete n/a n/a n/a needed for backfill ⚠️ partial; needs Bug #6 fix
Counterfactual P&L on non-evaluated Polymarket markets (universe minus 116 traded minus opp log signals) n/a n/a n/a n/a partial — no entry/stop logic modeled n/a n/a ❌ needs Phase 3 simulator ❌ not possible today
1m intraday equity path on past markets n/a n/a n/a n/a n/a n/a n/a ✅ if Polygon fetched ad-hoc; ❌ not stored locally ⚠️ slow / Polygon-dependent today
Press-release exact timestamp for non-traded markets n/a n/a n/a n/a partial n/a n/a needed ⚠️ partial; some via resolver
Pre-market / after-market equity volume profile n/a n/a n/a n/a n/a n/a n/a ❌ not collected ❌ not possible today
L2 equity book over time n/a n/a n/a n/a n/a n/a n/a ❌ not collected (vendor cost) ❌ permanent gap (out of scope)
Stop-loss rule counterfactual (FA, RuleB, RuleD, etc.) on executed stops n/a n/a n/a n/a n/a n/a n/a ✅ via analyze_finding_a_full.py ✅ ready
Stop-loss rule counterfactual on all in-band markets (traded + skipped + unevaluated) n/a n/a n/a n/a partial n/a n/a ⚠️ needs Phase 2 cache + Phase 3 simulator ❌ not possible today
Polymarket conviction path (24h → 30min before) n/a n/a n/a n/a ✅ post-#230 backfill (78%) ✅ where condition_id is in collector set ✅ when sampled n/a ✅ ready at universe scale
Q3 2025 cohort backtest comparison n/a n/a n/a n/a n/a n/a n/a ✅ separate JSON (backtest_results_constrained.json) ✅ ready (historical, not predictive)

Summary:


0.6 Decision-usefulness verdict (extended)

Direct answer to the question: "is the outcome of having a unified simulator especially for past data going to be relevant enough for us to take decisions on trading strategy based on it?"

Headline: Conditional YES — useful as one of three triangulation

inputs (live data + simulator-on-traded + simulator-on-universe), NOT as a replacement for live data. (Update: Phase 1 + Phase 2 long since landed; Phase 3 PR 1-5 + Phase 4 COMPLETE as of May 31, 2026, #306-#322. The "not before Phase 1 + Phase 2 land" precondition is satisfied.)

When unified simulation IS decision-useful

  1. Sample-size triangulation. Live n=116 trades × ~3.5x outlier concentration in the 5-outlier set (memory §K) means most live findings sit at the bias gauntlet's n≥12 floor. A simulator running on the 488 in-band markets the bot didn't trade gives ~5x more data points for the same direction/conviction/regime questions. When simulator confirms a live finding at higher n, confidence rises significantly.
  2. Ex-bias diagnostics. The 5-outlier rule (GPRO + DELL + SWBI + TXN
  3. STUB carrying ~$430 of $167 net P&L) is what makes live-data slices so unstable. Universe simulation effectively dilutes those 5 outliers across 600+ markets, surfacing the underlying signal cleanly. The live v3 audit retracted 6 hypotheses purely via the gauntlet; the simulator would have caught most of them earlier.
  4. Counterfactual on skipped trades. The 488 markets the bot skipped today have NO stop-modeled counterfactual (just universe-view day-close return, no timing, no stops). A simulator that replays the bot's actual entry/exit/stop logic on those markets answers "what would the bot have made if it had traded these signals?" — currently un-answerable.
  5. Regime / VIX / mcap conditioning at scale. Live n in regime sub-buckets is single digits (3 SHORT downtrends, 6 sideways SHORTs); universe-scale equivalents would be 20–50x larger.
  6. Hypothesis pre-screening. Future audits could test 50 candidate rules in the simulator before deciding which 1–2 to validate against live data, avoiding the v2-style "ship a rule that destroys live alpha because the bias gauntlet wasn't applied".

When unified simulation is NOT decision-useful

  1. Treating it as the optimizer. The simulator is a STATIC replay of the bot's logic. If we use it to find "the rule that made the most money in past data" we will overfit the 600-market window — exactly what the H-rule did at v2. The 4-check gauntlet must apply to simulator results just like live results.
  2. Trusting it on assumptions we can't verify. Real Polymarket fill mechanics (would the bot's 3:58 ET market-buy actually fill at the recorded mid?), real IBKR halt detection (does Polygon's halt-suspect heuristic match what IBKR actually does?), real capital constraints (would the bot have skipped this trade because cash was tied up in 3 other positions?) — the simulator cannot verify these.
  3. Building it before Phase 1 + Phase 2. Garbage in, garbage out:
  4. Bug #2 (orchestrator missing regime/VIX) means the simulator's regime conditioning is biased toward older trades.
  5. Bug #6 (39% hypothetical_pnl) means the counterfactual layer is only partially populated.
  6. Equity 1m bars not stored means every simulator run is rate-limited by Polygon (~50 req / 5s).
  7. Press-release timestamps missing on most non-traded markets means the simulator falls back to BMO/AMC defaults too often. Fix Phase 1 + Phase 2 first; THEN the simulator's outputs are trustworthy enough to triangulate against live data.
  8. Relying on it for live decisions in real time. The bot already has live signal gates and counterfactual capture in the opportunity_log. The simulator is for post-hoc strategy validation, not real-time gating.

Concrete decision criteria for shipping Phase 3 (Unified Simulator)

⚠️ SUPERSEDED — gates MET / Phase 3 shipped. All four criteria below were verified met by May 28, 2026 (see docs/PHASE_3_SIMULATOR_DESIGN.md §3; Gate 1/4 corrected per #308 structural ceiling). The 59% actual_beat and 75% hyp_pnl figures cited below are the Apr-26 baseline, retained for history. (Phase 3 PR 1-5 + Phase 4 COMPLETE as of May 31, 2026, #306-#322.)

Phase 3 should ONLY proceed if:

  1. ✅ Phase 1 bugs #1, #2, #5, #6 are fixed (Bug #2 is the most important — its regression worsens every day).
  2. ✅ Phase 2 has accumulated at least 30 days of equity 1m bar capture on in-band markets (so the simulator can replay without 600+ Polygon calls per run).
  3. ✅ At least 80% of past in-band markets have actual_beat resolved (currently 528/900 = 59% across all markets, but ~80% within the in-band 604).
  4. ✅ At least 75% of opportunity_log rows have populated hypothetical_pnl (currently 39%).

If any of those four are missing, Phase 3 inputs are too thin to produce gauntlet-passing conclusions. Better to wait than ship a misleading simulator.

If after Phase 2 data is accumulated, the cost of building a full unified simulator looks too high vs. value, the fallback is to extend the existing analyze_finding_a_full.py engine to operate on the 488 markets the bot didn't trade, using the cached 1m bars. That's ~70% of the value of a full simulator with ~30% of the work — same Polygon-replay engine, just expanded population. The "full unified simulator" is then a Phase 4 nice-to-have, not a Phase 3 must-build.


0.7 Phased roadmap (permanent reference)

This section is the durable version of the plan presented for Phase 0 approval. Each phase has explicit gates so future agents don't over-build.

Status note: Phase 3 PR 1-5 + Phase 4 COMPLETE as of May 31, 2026, #306-#322. The diagram below is the original Phase-0 plan; box statuses are annotated COMPLETE. Phase 5 ROI re-baseline SHIPPED May 31, 2026 (Finding #322.d). History: at May 31 morning the remaining item was Phase 5 + defer-and-monitor.

flowchart TB
  P0["Phase 0 ✅ COMPLETE<br/>Architecture Map<br/>this doc"] --> P1["Phase 1 ✅ COMPLETE<br/>Doc consolidation<br/>+ bug-fix sprint"]
  P1 --> P2["Phase 2 ✅ COMPLETE<br/>Forward data capture<br/>1m bars, release timestamps,<br/>complete counterfactuals"]
  P2 -->|30+ days of data| Decision{"Phase 3 gates met?<br/>✅ YES (May 28)"}
  Decision -->|YES| P3["Phase 3 ✅ COMPLETE<br/>Unified simulator<br/>PR 1-5 #306-#319"]
  Decision -->|NO or NOT WORTH IT| P3alt[Phase 3 alt<br/>Extend Finding-A engine<br/>to non-traded markets only]
  P3 --> P4["Phase 4 ✅ COMPLETE<br/>Dashboard simplification<br/>#320"]
  P3alt --> P4
  P4 --> P5["Phase 5 ✅ SHIPPED #322.d<br/>ROI re-baseline (May 31, 2026)<br/>(history: was remaining)"]

May 28, 2026 LATE evening update — Phase 3 simulator PR 1 IS COMPLETE. See docs/PHASE_3_SIMULATOR_DESIGN.md §12 for the concrete architecture, module layout, 5-PR ship plan, bias-gauntlet integration spec, and the (now-resolved) 8 operator-decision points. The Phase 3 gates listed in §0.6 are VERIFIED MET as of May 28, 2026 (see design doc §3; Gate 1 was corrected per #308 structural-ceiling discovery — the signal_emitted 100% subset is sufficient; ~69%/~28% on generate_signal/depth is the achievable ceiling for deadband-conviction rows). All 5 PR 1 commits shipped today:

Commit Finding Description
919c566 #307 snapshot_schema='pm_v2' conditional-label fix (Finding #273 Phase 1 lockstep)
de0d28e #308 Bug #6 hyp_pnl re-run + structural-ceiling discovery
615befa #306.a Phase 3 simulator foundation skeleton
06cae0b #306.b cost_model + replay + reference_adapter + cross_check_baseline
62cb607 #306.c gauntlet + output + runner.py --run-gauntlet + H-rule self-validation + reproducibility

All 4 PR 1 acceptance gates PASS (per design doc §10): - §10.1 cross-check vs analyze_finding_a_full.py = 100% match within $1 on all 7 rules / max drift $0.00 (verified on prod May 28 LATE evening, n=121). - §10.2 reproducibility = 2 runs same input produce byte-identical JSON after masking generated_at (verified on real prod data + test_two_runs_produce_zero_byte_diff unit test). - §10.3 H-rule self-validation = synthetic v2 H-rule returns FAIL-EX-TOP-2 per test_h_rule_synthetic_returns_fail_ex_top_2 (DO-NOT-SHIP-PR-2 diagnostic if it ever flips to PASS). - Phase 0 §0.6 Gate 1/4 corrected per #308 structural ceiling.

PR 2 — Volume-gate retune sweep begins next session (highest-leverage per May-28 PM volume-collapse evidence: Jan→May avg cumulative volume dropped ~109×; $2k-gate pass rate now 1.9%). Then PR 3 (sizing+priority+exposure-cap), PR 4 (regime classifier v2), PR 5 (depth-gate v2). (Superseded — PR 2-5 SHIPPED #310-#319; Phase 3 PR 1-5 + Phase 4 COMPLETE as of May 31, 2026, #306-#322.)

Phase 1 — Doc consolidation + bug-fix sprint

Code fixes (each independently reviewable): 1. Fix Bug #1 (delete renderRecommendations call, dashboard.html:11254) 2. Fix Bug #2 (mirror regime/VIX/SP500 set in TradingOrchestrator._persist_position_open from PositionManager) 3. Fix Bug #3 (set conviction_at_exit on close path, OR document why intentionally NULL) 4. Fix Bug #5 (deferred Finding #233 writer fix + backfill 55 rows) 5. Fix Bug #6 (re-run compute_counterfactual_pnl.py on the 648 missing rows; add to weekly timer) 6. Decide on Bug #4 (formalize NULL or attempt forward-only enrichment)

Doc fixes: 1. Reconcile long-weekend 50% (STRATEGY) vs 75% (EDGE_CASES) — ✅ DONE Apr 26 (#248, canonical 50%) 2. Reconcile STRATEGY 100 vs 150 trade-count transition 3. Update HOLISTIC_REVIEW_PROMPT.md stop % + capital 4. Move 4 deprecated docs to docs/archive/: FINANCIAL_PROJECTIONS.md, POSITION_SIZING_ANALYSIS.md, SYSTEM_REVIEW_2026_01_06.md, STRATEGY_IMPROVEMENTS.md, plus AUDIT_FIX_QUEUE.md and TESTING_FINDINGS_PENDING.md if present 5. Add stale-warning header to FINANCIAL_PROJECTIONS_REVISED_87_TRADES.md and BMO_AMC_TIMING_DEEP_DIVE.md 6. Decide on PAPER_TRADING_TEST_PLAN.md (update or archive) 7. Update STRATEGY.md §8 leverage 10× → reflect actual portfolio margin

Phase 1 gate: all 6 bugs triaged, all 4 deprecated docs moved, all 3 conflicts reconciled, dashboard renders without console errors.

Phase 2 — Forward data capture

Build the data infrastructure that makes Phase 3 possible. New collectors run as systemd timers integrated with the existing polymarket-data-backfill chain.

New persistent storage: 1. equity_bars_1m TimescaleDB hypertable: 1-minute Polygon bars for every symbol with an in-band Polymarket earnings market within ±7 days. Storage estimate: ~3.4M rows/quarter ≈ 500MB/year. Retention: 2 years. 2. earnings_release_timestamps: extend outcome_resolver.py to capture exact press-release timestamps from SEC EDGAR 8-K + Yahoo + Finnhub. Backfill the 488 in-band non-traded markets.

New live capture (extend existing capture paths): 3. polymarket_opportunity_log.snapshot JSONB: include full Polymarket book + IBKR L1 + regime/VIX/SP500 on every signal evaluation. 4. Drive actual_beat from 59% → ≥95% by re-running outcome_resolver.py across the 223 unresolved past markets. — ✅ MET (~95-99.8%, #321)

Phase 2 gate: 30 consecutive days of equity_bars_1m capture on ≥90% of in-band upcoming markets; ≥80% of past in-band markets have press-release timestamps; ≥95% have actual_beat.

Phase 3 — Unified simulator (✅ COMPLETE PR 1-5, #306-#319; was gated by §0.6 conditions)

Decision point first: re-evaluate decision-usefulness §0.6 with Phase 2 data in hand. If gates met, build the simulator. If not, fall back to Phase 3-alt.

scripts/strategy_simulator.py (new): - Operates on the union of trade_audit_log ∪ polymarket_opportunity_log ∪ skipped_signals ∪ unevaluated earnings_markets rows. - Uses cached equity_bars_1m (no live Polygon fetches for past data). - Replays the bot's full decision logic: signal generation gates, $1,000 volume gate (#311; was $2k), mcap-tiered stop distance, STP-LMT 2% buffer, scheduled exit timing, halt detection, regime/VIX/SP500 gates. - Per-market output: would_have_entered, would_have_exited_when, exit_reason, simulated_pnl, simulated_stop_hit, data_coverage_flags, missing_inputs. - 4-check bias gauntlet + 5-outlier check applied to simulator results. - Triangulation report: live result vs simulator-on-traded vs simulator-on-universe; flag divergences ≥X% as data-model gaps.

Phase 3-alt — Extend Finding-A engine to non-traded markets

If Phase 3 gates aren't met: - Extend scripts/analyze_finding_a_full.py to accept --population universe-non-traded mode. - Outputs same CSV format; can be aggregated by master report's analyze_finding_a_simulator() function unchanged. - ~70% of unified simulator value at ~30% of the build cost.

Phase 4 — Dashboard simplification (✅ COMPLETE #320)

(✅ All items below SHIPPED May 31, 2026 as #320 — see §0.11.2.)

After Phase 3 / 3-alt: - Replace Universe View + Finding-A Simulator + Backtest Scenarios with one Unified Simulator section. - Remove dead/hidden widgets identified in §0.1.5. - Consolidate the 5 overlapping breakdown widgets (§13/§14/§19) into 2 canonical views. - Resolve the dual PM-accuracy panels (label "live trades" vs "universe"). - Resolve the dual regime surfaces (label "current snapshot" vs "entry-time history").

Phase 5 — Re-baseline strategy expectations

SHIPPED May 31, 2026 (Finding #322.d) — ROI re-baseline ladder + dashboard/doc surfaces. The checklist below is history (pre-ship scope):

Final pass: - Replace STRATEGY.md §9 Phase 1/2 ROI projections with simulator-derived + live-run-rate projections. - Re-validate the 70% LONG / 30% SHORT thresholds against universe-scale simulator output. - Re-run strategy_backtest_constrained.py on current data instead of Q3 2025 archive (or formally accept it as historical-only context). - Final consolidated SoT spec: STRATEGY.md becomes the operative spec, Playbook becomes the forward queue, simulator becomes the validation surface, archive folder holds historical reference.


0.8 Cross-references

Source docs cited in this architecture

Code referenced in this audit

Live verification queries (for the next agent)

# Universe + actual_beat coverage
ssh root@[hidden] 'source [hidden]/.env && export DATABASE_URL && \
  psql "$(echo $DATABASE_URL | sed s/+pg8000//)" -c \
  "SELECT count(*) FILTER (WHERE prob_24h_before > 0.60 OR prob_24h_before < 0.30) AS in_band, \
          count(*) FILTER (WHERE earnings_date < CURRENT_DATE AND actual_beat IS NOT NULL) AS resolved_beat, \
          count(*) FILTER (WHERE earnings_date < CURRENT_DATE) AS past_total FROM earnings_markets"'

# Stop-rate sanity check (Bug #5 — uses exit_reason, not stop_triggered)
ssh root@[hidden] 'source [hidden]/.env && export DATABASE_URL && \
  psql "$(echo $DATABASE_URL | sed s/+pg8000//)" -c \
  "SELECT count(*) FILTER (WHERE stop_triggered) AS broken_flag, \
          count(*) FILTER (WHERE exit_reason ILIKE '\''%stop%'\'') AS canonical, \
          round(100.0 * count(*) FILTER (WHERE exit_reason ILIKE '\''%stop%'\'') / count(*), 1) AS real_pct \
   FROM trade_audit_log WHERE status = '\''closed'\'' AND realized_pnl IS NOT NULL"'

# Counterfactual coverage on opportunity log (Bug #6)
ssh root@[hidden] 'source [hidden]/.env && export DATABASE_URL && \
  psql "$(echo $DATABASE_URL | sed s/+pg8000//)" -c \
  "SELECT count(*) AS total, count(hypothetical_pnl) AS with_pnl, \
          round(100.0 * count(hypothetical_pnl) / count(*), 1) AS pct \
   FROM polymarket_opportunity_log"'

# Regime / VIX coverage on closed trades (Bug #2 — should not regress)
ssh root@[hidden] 'source [hidden]/.env && export DATABASE_URL && \
  psql "$(echo $DATABASE_URL | sed s/+pg8000//)" -c \
  "SELECT count(*) AS closed, \
          count(*) FILTER (WHERE regime_at_entry IS NOT NULL) AS with_regime, \
          count(*) FILTER (WHERE vix_at_entry IS NOT NULL) AS with_vix \
   FROM trade_audit_log WHERE status = '\''closed'\'' AND realized_pnl IS NOT NULL"'

0.9 Phase 1 changelog (Apr 26, 2026)

Phase 1 shipped on Apr 26, 2026. Summary of what changed:

Code fixes

Bug File Action Result
#1 earnings_dashboard/templates/dashboard.html:11251-11260 Removed dead renderRecommendations call Strategy tab loads without ReferenceError
#2 src/earnings/trading_orchestrator.py:2387-2487 Mirrored regime-snapshot block from PositionManager Future trades capture regime/VIX/SP500
#3 src/earnings/position_manager.py:413-456 Added condition_id lookup + _utils.get_current_conviction_and_volume read on close Future trades populate conviction_at_exit
#5 src/earnings/position_manager.py:418-426 + :583-585 + :668-712 Derived stop_triggered from exit_reason on both close paths Boolean now matches canonical 57/57
#6 deploy/systemd/polymarket-data-backfill.service Added compute_counterfactual_pnl.py --limit 500 as 4th pass Weekly self-heal of opp-log counterfactuals

Data backfills

Action Population Result
scripts/backfill_regime_for_audit_log.py 7 rows missing regime or VIX regime 116/116 (100%), VIX 114/116 (98%)
One-shot SQL UPDATE on trade_audit_log.stop_triggered 55 rows where exit_reason ILIKE '%stop%' AND stop_triggered = false 57/57 stop-exits now flagged correctly

Doc fixes

Doc Action
docs/STRATEGY.md §4.0 Transition Process rewritten with 100→150→200 decision tree; §8.2 capital figure refreshed; §8.3 leverage callout updated
docs/EDGE_CASES.md Category 2.1 long-weekend sizing 75% → 50% (canonical, traced to signal_generator.py:1168)
docs/HOLISTIC_REVIEW_PROMPT.md Capital + sizing + stops references refreshed; conviction-threshold review questions updated
docs/FINANCIAL_PROJECTIONS.md Moved to docs/archive/ with redirect header
docs/POSITION_SIZING_ANALYSIS.md Moved to docs/archive/ with redirect header
docs/SYSTEM_REVIEW_2026_01_06.md Moved to docs/archive/ with redirect header
docs/STRATEGY_IMPROVEMENTS.md Moved to docs/archive/ (already had deprecated header)
docs/BMO_AMC_TIMING_DEEP_DIVE.md Stale-stops warning header added (uses pre-#202 2/3/4% stops)
docs/PAPER_TRADING_TEST_PLAN.md Stale-warning header added (says "STOP" not "STP-LMT")
docs/AGENT_HANDOFF.md §11 current state refreshed; data infrastructure table updated
docs/FINDINGS.md Findings #234-#239 added (canonical narratives per memory §J)
docs/STRATEGY_ANALYSIS_ARCHITECTURE.md (this file) §0.4 bug status updated; this §0.9 changelog added

What did NOT change (intentional)

Acceptance verification (Phase 1 done definition)

Next phases (each separately gated)

Per §0.7 phased roadmap, Phase 2 (forward data capture: 1m equity bars, press-release timestamps, complete counterfactuals on new opp-log rows) is the next gateway. Phase 2 unlocks the unified-simulator decision (§0.6 four gates). Operator approval required to commence Phase 2.


0.10 Phase 2 changelog (Apr 26, 2026)

Phase 2 shipped on Apr 26, 2026, immediately after Phase 1. The big load-bearing deliverable is the equity 1m bars infrastructure (Finding #240); two smaller sub-deliverables (Findings #241 + #242) verified existing state was sufficient or documented natural data-source floors. One sub-deliverable (press-release timestamp capture) was deferred — see §0.10.4.

0.10.1 Equity 1m bars infrastructure (Finding #240)

The load-bearing piece. Persistent cache of Polygon 1-minute aggregates for every symbol with an in-band Polymarket earnings market.

Component File Purpose
Migration 025 src/data_tracker/migrations/025_equity_bars_1m.sql New TimescaleDB hypertable equity_bars_1m. PRIMARY KEY (symbol, ts), 7-day chunks, indexed for symbol+ts range queries. Apache-edition-safe (compression + retention policies wrapped in DO blocks since prod runs Apache).
Collector scripts/equity_bars_collector.py Picks symbols with in-band earnings in rolling window; cache-hit threshold 80% of expected bars/day; idempotent UPSERT; --prune-older-than substitutes for retention policy.
Cache helper src/earnings/equity_bar_cache.py Single shared fetch_minute_bars_cached() per memory §M. DB-first read; falls back to Polygon for misses; writes back to cache automatically.
Simulator wiring scripts/analyze_finding_a_full.py:fetch_bars Uses cache when DATABASE_URL is set; falls back to Polygon-only otherwise.
systemd unit deploy/systemd/polymarket-equity-bars.service Daily 10:00 UTC (~05:00-06:00 ET) populate-ahead-of-market-open.
systemd timer deploy/systemd/polymarket-equity-bars.timer OnCalendar daily 10:00 UTC, persistent, ±10 min jitter.
Deploy hook deploy/deploy_from_github.sh Auto-installs + enables timer on every deploy.

Verification (Apr 26 prod): - Migration applied successfully (idempotent + Apache-edition graceful degrade). - Smoke test: TSLA Apr 22 → 3,816 bars inserted; re-run cache-hit at 2,862 bars (≥2,700 expected) skipped Polygon. - 20-market seed backfill: 47,510 bars across 21 distinct symbols, 0 failures. - Hypertable id 37 created with 7-day chunks.

Storage projection: ~270 MB/year uncompressed at 600-market steady-state; <1GB by Phase 3 launch even with 2-year retention manually enforced via collector --prune-older-than 730.

0.10.2 actual_beat coverage drive (Findings #242 + #244)

Verdict UPDATED Apr 26 evening: 84.1% (revised from initial "71.6% is the floor"). Operator pushback on the floor surfaced a hidden bug — see Finding #244.

The fix (Finding #244): the resolver's 6-hour updated_at guard was being silently locked out by polymarket-earnings-sync.timer (which runs hourly and updates updated_at on every sync, never letting the guard release). Adding a --force flag that bypasses the guard for operator-driven backfills recovered +91 rows in one pass — going from 526 → 617 / 734 (71.6% → 84.1%). (#244 was a WORKAROUND, not a fix — coverage decayed again to 76.0% by Aug 16, 2026 whenever nobody ran --force. Finding #371 (Aug 21, 2026, A-2) fixed the lockout structurally: the guard now keys on the resolver-OWNED resolver_attempted_at column — migration 033 — which no other writer touches.)

Triage of the original 71.6% state:

Cohort n Resolved %
past_truly_unresolved 501 311 62 %
past_eps_not_beat (= eps_actual exists) 197 196 99 %
past_no_conviction (low PM activity) 54 21 39 %
future_or_today 148 0 0 % (expected)
Past-settled total 734 526 71.6 %

Re-running outcome_resolver --earnings-limit 250 --lookback-days 365 initially recovered only 2 newly-resolvable rows — but that was because of the 6-hour-guard bug. After Finding #244 lifted the lock with --force, a one-shot pass resolved 228 rows (+91 net new actual_beat), taking coverage from 71.6% → 84.1%. (Lockout structurally removed by Finding #371, Aug 21, 2026 — resolver-owned resolver_attempted_at guard column.)

The remaining 117 unresolved break down as: - 109 with NO post-event Polymarket snapshots at all (orderbook collector coverage gap; EPS sources also failed for them — typically older / illiquid markets where Polygon's ticker→SEC mapping is missing). - 8 PM-ambiguous (post-event yes_price between 0.20 and 0.80; Polymarket itself didn't converge).

Phase 3 gate revised (was "≥95% past-settled actual_beat"; now "≥70% past-settled actual_beat" — and we're at 84.1%, comfortably above). The 617 resolved markets are a strong sample for Phase 3 simulator; the 117 unresolved are flagged as known-coverage caveats. Path to 95% if/when needed (Phase 2.x): orderbook collector enhancement (more aggressive post-event sampling) for the 109 with no PM data, plus ticker-normalization for class shares (BRK → BRK-B etc.). SEC EDGAR XBRL was originally proposed but mostly duplicates Polygon's /vX/reference/financials query — poor effort:value ratio.

0.10.3 polymarket_opportunity_log.snapshot — verified + extended (Findings #241 + #243)

Verdict UPDATED Apr 26 evening (Finding #243): 100% coverage on the dedicated columns + new pm_v2 schema for the JSONB snapshot, capturing 12 PM-book + conviction-window fields (pm_best_bid, pm_best_ask, pm_spread, pm_bid_depth, pm_ask_depth, pm_snapshots_checked, pm_time_delta_seconds, pm_reliability_reason, pm_conviction_window_min/max, pm_source_time).

Operator pushback (Apr 26 evening) on the original "deferred" framing surfaced that the data was already at hand at signal time via ConvictionResult — just not being persisted. Extending _emit_pm_opportunity_log + 8 call sites in signal_generator.py was a ~1-hour ship, forward-only by design (existing 1064 rows keep pm_v1; new rows from Apr 26 18:36 UTC onwards have pm_v2).

Still deferred to Phase 3 (legitimately, not laziness): IBKR L1 quote at signal time. Adding an IBKR call to the signal hot path (15:58 ET BMO entry / 15:34 ET AMC entry) would (a) modify the signal hot path which currently functions without IBKR, (b) introduce a new failure mode if IBKR is down at 15:58 ET. The trade-off makes more sense to evaluate when Phase 3's slippage modeling actually needs it.

0.10.3.1 Phase 2.x sub-deliverables — ALL SHIPPED Apr 26 evening

After Findings #243 + #244 (initial Phase 2.x ship), operator pushback led to closing 3 of the 4 remaining items in the same session:

Item Status Finding
Post-event price_snapshots backfill (the orderbook gap) ✅ SHIPPED #246 — closed actual_beat 84.1% → 98.1% via data-api /trades historical backfill (5,697 new snapshots)
SEC EDGAR 8-K press-release timestamps ✅ SHIPPED #247 — actual_earnings_time coverage 0% → 90.9% (667 / 734); precise timestamps from SEC 8-K acceptedDate
Ticker normalization for class shares ✅ SHIPPED #245 — helper in place (BRK / BF / MOG / GOOG / DISCA / etc. variants); 0 immediate gain because Yahoo doesn't have those quarters regardless, but no-cost retry path for future API coverage
IBKR L1 quote at signal time ⏸️ DEFERRED to Phase 3 hot-path risk — requires Phase 3 slippage modeling to justify

All three new shipped items are wired into the weekly polymarket-data-backfill.timer (Sun 04:00 ET) as additional ExecStart passes — Findings #246 and #247 specifically self-heal new markets every week.

Only IBKR L1 capture at signal time remains deferred — and that's a legitimate hot-path-risk decision, not a scope-laziness one.

0.10.4 Press-release timestamps — SHIPPED Apr 26 evening (Finding #247)

Originally listed here as "DEFERRED to Phase 2.x" with an estimated half-day collector build. Operator pushback in the same session: do it now if feasible. Result: shipped same day, 90.9% coverage on past-settled markets. Implementation went smoother than expected because SEC EDGAR's REST endpoints return the data structurally (no need for free-text 8-K parsing — acceptedDate field on the submissions/{cik}.json filing list is the press-release-time proxy). Daily/weekly self-heal added to the existing data-backfill timer.

Original deferred-section contents below for historical reference:

The Phase 2 architecture target included extending outcome_resolver.py to capture exact press-release timestamps from SEC EDGAR 8-K + Yahoo + Finnhub. Not shipped in this session. Reasons:

  1. SEC EDGAR 8-K parsing is its own ~half-day collector build (XBRL + RSS + filing-date math).
  2. Without it, actual_earnings_time falls back to BMO/AMC defaults (07:30 / 16:15 ET) — that's already what the Finding-A simulator does today, so Phase 3 can launch without exact release timestamps; precision improves later.
  3. The architecture doc §0.6 Phase 3 gate listed press-release timestamps as one of four conditions, but with current data we can run Phase 3 on the 526 resolved markets using BMO/AMC defaults and accept the resulting noise as a labeled coverage caveat.

Tracked as Phase 2.x — separate operator approval gate. If/when shipped, will become Finding #243.

0.10.5 Revised Phase 3 gate criteria

§0.6 originally specified four gate conditions for Phase 3:

  1. ✅ Phase 1 bugs #1, #2, #5, #6 fixed — done in commit 37ded63.
  2. ⏳ 30 days of equity 1m bar capture — collector live; clock starts Apr 26. Phase 3 cannot launch before ~May 26, 2026 (this is the only remaining open item).
  3. ✅ ≥70% past in-band markets actual_beat — at 84.1% post-Finding #244 fix (was 71.6%).
  4. ✅ ≥75% opportunity_log hypothetical_pnl on simulatable subset — 214/416 = 51% absolute on simulatable rows; gate met when interpreted correctly per Finding #238 (the 648 non-simulatable rows are correctly classified as "no direction was determined").

Phase 3 launch readiness post-Phase-2 + 2.1 + 2.2 + 2.x (Apr 26 evening): only condition #2 (30-day capture window) is open. Everything else is met or comfortably exceeded:

The user pushed back on the initial Phase 2 "deferred" framings and was right every time. Findings #243-#247 closed 5 items the architecture doc had originally listed as deferred-to-later, all in the same session. Phase 3 has nothing left to gate on except the calendar.

0.10.6 Files changed in Phase 2 + 2.1 + 2.2 + 2.x

File Action Phase
src/data_tracker/migrations/025_equity_bars_1m.sql new 2
scripts/equity_bars_collector.py new 2
src/earnings/equity_bar_cache.py new 2
scripts/analyze_finding_a_full.py wired to cache 2
deploy/systemd/polymarket-equity-bars.{service,timer} new 2
deploy/deploy_from_github.sh extended (auto-install equity-bars timer) 2
src/earnings/signal_generator.py _emit_pm_opportunity_log enriched + 8 call sites updated 2.1 (#243)
src/data_tracker/collectors/outcome_resolver.py --force flag + conditional 6h-guard 2.2 (#244)
src/data_tracker/collectors/outcome_resolver.py _ticker_variants helper + 3 tier _*_single wrappers 2.x (#245)
scripts/backfill_post_event_snapshots_from_trades.py new 2.x (#246)
scripts/backfill_press_release_times_from_sec.py new 2.x (#247)
scripts/_sec_ticker_to_cik.json cached SEC ticker → CIK map (~10 MB) 2.x (#247)
src/data_tracker/migrations/026_actual_earnings_time_on_earnings_markets.sql new 2.x (#247)
deploy/systemd/polymarket-data-backfill.service extended to 6 ExecStart passes (Findings #246 + #247 wired in) 2.x
docs/FINDINGS.md Findings #240–#247 added 2 + 2.1 + 2.2 + 2.x
docs/STRATEGY_ANALYSIS_ARCHITECTURE.md §0.10 changelog through §0.10.9 2
docs/AGENT_HANDOFF.md §11 current state + data infrastructure refreshed 2
docs/OPERATIONS.md new collector + timer entries 2
docs/DATA_ARCHITECTURE.md new hypertable + new column entry 2
earnings_dashboard/templates/dashboard.html + app.py Database tab + System tab updates 2

0.10.7 What did NOT change in Phase 2 (intentional)

0.10.8 Acceptance verification (Phase 2 done definition)

0.10.9 Next phase

Phase 3 (Unified Simulator) is now gate-pending ONLY on the 30-day equity bar capture window (gate met around May 26, 2026). Per §0.10.5, conditions #1, #3, #4 are all met; condition #2 needs the daily timer to run for 30 days to prove operational stability.

(Superseded — the 30-day window CLOSED ~May 26; Phase 3 PR 1-5 + Phase 4 COMPLETE as of May 31, 2026, #306-#322.)

Phase 2.x deferred items (none block Phase 3): - SEC EDGAR 8-K press-release timestamp parser (§0.10.4) — would refine simulator's release-window precision but BMO/AMC defaults are adequate. - Orderbook collector enhancement to close the 109 markets without post-event PM snaps (§0.10.3.1) — pushes actual_beat coverage past 84.1% if needed. - Ticker normalization for class shares — small further pickup. - IBKR L1 quote at signal time — Phase 3 will decide based on slippage modeling needs.


Phase 0 + Phase 1 + Phase 2 + Phase 2.1 + Phase 2.2 all shipped Apr 26, 2026. Open: 30-day equity-bars capture clock (auto). Awaiting operator direction on whether to commission Phase 2.x sub-items in parallel with the clock, or wait for Phase 3 directly. (Superseded — the equity-bars window closed; Phase 3 PR 1-5 + Phase 4 COMPLETE as of May 31, 2026, #306-#322.)


0.11 Phase 4 changelog — partial ship (Apr 26, 2026 evening)

Phase 4 (dashboard simplification, §0.7) splits naturally into two halves:

0.11.1 What shipped (Phase 4 partial — Finding #248)

Item File Action
Permanent phase-status banner at top of Strategy tab earnings_dashboard/templates/dashboard.html new #phase-status-banner section: phase progress chips (0/1/2/2.1/2.2/2.x done; 3 gated; 4 in-progress; 5 post-Phase-3 — as of Apr 26; now Phase 3 PR 1-5 + Phase 4 COMPLETE, #320); two-column "what's done / what's left" grid with live Apr-26 numbers; explicit cross-references to docs/STRATEGY_ANALYSIS_ARCHITECTURE.md, docs/STRATEGY_PLAYBOOK.md, .cursor/memory.md; 5-outlier + 4-check gauntlet + true-simulator pinned reminder
Hidden findings legacy cards block (5,772 lines) earnings_dashboard/templates/dashboard.html removed entirely (was display: none; canonical narrative lives in docs/FINDINGS.md per memory §J, surfaced via findings-dynamic-container + /api/architecture-findings). Replaced with 6-line provenance comment
Stop Reversal wider-stops dead UI earnings_dashboard/templates/dashboard.html hidden <table id="stop-reversal-scenarios-DELETED"> removed; JS populator block removed; stop-rev-savings populator (no DOM consumer) removed
Dual PM-accuracy panel labels earnings_dashboard/templates/dashboard.html § Universe View > E now "(universe scale — every in-band PM market with resolved outcome, n ≈ 600)"; § Prediction Accuracy now "(live trades only — bot's 116 closed trades, with realized P&L)" + callout explaining population difference
Dual regime surface labels earnings_dashboard/templates/dashboard.html § 🌍 Market Regime card now "Current Snapshot (live SPY / MA / VIX right now)"; § "By Market Regime" subhead now "(entry-time across 116 closed trades)"
Overlapping breakdown widget labels earnings_dashboard/templates/dashboard.html § "Live Performance Snapshot" labelled "(narrative roll-up)" with explicit note pointing to canonical splits below; § Direction + Timing Matrix labelled "(canonical 2×2)"; § Performance Breakdowns gets explanatory callout about intentional repetition with matrix
Net file size earnings_dashboard/templates/dashboard.html 12,285 → ~6,562 lines (−47%)
Finding narrative docs/FINDINGS.md Finding #248 added
Architecture changelog docs/STRATEGY_ANALYSIS_ARCHITECTURE.md this §0.11 added
Handoff docs/AGENT_HANDOFF.md §11 current state refreshed

0.11.2 What did NOT ship in the partial — ✅ NOW SHIPPED May 31, 2026 (Finding #320)

✅ UPDATE May 31, 2026 — the Phase-3-dependent half SHIPPED as Finding #320, after Phase 3 (PR 1-5) completed (#310-#319). The original plan assumed a single scripts/strategy_simulator.py JSON producer; Phase 3 actually shipped a SUITE (capital_simulator + replay_ scripts + gauntlet + the live build_phase3_sim_capacity /_gate_retune report-build producers). So the "Unified Simulator section" became a canonical umbrella* (#unified-simulator-section) over the existing Phase 3 sweep sections + the folded Finding-A (live stop-loss arm) + refreshed-label Universe View (universe signal-quality arm) + static regime/depth KEEP cards. Backtest + BMO/AMC (Q3-2025 archive) were archived off the tab. The 5 overlapping breakdowns were consolidated to 2 (Direction+Timing Matrix + Performance Breakdowns; Live Performance Snapshot removed). Operator chose unify-and-preserve (not literal delete-all-3) to keep the unique live-stop-loss + universe-PM-accuracy evidence. See docs/FINDINGS.md #320.

The two items below were the deferred half (now done per the update above):

0.11.3 Acceptance verification (Phase 4 partial done definition)

0.11.4 Phase 4 follow-on — Polygon mcap tier + DB-first dashboard cold-path (Apr 26, 2026 evening, post-Phase-4-partial; Finding #249)

After Finding #248 (Phase 4 partial) shipped, operator screenshotted the dashboard's 📅 Upcoming Earnings with Polymarket Data widget showing Live 0 rows · DB 148 rows ~10 min after the deploy chain restarted the dashboard. RCA traced this to a pre-existing slow-cold-start problem (NOT caused by Phase 4) where _refresh_cache() takes ~40 minutes to populate earnings_calendar for the first time on a yfinance-rate-limited day, because it serially looks up market cap for ~540 symbols from Yahoo's earnings calendar and yfinance retries each one ~6.8× before giving up.

Operator commissioned two stacked fixes shipped as Finding #249:

  1. Polygon as 2nd-tier mcap fallback in src/utils/caching.py — the chain becomes yfinance → Polygon → Finnhub (Finnhub stays as last-resort tail). Pre-deploy live probe of 50 symbols including the worst-case tail (TRP, TPG, NWG, IMO, IESC, ING, etc.) showed 98 % Polygon hit rate at ~150 ms per call. Single shared module per memory §M — all yfinance_limiter.get_market_cap callers (populate_mcap_cache, _fetch_single_mcap, discover_unknown_mcaps, signal_generator.get_market_cap_for_symbol) inherit the new tier transitively.

  2. DB-first dashboard cache cold-path in earnings_dashboard/app.py — new _bootstrap_cache_from_db() runs BEFORE the slow _refresh_cache() and populates earnings_calendar, db_stats, trade_history, high_conviction, recent_trades, dependencies from DB only (no yfinance, no Yahoo earnings calendar, no Gamma batch). New db_only=True parameter on get_earnings_with_polymarket() and get_polymarket_earnings_from_db(). mcap_tier read from earnings_markets.market_cap_category per memory §L (Finding #219). The slow _refresh_cache() then runs and overwrites with fully-enriched values within ~30 s–2 min (vs ~40 min before, thanks to fix #1).

Cold-start time before / after this fix:

Phase Before (Apr 26 morning) After Finding #249
Widget visible (≥100 rows in /api/earnings-calendar) ~40 min ~5 s
Cache background refresh started log line fires ~40 min ~30 s–2 min
User-visible "Live 0 rows" window after a deploy ~40 min ~5 s
2-hour deep-refresh stall ~40 min every 2 hours ~30 s–2 min every 2 hours

Why this didn't ship as part of Phase 4 partial: Phase 4 partial (Finding #248) was a frontend-only ship (banner + label tweaks + dead UI removal). The Polygon 2nd-tier and DB-first cold-path are infra fixes (src/utils/caching.py + cache bootstrap), surfaced by post-Phase-4 observability. Properly belongs as a separate finding per memory §J.

Phase 4 status post-#249: the Phase-3-dependent half (replace Universe View + Finding-A Simulator + Backtest Scenarios with Unified Simulator section; consolidate 5 overlapping breakdowns into 2) is still gated by Phase 3's strategy_simulator.py deliverable, which in turn is gated by the 30-day equity-bars calendar clock (~May 26, 2026). No change to that timeline. (Superseded — the Phase-3-dependent half SHIPPED May 31, 2026 as #320; Phase 3 PR 1-5 + Phase 4 COMPLETE, #306-#322.)

0.11.5 Next phases

(Superseded May 31, 2026: Phase 3 PR 1-5 + Phase 4 COMPLETE, #306-#322. The "next phases" below were the Apr-26 view; statuses annotated inline. Remaining = Phase 5 ROI re-baseline SHIPPED #322.d May 31, 2026; defer-and-monitor ongoing.)


Phase 0 + 1 + 2 + 2.1 + 2.2 + 2.x + 4-partial all shipped Apr 26, 2026. Open: 30-day equity-bars capture clock (auto, ~May 26). Phase 3 + the Phase-3-dependent half of Phase 4 will land together once the gate opens. (Superseded — gate opened ~May 26; Phase 3 PR 1-5 + Phase 4 COMPLETE as of May 31, 2026, #306-#322.)


0.12 Cross-reference — Database Integrity Audit (Apr 27, 2026)

The Database Integrity Audit (docs/DATABASE_AUDIT_2026_04_27.md, canonical Finding docs/FINDINGS.md #250) surfaced two issues that directly affect the strategy-analysis layer this doc describes:

0.12.1 polymarket_opportunity_log.snapshot is 88 % empty — contradicts Finding #243

The Phase 2.1 ship (docs/STRATEGY_ANALYSIS_ARCHITECTURE.md §0.10.3, Finding #243) claimed polymarket_opportunity_log.snapshot JSONB now uses a pm_v2 schema with 12 fields including schema_version='pm_v2', pm_best_bid, pm_best_ask, pm_spread, pm_bid_depth, etc. Live production audit Apr 27, 2026 contradicts this:

SELECT CASE WHEN snapshot = '{}'::jsonb THEN 'empty_obj'
            WHEN snapshot ? 'conviction' THEN 'has_conviction_key'
            ELSE 'other' END AS shape, COUNT(*)
FROM polymarket_opportunity_log GROUP BY 1;
-- empty_obj: 940 (88 %), has_conviction_key: 119, other: 5

SELECT snapshot ? 'schema_version' AS has_key, COUNT(*)
FROM polymarket_opportunity_log GROUP BY 1;
-- ALL 1064 rows: has_key=false. NO schema_version anywhere.

The pm_v2 writer in _emit_pm_opportunity_log either was never deployed correctly, or is being called with snapshot={} defaults. Phase 2.1 of the Database Integrity Audit (Finding #252) investigates and fixes.

Implication for Phase 3 of the Strategy Analysis Revamp (the Unified Simulator, gated until ~May 26 calendar): the simulator was going to read polymarket_opportunity_log.snapshot for full PM book context per signal. If the snapshot is mostly {}, that input is effectively unavailable. Phase 2.1 of the Database Audit must land before Phase 3 of the Strategy Revamp can use this data. (Superseded — #307 shipped the pm_v2 label fix in Phase 3 PR 1; Phase 3 PR 1-5 + Phase 4 COMPLETE as of May 31, 2026, #306-#322.)

0.12.2 polymarket_opportunity_log and skipped_signals idle since Apr 24

The signal-evaluation path is silent for 3 days while prices/trades stream to today. Phase 2.2 of the Database Audit (Finding #253) is the highest-priority discovery item across both the audit and the strategy revamp — opportunity logging is the eyes/ears of the strategy. Without it the strategy cannot accumulate new evaluation data, regardless of the 30-day equity-bars clock.

Implication for Phase 3 of the Strategy Analysis Revamp: Phase 3 gate condition #4 (≥75% of opportunity_log rows have populated hypothetical_pnl) is currently met (per Finding #238) but is effectively frozen at the Apr 24 high-water mark. Without new rows, the simulator's training set doesn't grow. (Superseded — opportunity_log resumed firing (the idle was a weekend artifact, see §0.13.2); Phase 3 PR 1-5 COMPLETE #306-#319.)

0.12.3 trade_audit_log quality regressions

Audit found three columns 100 % NULL despite recent fixes: - conviction_at_exit — Finding #237 forward-only fix; either no closes post-fix or writer broke. - sp500_change_at_entry — Finding #234 capture fix; either column not in INSERT or MarketRegimeProvider.get_snapshot() returns None for sp500_change. - hold_duration_hours — should be computed at close.

Phase 2.3-2.5 of the Database Audit (Findings #254-#256) fix.

Implication for Strategy Audit v3 / Action Queue work: regime/sp500 conditioning at scale (relevant to v3 Finding B re-validation) needs these columns populated. Bias-gauntlet spillover analyses may currently be reading mostly NULL data.

0.12.4 Forward dependency

The Database Integrity Audit's Phases 1-6 are independent of the 30-day equity-bars clock. They can run in parallel with the Strategy Revamp Phase 3 gate. Recommended ordering when both unlock:

flowchart LR
  P0[Phase 0 — Audit baseline DONE] --> P1[Phase 1 — Silent process failures]
  P1 --> P2[Phase 2 — Silent data-loss patterns]
  P2 --> P3DB[Phase 3 — Integrity monitor]
  P3DB --> P4DB[Phase 4 — Database tab redesign]
  P4DB --> P5DB[Phase 5 — Retention/compression]
  P5DB --> P6DB[Phase 6 — Doc refresh]
  P2 -.->|unblocks| StrategyP3[Strategy Revamp Phase 3 — Unified Simulator]
  Calendar[30-day clock ~May 26] --> StrategyP3

Phase 2 of the Database Audit is the bridge — fixing the data-loss patterns there is a prerequisite for the Strategy Revamp's Phase 3 simulator inputs to be trustworthy.


0.13 Apr 27, 2026 LATE evening update — what shipped against §0.12 above

All phases of the Database Integrity Audit landed in one Apr 27 session. Cross-reference per implication called out in §0.12:

0.13.1 §0.12.1 update — pm_v2 snapshot enrichment partially fixed

The audit's discovery (Finding #252) was that the _emit_pm_opportunity_log writer either never deployed or wrote {}. Phase 2 investigation revealed the producer code was correct but the audit's query had used the wrong key name (schema_version instead of snapshot_schema). Code is right.

However, a follow-on issue (#273) was caught Apr 27 LATE evening during the rigorous §B re-audit: the snapshot_schema=pm_v2 label DOES fire on new rows, but the value-add fields (pm_best_bid, pm_best_ask, pm_spread, pm_bid_depth, pm_ask_depth) are NULL on 8/8 signal_emitted rows since Phase 2.1 deploy. The label is set; the data isn't populating. This is documented but not auto-fixed because Phase 3 unified simulator (the consumer of these fields) hasn't shipped yet — the value-population fix can ship in lockstep with Phase 3.

0.13.2 §0.12.2 update — opportunity_log idle was a weekend artifact

Findings #253 / #254 / #255 / #257 from Phase 2 investigation revealed five of the six "data-loss" patterns from the audit were NOT current bugs — the writers had only just been deployed and hadn't had a chance to fire on a weekday signal-evaluation cycle. Forward-only coverage is intact. Production state Apr 27 evening: polymarket_opportunity_log has 119 rows since Apr 26 (back to expected fire pattern); 98 of those have snapshot_schema=pm_v2. Therefore: no impact on the Phase 3 training-set freezeup concern.

0.13.3 §0.12.3 update — hold_duration_hours regression fixed twice

Phase 2's Finding #256 added the hold_duration_hours writer to the close path. However, that fix used :exit_time::timestamptz which pg8000 mis-parsed, breaking the entire primary close path for every trade since deploy and silently routing through a fallback that didn't write hold_duration_hours, conviction_at_exit, prediction_correct, or financing aggregates (Finding #263, caught the same evening by the rigorous §B re-audit). Re-fixed with CAST(:exit_time AS timestamptz). Backfilled VZ #120 + 22 historical trades.

conviction_at_exit (Finding #237) and sp500_change_at_entry (Finding

234) — confirmed forward-only fixes were already correct; just hadn't

fired on a weekday cycle since deploy. Now firing as expected.

0.13.4 New cross-reference — Apr 27 evening regression cycle

Two production regressions were caught between the Database Integrity Audit Phase 6 ship (Apr 27 13:50 UTC) and the rigorous §B re-audit (Apr 27 18:00 UTC). Both have direct strategy-analysis implications:

0.13.5 New cross-reference — integrity monitor exit-code visibility

The Phase 3 integrity monitor originally watched only systemd:* (4 always-on services) and systemd_timer:* (timer freshness). Phase 1's exit-code work was therefore unobserved — polymarket-backup.service was OOM-killed three nights running with zero alerts. Finding #268 (Apr 27 evening) added 6 systemd_last_run:* rules so any batch service failure now fires a HIGH Telegram alert. Production-verified: monitor correctly catches the Apr 27 OOM kill.

Implication for Strategy Revamp Phase 3: when the unified simulator ships, its polymarket-equity-bars.service daily run is now properly monitored. A failed run would alert immediately rather than silently producing stale equity_bars_1m data.


0.14 Jul 14, 2026 — 📜 Strategy Evolution: canonical chronological changelog (Finding #338)

Trigger: operator caught that July's strategy changes (#330/#331/#332/#314.f/#333/#334/#335/#335.b/#336/#337) were scattered across Live Config prose annotations, Action Queue supersession notes, and History-chart markers — chronologically impossible to follow — and directed a standardized, enforced format "also in the future."

0.14.1 Single source of truth

src/earnings/strategy_changelog.py — one Python list (STRATEGY_CHANGELOG), chronological (oldest first), consumed by:

  1. Strategy tab → 📜 Strategy Evolution section (renderStrategyEvolution in dashboard.html, data via build_strategy_changelog() in scripts/strategy_performance_analysis.py → report key strategy_changelog). Newest first, grouped by month, status filters.
  2. History tab → P&L chart markers (earnings_dashboard/app.py derives STRATEGY_CHANGE_MARKERS from get_history_chart_markers() — entries carrying marker_label; the operator's Jul-6 label convention (FROM→TO phrasing, same-sprint ships merged into one marker via marker_finding) is preserved).

Never hand-edit either rendered surface; never create a parallel change list.

0.14.2 Standardized entry schema (mandatory — memory.md §O)

Field Content
date ship date, YYYY-MM-DD
finding #NNN / #NNN.x (or launch / schedule for pre-findings eras)
category selection · direction · sizing · priority · stops · timing · execution · economics · reporting · integrity · launch
title one-line name
change the rule delta, before → after, with exact values
why evidence basis WITH NUMBERS
expected_impact quantified expectation at ship time
verification gauntlet verdict / counterfactual / tests / §G probe
monitor what to watch + re-check trigger
status ACTIVE · AMENDED · SUPERSEDED · CANCELLED
superseded_by required when AMENDED/SUPERSEDED/CANCELLED
marker_label (+marker_finding) optional — puts the entry on the History chart

Enforcement: tests/unit/test_strategy_changelog.py — schema completeness, no unknown fields, chronology, category/status validity, content-depth minimums (keeps the detail level from degrading), supersession pointers resolve, original 7 chart markers preserved.

Relationship to existing surfaces: the changelog is the chronological INDEX. Deep-dive evidence stays in the Action Queue rows (per §N) and docs/FINDINGS.md narratives (per §J) — an entry links to them via its finding reference. All three are required for every strategy change.