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 Queuetab -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)
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.
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:
analyze_universe_hit_rate). Different
thresholds for different surfaces — definition drift risk if anyone
cross-reads numbers.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:
The ground truth for every live and counterfactual analysis. NULL rates and row counts pulled from prod on Apr 26, 2026. **(Snapshot superseded — see
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 |
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 |
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:
renderRecommendations(...) BROKEN CALL — invoked at
dashboard.html:11254 but the function was deleted (comment at
:11482 confirms). Causes a runtime ReferenceError on every Strategy
tab load (verifiable by opening browser console). Fix is one-line.display: none (~line 9474), but JS still populates the tbody. Dead UI
work; remove either the populator or un-hide the parent.analysis.weekly_performance, analysis.hold_duration,
analysis.cumulative_pnl_clean, analysis.cumulative_pnl_all,
analysis.red_flags, analysis.trade_details,
analysis.missed_signals_analysis — computed in run_analysis but
no consumer in the Strategy-tab template. Some are intentionally hidden
(per JS comment line 11242 "removed — not actionable"); others are
orphan computations.API:
/api/strategy-analysis — primary, returns analysis (from
strategy_analysis_report.json) + backtest + bmo_amc_timing +
current_regime + timestamp/api/regime — separate lightweight regime endpoint (reads same
regime_cache from the same JSON)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)
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.
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).
renderRecommendations ReferenceError on Strategy tab loadearnings_dashboard/templates/dashboard.html:11254
invokes renderRecommendations(analysis, backtest, cohortTiming). The
function definition was deleted (comment at :11482 confirms removal).ReferenceError: renderRecommendations is not defined. Tab still
renders (the ReferenceError aborts the rest of renderStrategyAnalysis
but the early sections have already painted before the call).renderXxx call AFTER line 11254 in
renderStrategyAnalysis may not execute. Hard to tell which sections
silently fail without testing each.TradingOrchestrator._persist_position_open omits regime/VIX/SP500src/earnings/trading_orchestrator.py:2388–2412 (INSERT statement) vs
src/earnings/position_manager.py:324–347 (sets the fields).regime_at_entry IS NULL; 7
have vix_at_entry IS NULL. NULL trades cluster in post-Apr-21 entries,
consistent with the orchestrator path becoming more frequent recently.regime_vix_performance,
v3 Finding B downtrend skip, v3 Finding E SHORT regime) loses signal as
more trades land via the orchestrator path. The bug is a regression
that gets worse with each new trade until fixed.PositionManager.persist_position_open into the orchestrator INSERT).trade_audit_log.conviction_at_exit never set on close pathPositionManager close
UPDATE (src/earnings/position_manager.py:505–572) does not touch it.conviction_at_exit IS NULL.trading_positions.final_conviction exists for
earnings-detection cases (75/116 populated) but is not the same field.trade_audit_log.sp500_change_at_entry never populatedscripts/backfill_regime_for_audit_log.py:97–98 explicitly leaves
SPY intraday change as NULL on historical rows ("undefined for past
intraday").TradingOrchestrator._persist_position_open does not set it (Bug #2
parent).PositionManager.persist_position_open does set it, but only when
MarketRegimeProvider.get_snapshot() returns a non-NULL sp500_change.sp500_change_at_entry.polymarket_opportunity_log (544/1064 = 51%
populated post-#227).trade_audit_log.stop_triggered undercounts by 28× (Finding #233 known)exit_reason like
'stop_loss:hardware' but only 1 of them flips the stop_triggered
boolean.stop_triggered = true vs 57/116
matching exit_reason ILIKE '%stop%'. Documented in Finding #233.stop_triggered is wrong by orders of
magnitude. Workaround in place — all current analyses use exit_reason.
Without writer fix, the bug stays._persist_exit() helper that derives the
boolean from the textual reason; backfill 55 historical rows.polymarket_opportunity_log.hypothetical_pnl only 39% populatedscripts/compute_counterfactual_pnl.py is the backfill
but is run on demand, not scheduled.hypothetical_pnl;
counterfactual_computed_at is set on all 1064 (so the script ran but
produced NULL on 648 rows — possibly Polygon failures, possibly
intentional "no data → leave NULL").analyze_skipped_cost) and
any future unified simulator have a 61% data hole.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 |
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:
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?"
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.)
⚠️ 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_beatand 75%hyp_pnlfigures 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:
actual_beat resolved
(currently 528/900 = 59% across all markets, but ~80% within the
in-band 604).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.
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 — thesignal_emitted100% 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 maskinggenerated_at(verified on real prod data +test_two_runs_produce_zero_byte_diffunit test). - §10.3 H-rule self-validation = synthetic v2 H-rule returns FAIL-EX-TOP-2 pertest_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.)
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.
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.
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.
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.
(✅ 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").
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.
.cursor/memory.md (§J / §K / §M / §N — process discipline)docs/STRATEGY.md (§1–§10 — operative spec)docs/STRATEGY_PLAYBOOK.md (§1–§8 — forward SoT)docs/STRATEGY_AUDIT_v3.md (§1–§15 — audit trail; §13.6 single-source rule, §14 Apr 25 re-validation, §14.7 canonical-field-validity rule)docs/FINDINGS.md (#179, #202, #211, #219, #220, #222–#226, #227, #228, #229, #230, #231, #232, #233 are the strategy-affecting findings; bulk of others are operational)docs/AGENT_HANDOFF.md (§11 current state)scripts/strategy_performance_analysis.py (master orchestrator)scripts/analyze_finding_a_full.py (1m bar simulator)scripts/compute_counterfactual_pnl.py (counterfactual resolver)scripts/strategy_backtest_constrained.py (Q3 2025 archive backtest)scripts/strategy_backtest_bmo_amc.py (BMO/AMC cohort)scripts/backfill_*.py (regime + learning loop + volume + conviction)src/earnings/counterfactual_pricing.py (Polygon 1m + yfinance fallback)src/earnings/market_regime_provider.py (regime + VIX + SPY change SSoT)src/earnings/volume_policy.py (volume gate SSoT)src/earnings/trading_orchestrator.py:2388–2412 (Bug #2 INSERT)src/earnings/position_manager.py:324–347 (Bug #2 reference impl) +
:505–572 (Bug #3 close UPDATE)earnings_dashboard/templates/dashboard.html (~22 sections + Bug #1 ReferenceError at line 11254)earnings_dashboard/app.py:4915–4966 (/api/strategy-analysis)# 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"'
Phase 1 shipped on Apr 26, 2026. Summary of what changed:
| 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 |
| 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 | 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 |
STRATEGY.md §9 Phase 1 / Phase 2 ROI projections — stale per §0.2 row #23-24 but kept for the model-baseline value. Re-baselining is a Phase 5 task.dashboard.html:2071-7843 — display: none so operators don't see them. Removing them would be a clean-up but requires a separate review of which findings still need preservation. Tracked as a Phase 4 task.scripts/strategy_backtest_constrained.py) — explicitly historical reference, kept with its existing labels. Re-running on current data is a Phase 5 task.git -C [hidden] log -1 --oneline matches master HEADsystemctl is-active earnings-trader polymarket-dashboard polymarket-collector polymarket-resolver.timer polymarket-data-backfill.timer ibgateway-ibc returns 6× activeSELECT count(*) FILTER (WHERE regime_at_entry IS NULL) FROM trade_audit_log WHERE status='closed' = 0SELECT count(*) FILTER (WHERE stop_triggered) AS flag, count(*) FILTER (WHERE exit_reason ILIKE '%stop%') AS canonical, count(*) FILTER (WHERE stop_triggered AND exit_reason ILIKE '%stop%') AS both FROM trade_audit_log WHERE status='closed' = 57 / 57 / 57http://[hidden]:8080/#strategy shows no ReferenceError: renderRecommendations is not definedPer §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.
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.
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.
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.
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.
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.
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:
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.Tracked as Phase 2.x — separate operator approval gate. If/when shipped, will become Finding #243.
§0.6 originally specified four gate conditions for Phase 3:
37ded63.actual_beat — at 84.1% post-Finding #244 fix (was 71.6%).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:
actual_beat coverage: 98.1% (was target ≥70%; original aspiration was 95%; we exceeded it)actual_earnings_time coverage: 90.9% via SEC EDGAR (was 0% — this is a NEW capability, not in original gate criteria)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.
| 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 |
sp500_change_at_entry — formalized in Phase 1 as forward-only post-Bug-#2-fix; no further action this phase.display: none; cleanup deferred to Phase 4 dashboard simplification.compute_counterfactual_pnl.py migration to fetch_minute_bars_cached — not migrated this phase; the cache is in place if/when that script is rerun on a large batch and benefits from caching.systemctl is-active polymarket-equity-bars.timer → activesystemctl list-timers polymarket-equity-bars.timer shows next-fire = next 10:00 UTC ± 10 minSELECT count(DISTINCT symbol), count(*) FROM equity_bars_1m returns growing counts (≥21 symbols / ≥47k bars on Day 0)timescaledb_information.hypertablesearnings-trader, polymarket-dashboard, polymarket-collector, polymarket-resolver.timer, polymarket-data-backfill.timer, ibgateway-ibc) remain active post-deployPhase 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.)
Phase 4 (dashboard simplification, §0.7) splits naturally into two halves:
strategy_simulator.py deliverable exists (~ May 26,
2026 + simulator build time).| 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 |
✅ 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.pyJSON producer; Phase 3 actually shipped a SUITE (capital_simulator + replay_ scripts + gauntlet + the livebuild_phase3_sim_capacity/_gate_retunereport-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. Seedocs/FINDINGS.md#320.
The two items below were the deferred half (now done per the update above):
strategy_analysis_report.json
(weekly_performance, hold_duration, cumulative_pnl_clean/all,
red_flags, trade_details, missed_signals_analysis). Partially
intentional — some surface via stdout report["red_flags"] print path
in strategy_performance_analysis.py:run_analysis. Removing them
touches the JSON contract for /api/strategy-analysis; cheap to keep
in place; clean up during post-Phase-3 widget consolidation when the
full schema gets reorganized.git -C [hidden] log -1 --oneline matches master HEADwc -l earnings_dashboard/templates/dashboard.html returns ~6,562http://[hidden]:8080/#strategy
shows 0 errors on load (no missing-element warnings from removed populators,
no ReferenceError since Bug #1 was already fixed in Phase 1)docs/STRATEGY_ANALYSIS_ARCHITECTURE.md explicitlysystemctl is-active earnings-trader polymarket-dashboard
polymarket-collector polymarket-resolver.timer
polymarket-data-backfill.timer polymarket-equity-bars.timer
ibgateway-ibc returns 7× activeAfter 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:
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.
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.)
(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.)
STRATEGY.md §9 ROI via 3-epoch ladder + dashboard. History: was remaining work as of Apr 26, 2026 §0.11.5.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.)
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:
polymarket_opportunity_log.snapshot is 88 % empty — contradicts Finding #243The 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.)
polymarket_opportunity_log and skipped_signals idle since Apr 24The 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.)
trade_audit_log quality regressionsAudit 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.
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.
All phases of the Database Integrity Audit landed in one Apr 27 session. Cross-reference per implication called out in §0.12:
pm_v2 snapshot enrichment partially fixedThe 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.
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.
hold_duration_hours regression fixed twicePhase 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
fired on a weekday cycle since deploy. Now firing as expected.
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:
#264 — Yahoo override of confirmed Polymarket (HIGH): VZ
earnings BMO, Polymarket signalled YES=99.95% (BEAT), Yahoo's GAAP
scrape returned $1.20 vs $1.21 consensus (1¢ miss). Trader trusted
Yahoo, exited LONG with instant_exit:earnings_miss. Polymarket
resolved YES (the company beat non-GAAP). The trader exited a
CORRECT prediction. Implication for Strategy Audit v3: earnings
detector now blocks Yahoo when Polymarket's live price_snapshots.yes_price
is at extreme (≥99% / ≤1%); future signal-evaluation paths see the
same logic.
#272 — volume_24h_at_entry NULL since Feb 16 (Medium): RCA
showed Polymarket Gamma stopped reliably returning the volume24hr
field; collector wrote 0; signal_generator treated 0 as None; 76
consecutive trades had NULL. Implication for Universe View
analyses: queries that compared bot's live volume_24h_at_entry
against the universe view's volume gate would have under-estimated
bot's coverage. Fix: signal_generator now falls back to most-recent
positive price_snapshots.volume_24h within last 6 hours. Backfill
populated 114/117 (97%) historical rows. Re-running
scripts/strategy_performance_analysis.py will refresh §3 evidence
numbers if any analysis joined on volume_24h_at_entry.
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.
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."
src/earnings/strategy_changelog.py — one Python list (STRATEGY_CHANGELOG), chronological (oldest first), consumed by:
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.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.
| 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.