← Back to Dashboard

Polymarket Earnings Bot - Complete Context Document

Purpose: This document captures ALL key directives, architecture, configuration, and implementation details. Use this to bootstrap new AI conversations without losing context.

Last Updated: 2026-09-26 (Sep 27, 2026 — Findings #481/#482 DEPLOYED (deploy #14 28b572b 00:33 UTC: #481 quarantines a permanently failing live-trade row once and marks it seen, market-data poison is WARN only; deploy #15 860af84 02:43 UTC: #482 migration 042 widens trades.token_label to varchar(64), the typmod probe is scoped to 042, and the three quarantined payloads — two distinct trades — were released, spool empty; the deploy-#14 dashboard restart paged once again, fix PR #9 pending; no trading change). Reliability Batch B3 — Findings #454/#455 and migration 039 — DEPLOYED Sep 26, 2026 23:29 UTC (deploy #13 00607c9, the exact approved SHA, pure fast-forward: paced IBKR in-process recovery with reset/latch truth; a durable DB-write spool for entry/exit/execution/reconciliation/live-trade writes; 039 idempotency keys; the live-trades poison-row loop (trades.token_label varchar(20) vs a 22-character outcome; 26 CRITICAL alerts 23:32:47–23:37:46 UTC; polymarket-live-trades stopped 23:39:08 by operator decision) was fixed by #481 (deploy #14 28b572b, Sep 27 00:33 UTC: poison rows quarantined once and marked seen, market-data poison WARN only, collector restarted) and #482 (deploy #15 860af84, 02:43 UTC: migration 042 widened token_label to varchar(64); the quarantined trades were released and the spool was empty at 02:51); the #454 soak (next 04:13 reset and 06:00 AutoRestart) is pending; no strategy change). The #479 sampler-grace race hardening DEPLOYED Sep 26, 2026 22:39 UTC (deploy #12 2ecb02c: one systemctl show snapshot per evaluation, a restart-loop FAIL that overrides the grace, the untrusted-address WARN checked before the stale grace; the deploy's own dashboard restart still raced the new monitor's first cycle and paged once, resolved five minutes later — follow-up OPEN; no trading change). Finding #467 and the #479 sampler restart-grace fix DEPLOYED Sep 26, 2026 22:19 UTC (deploy #11 3138aca: dashboard response secret-redaction filter; FastAPI auto-docs off and the three inert write endpoints removed; the IBKR account, wallet and server identifiers masked on public pages, docs included (one open gap: /api/btc5m/docs/doc_map still shows the raw WALLET_NAGI value, the six-character prefix of the third-party benchmark maker's wallet (not a full address, not Marius's wallet); a code follow-up is OPEN); the sampler rules tolerate a deploy restart for 120 s; no trading change). Findings #477/#480 DEPLOYED Sep 26, 2026 21:56 UTC (deploy #9 0e985dd: guarded data migrations 040 then 041 applied — #477 one P&L definition for closes and corrections plus a reconciler P&L-consistency check, BBW 274/307 −121.52 → −282.10 and SNOW 75 net −9.96 → −1.16; #480 pg8000 covered-channel lookup typed, the unaudited legacy sync retired, the four false leak rows relabelled timing_channel_mismatch; the Phase-1 net from the DB is now −$922.28 (n=13), matching the record; no strategy or admission change). Findings #457/#478/#479 DEPLOYED Sep 26, 2026 20:55 UTC (deploy #8 614a29d: #457 committed secrets scrubbed from HEAD + hash tripwire, the doadmin reset DONE 20:49:21 UTC before the deploy; #478 C-Q2/G-RS count trigger, 2/12; #479 per-service application_name + DB activity sampler — the post-deploy evaluator defect (security:db_client_activity could not evaluate, so the foreign-client page was inactive 20:58–21:33 UTC) is fixed DEPLOYED Sep 26, 2026 21:30 UTC (deploy #10 f28c0da); the rule reads ok; no live trading behaviour change). Findings #473/#474/#475 reliability follow-ups + docs sweep DEPLOYED Sep 26, 2026 19:05 UTC (deploy #7 a695050: #473 requirements pinned to the prod venv — the deploy never runs pip, prod pip freeze equals constraints.txt; #474 migration-baseline check — prod --check-baseline 5/5, the deploy ran no DDL; #475 C-Q2 detection replay, offline research tooling; docs sweep — Phase-1 P&L record, dashboard findings parser, drift-audit C10/C12, comment-only code edits; no live trading behaviour change). Finding #468 prod checkout hygiene DEPLOYED Sep 26, 2026 15:15 UTC (deploy #5 f130113; .gitignore + OPERATIONS §8.A2 + test only, no runtime change; prod git status --porcelain empty; prod moves DONE 14:59 UTC). Reliability Batch A DEPLOYED Sep 26, 2026 (deploy #1 884e528 10:18 UTC; #445 fix + F-7/F-8 deploy #2 3d7f1b1 11:48 UTC; apt-daily-upgrade hold ended 11:27 UTC): Finding #453 Monday 28 Sep stall stopgap (300 s dated watchdog budget, self-expiring Tue 00:00 ET); Finding #445 dashboard hypertable SQL bounded + Sep 25 read-only incident record (7-day coverage cached 6 h; per-statement transactions; 60 s statement timeout); Finding #444 deploy migration ledger (029 never re-runs; DDL under lock_timeout; output logged); Finding #443 needrestart covers the live xvfb.service/x11vnc.service (deploy-time coverage check; apt timer hold ended operator-attended at 11:27 UTC). Previous production baseline was #442 (DEPLOYED Sep 13, 2026). Finding #442 earnings-date join + 21-day verifier lookahead + timeline DATE SPLIT DEPLOYED Sep 13, 2026 (CBRL Wed 23 no longer wears Sep 14; verify 5→21 days; votes persist on confirm-in-place; #349/#424 leftover). Finding #441 1102 no nested util.run; attach-bounce after IB() reset DEPLOYED Sep 9, 2026 (04:14 1102 then 5h reset loop; farm sticky blocked bounce; #440 leftover). Finding #440 same-thread req after 2FA DEPLOYED Sep 7, 2026 (heartbeat/reconnect must not util.run on another loop; #87 leftover; 03:57 RESTORED then 40s Mode B). Finding #439 weekly last-resort 2FA budget DEPLOYED Sep 5, 2026 (one Python recycle per Sunday week; 7d cooldown; Sunday 06:00–18:00 UTC quiet; #438 leftover). Finding #438 last-resort recycle DEPLOYED Sep 5, 2026 (P1 req first; P2 2FA only Sunday or after IB() reset failed; #435 age/cooldown; #437 leftover). Finding #437 Saturday stay-connected DEPLOYED Sep 5, 2026 (recycle-off periodic IB() reset; heartbeat keeps probing; weekday AutoRestart reattach no bounce; attach down_since continuous; #436 leftover). Finding #436 weekly-only Sunday 2FA DEPLOYED Sep 3, 2026 (recycle-off default; Mode A/B keep probing; Mon–Sat session preserve; Sunday/crash attach-only; #434 leftover). Finding #435 recycle-once DEPLOYED Sep 3 (Mode B in-place first; no 2FA while Gateway young / cooldown / late-watch; 06:27/06:48/07:14 leftover). Finding #434 stay connected first DEPLOYED Sep 3 (recycle-on even with a live book; farm window still no-2FA; #430 leftover). Finding #433 keep probing when weekly 2FA holds recycle off DEPLOYED Sep 3 (Mode B exhaust-and-stop has no next step; header follows request_path_ok; 3 Sep 05:16 leftover). Finding #432 Mode A daily reconnect without 2FA DEPLOYED Sep 3 (1100 sticky farm probe until durable success; 900s timer no longer exhausts; Mode B leftover AMENDED by #433; recycle stays off). Finding #431 channel-window already-evaluated DEPLOYED Sep 2 (AEO C-1 generate_signal covers; any non-timing row; true timing-only still FAIL). Finding #430 weekly 2FA contract DEPLOYED Sep 2 (standing Gateway recycle off; midweek ibgateway-ibc restart was the extra 2FA; Sunday 06:00 stays; true needs GUARD-OVERRIDE-UNTIL). Finding #429 late-login quiet + Mode B window DEPLOYED Sep 2 (2FA resets 1800s quiet; 8 heartbeat fails/600s exhaust). Finding #428 late-login attach DEPLOYED Aug 30 (2FA abort keeps watching IBC+:4001; systemd-run bounce; flat-book re-check in that call). Finding #427 farm-recovery + standing recycle DEPLOYED Aug 29 (1100 keeps 900s probe window — no 61s zombie-exhaust; no-1100 TCP-up still #426 exhaust; flat-book auto-recycle Gateway then trader; IBC 06:00 is not recycle). Finding #426 Gateway zombie DEPLOYED Aug 28 (durable-only healthy; TCP-up trip exhausts not 300s loop; no client 99 on dead path; recycle CONFIRMED 11:22 UTC). Finding #425 etfstack VIX fallbacks DEPLOYED Aug 28 (Yahoo chart KeyError → history → Cboe → last-good cache). Finding #424 ASO official IR DEPLOYED (Sep 1 disputed → Sep 9 BMO confirmed). Finding #423 trade-reconcile BBW#274 DEPLOYED (live log + LMT fill + IBKR ingest). Finding #422 Trader Session READY DEPLOYED (Connected, not Unknown). ** Finding #421 IBKR one session machine DEPLOYED — TCP-up always in-place; connect/1102 clears REAL_DROP; bind errorEvent before connect; 1102 probes in place; STILL DISCONNECTED only if TCP down; PATH_DEAD/EXHAUSTED win the header; empty live book no ghost last-good stops; #420 1100-misclassify leftovers AMENDED; trader bounce. Finding #420 IBKR nightly-1100 leftovers DEPLOYED — CancelledError swallow; 1100 skips in-place exhaust; skip VIX/snapshot flood; UNREADABLE not on live book; last-good stops kept; recon-guard 1800 after exhaust; #419 AMENDED; trader bounce. Finding #419 IBKR in-place reconnect escape hatch DEPLOYED — quiet heartbeat while TCP up; one IB() reset then stop; no infinite 60s; leftover timeout cancel; UNREADABLE not on live book; #418/#416 AMENDED; trader bounce. Finding #418 IBKR reconnect amplifier DEPLOYED — keep TCP session on durable-health fail; last-good book; skip 412 reload; #415 AMENDED; trader bounce. Finding #417 needrestart + overnight trades freshness DEPLOYED — live-trades + integrity-monitor excluded; overnight 180 min / RTH 60 min. Finding #416 unverifiable IBKR ≠ missing DEPLOYED — IBKR UNREADABLE vs MISSING. Finding #415 IBKR latch leftover DEPLOYED — re-trip only inside durable probe; 300s reconnect while dead; #413 AMENDED; trader bounce. Finding #414 channel-window leak already-admitted suppress DEPLOYED — ignore timing_channel_leak when same condition_id already signal_emitted/admitted; DKS Aug 25 false #384; trader + integrity bounced. Finding #413 IBKR request-path latch hysteresis DEPLOYED — 3 successes incl. qualify to clear; 5 min reconnect cooldown; no ibkr_disconnect on #412 trips; re-trip leftover AMENDED by #415. Finding #412 IBKR request-path latch DEPLOYED — 3 consecutive bounded_request failures block trading + page; clear/reconnect/alert AMENDED by #413. Finding #411 orderbook-archive dual-limit pacing DEPLOYED — 18/run, 6h kill-clock, 00:35+06:35 UTC. Trader not bounced.) Finding #410 ship-verification letter-audit (record only). Finding #409 DONE-CLAIM + C10/C11 DEPLOYED Aug 22, 2026 15:23:13 UTC SHA defe78f. Finding #408 leftover-orchestrator twins DEPLOYED Aug 22, 2026 14:49:44 UTC SHA 4e4ffa1. One persist helper + one missed-exit/scheduled-exit helper. This docs-stamp is a later SHA than the trader process. Finding #407 HTTP quota + five lean memory.md §F/§H clauses DEPLOYED Aug 22, 2026 13:39:29 UTC SHA 7fbfe05. T2 leftover HTTP quota closed. #401–#406 DEPLOYED Aug 22, 2026 07:53:22 UTC SHA 88014f4. #393–#400 remain DEPLOYED from the Aug 21 weekend restarts. See FINDINGS for status markers. Prior 2026-08-21 — Finding #374 doc-drift framework: the ⚙️ CONFIGURATION VALUES section below now cites SSOT (src/earnings/config.py / STRATEGY.md) instead of copying numbers — see docs/DOCUMENTATION_DISCIPLINE.md. Previous full staleness re-audit: 2026-05-31, Finding #322.b — all 29 files (3 dashboard render sources + 26 docs, ~47k lines) read line-by-line; ~110 stale current-state claims fixed (docs + dashboard render code; NO trading change). Operator caught #322 as a shortcut (it only covered dashboard.html static HTML, skipping the Python that generates the dashboard + all docs); this is the complete re-do per "absolutely everything, not a single line skipped." See the "What changed this week" bullet + FINDINGS #322.b for the full per-file breakdown. Earlier same day — Finding #321 Strategy-analysis data completeness: Polygon backfill + full simulator re-validation (data backfill; NO code/strategy change) — operator directive: Polygon was bought for COMPLETE data; audit + backfill gaps, THEN full simulator run. Audit found 472 PAST counterfactual-P&L gaps in polymarket_opportunity_log (price_data_unavailable but equity_bars_1m existed → original fetch failed transiently + never retried) + ~48 earnings_markets outcome/return gaps. Backfilled via compute_counterfactual_pnl.py (counterfactual P&L 1,288→1,755 priced; 30 remain = 25 future + 5 delisted) + outcome_resolver.py (stock_return 99.8% of past markets; actual_beat 948/961). Validation batch repriced 20/20 via Polygon before the full run. Full re-run on COMPLETE data — every conclusion HOLDS: capacity $1k gate KEEP +$18.11/wk (was +$16.40); regime #318 KEEP compound_v1 (identical); depth #319 KEEP gate (identical). The backfill VALIDATED the shipped KEEP decisions rather than overturning them. Key insight: the _CF_SQL universe is filter-limited (volume≥$1k drops 713, conv≥0.90/small-cap drops 369 → 590 raw → 441 post-dedup), NOT data-limited — so completeness improved power/accuracy without changing conclusions. Corrected the #320 Universe View label (it regenerates on every report build via analyze_universe_hit_rate, not a 4h manual job). Earlier same day — Finding #320 Phase 4 (remainder): Unified Simulator dashboard consolidation (dashboard-only; NO trading change) — completes the Phase-3-dependent half of Phase 4 (now un-gated since Phase 3 PR 1-5 is complete). NEW 🧪 Unified Simulator umbrella on the Strategy tab framing one canonical engine over its arms: live stop-loss (Finding-A), capacity-aware (Phase 3 Capacity-Aware Sweeps + Gate Retune), selection-rule research (static regime #318 + depth #319 KEEP cards), universe signal-quality (Universe View, folded + honestly Apr-25-baseline-labeled). Archived the Q3-2025 Backtest Scenario Comparison + BMO/AMC Timing Matrix off the canonical tab (a third historical population that fragmented "which number do I trust?"; producers + JSON retained). Consolidated the 5 overlapping breakdown widgets → 2 canonical (removed Live Performance Snapshot; kept Direction+Timing Matrix + Performance Breakdowns). Operator chose unify-and-preserve (not literal delete-all-3, which would lose unique live-stop-loss + universe-PM-accuracy evidence) + static regime/depth cards + include-the-breakdowns-task. Verified: both inline JS blocks pass node --check, Jinja parses, div balance preserved, removed-DOM-ids only referenced by 3 now-dead (never-called) render fns. Dashboard-only; deployed (dashboard restart). Earlier same day — Finding #319 PR 5: Depth-gate v2 widened reconstruction — the "unreliable orderbook" skip is a roughly break-even low-information-signal filter → KEEP (offline-only; closes Finding #283; Phase 3 simulator PR 1-5 COMPLETE) — NEW scripts/phase3_simulator/depth_gate_variants.py (decompose score into min-depth/spread/asymmetry + variants: remove_all / carveout_real_trade [#283 hyp-#3] / spread_gate_only / depth_gate_only) + replay_depth_gate_v2.py (replays the REAL ConvictionCalculator point-in-time over the full 915-market universe → 22 BINDING depth kills [gate fired AND clears volume + #202/#207 skips], recovers raw orderbook + reliability_reason, prices counterfactual equity P&L via run_counterfactual_for_row). Answer to operator's "has it ever saved us from a bad trade?": YES — 13 losers BLOCKED (−$142.56), 9 winners KILLED (+$165.09), net removal +$22.54 over ~7 months (≈ +$1/trade = noise). Kill cause: 18/22 snapshot_reliability (hollow 1¢/99¢ book → sparse/unstable price-snapshot conviction), 1 spread, 3 no-orderbook → it's a low-information-signal filter, not the pure spread gate #283 implied. The earlier n=4 "only costs us / blocked 0 losers" was a small-sample mirage — widening (operator-requested) reversed it. Removal/carve-out (remove_all +$22.54, carveout +$22.78 ≈ identical) adds variance for ~$0 EV. Operator-confirmed KEEP; production conviction_calculator/signal_generator depth check UNCHANGED. 395 tests PASS (+20). NO PRODUCTION CHANGE, NO deploy. Earlier same day — Finding #318 PR 4: Regime classifier v2 sweep — faster detectors add no robust ROI → KEEP compound_v1 (offline-only; closes Finding #278) — NEW scripts/phase3_simulator/regime_classifiers.py (point-in-time SpyDailyHistory + 6 classifiers: compound_v1 baseline + compound_or_dd5 + below_ema20 + below_ma50 + dd_3pct + compound_symmetric; VIX deferred to Phase 4) + replay_regime_v2.py (re-classify regime point-in-time prior-close → re-apply Rule #1 → recompute P&L via run_sizing_sweep + apply_sizing_gauntlet vs compound_v1, on BOTH live-only primary + capacity-aware 1.40 cross-check). Prod (568-signal universe): below_ema20 (fastest, skips ~40% of LONGs vs compound's ~17%) gains +$70.31 PASS on live-only but −$17.02 FAIL-EX-TOP-2 on the capacity-aware universe — the live-only win is selection luck that doesn't generalize (#315 "don't look in isolation"); its only durable benefit is a better worst-week tail at ~breakeven P&L (a downside trade-off, not a money-maker). All other candidates inert or FAIL. PIT compound_v1 reproduces the live recorded regime 95.3%. Operator-confirmed KEEP compound_v1; no classifier swap; production market_regime_provider.py UNCHANGED. 375 tests PASS (+22). NO PRODUCTION CHANGE, NO deploy (offline analysis). Earlier same day — Finding #314.e PR 3.b.3: 4 deferred SKIP-rule candidates — real-scaled + bias gauntlet (ANALYSIS-ONLY, ship deferred to operator) — re-evaluated the 4 Finding #313 deferred SKIP cells REAL-SCALED (not hypothetical $10k-notional) + capacity-aware. NEW scripts/phase3_simulator/replay_skip_rules.py. Real-scaling corrected 2 cells: Large+SHORT+$2k-$5k is only n=3 (not 12); Large+LONG[.80,.90)+$1k-$2k is net-POSITIVE (+$0.93 avg, hyp said −$20). Gauntlet verdicts: skip Large+LONG+≥$10k +$2.04/wk PASS; skip Mid+LONG+$1k-$2k +$1.12/wk PASS; skip Large+SHORT+$2k-$5k FAIL (n=3); skip Large+LONG+$1k-$2k −$0.82/wk FAIL (cell positive); combined skip-all-4 +$5.42/wk PASS (maxDD −$220, WR 39.1%). ANALYSIS-ONLY (operator ship-bar=C): nothing shipped; per-candidate ship decision pending operator. 313/313 tests PASS (+10). NO PRODUCTION CHANGE. Earlier same day — Finding #314.d PR 3.b.5 SHIPPED: Hybrid seasonal gate variant — KEEP uniform $1k — evaluated operator's "$2k peak-weeks / $1k off-peak" idea. Verified first (operator directive): academic peak-week classifier TRACKS PM data (peak windows hold 57.7% of 977 earnings markets / 60% of vol≥$1k). NEW scripts/phase3_simulator/replay_gate_hybrid.py — hybrid = uniform $1k gate + peak-week sub-$2k skip predicate; reuses run_sizing_sweep. Prod run (568 signals): hybrid removes 144 peak-week $1k-$2k signals → +$8.08/wk vs uniform $1k +$16.86/wk (Δ −$8.78/wk, FAIL-SPILLOVER), landing almost back at uniform $2k (+$6.70/wk). Operator premise "$1k is noise in peak weeks" REFUTED — those markets are net-positive (#314.b $1k-$2k bucket +$423), removing them gives back most of #311's lift; capacity-reallocation doesn't rescue it (cap-skips 58→5 but freed slots not usefully refilled). Honest note: pre-build earnings_markets diagnostic under-counted footprint (31 via cumulative volume vs 144 in simulator universe — different volume fields). DECISION: KEEP uniform $1k (Finding #311 unchanged). 303/303 tests PASS (+7). NO PRODUCTION CHANGE. Earlier same day — Finding #314.c PR 3.b.4 SHIPPED: Sizing variants sweep — KEEP 30/22/15 — first sweep on the size-aware capital simulator. capital_simulator.simulate_one_variant() gained scale_pnl_with_size flag (default OFF — #314.b + SKIP sweeps byte-identical; design doc §12 PR3 "P&L = realized_pnl_pct × new_position_value") + realized_pnl_credited on AdmissionDecision; NEW gauntlet.apply_sizing_gauntlet() (per-trade delta over union of admitted trades); NEW scripts/phase3_simulator/replay_sizing_variants.py (8 variants, per-period + per-bucket + gauntlet vs baseline + Pareto). Prod run (568 signals / 36 wk / $2.5k × 2.0 = $5k max gross): operator's "lower sizing → more positions → more P&L" hypothesis REFUTED. Lowering sizing admits more (20/15/10 → 437 vs baseline 409, capacity-skips 58→30) but total P&L DROPS −$6.80/wk (FAIL-SPILLOVER — marginal capacity-freed trades are low-EV while shrinking good positions costs more). Only positive-Δ variants (flat 25/25/25 +$1.41/wk, 35/25/18 +$0.75/wk) FAIL the gauntlet (single-name / outlier dominated) AND worsen maxDD/5p tail risk. DECISION: KEEP production 30/22/15 (Finding #211 unchanged). 296/296 phase3_simulator tests PASS (+14). NO PRODUCTION BEHAVIOR CHANGE (offline analysis). Earlier (LATE LATE evening May 30) — Finding #314.b PR 3.b.2 SHIPPED: Capacity-aware re-validation of Finding #311 — first sweep landing on the capital simulator foundation. NEW scripts/phase3_simulator/replay_capacity_aware.py orchestration script. Run on prod (568 signals = 127 live + 441 counterfactual over 36 weeks): post-#311 ($1k gate) admits 409 / total +$607 / +$16.86/wk; pre-#311 ($2k gate) admits 295 / total +$241 / +$6.70/wk; Δ +$10.16/wk (about HALF the optimistic +$21/wk Phase γ+δ projection — gap is capacity contention: 58 capacity-rejected at $1k vs 5 at $2k). $1k-$2k bucket admitted (n=132): avg +$3.20, WR 31.8%, total +$423 → POSITIVE contribution; operator's earlier concern (from live n=10 = -$0.77 avg) not confirmed at scale. max_capital_used=$4,949 (99% of $5,000 cap) — capacity binding. Decision UNCHANGED: KEEP $1k gate as shipped; real lift confirmed at +$10/wk (~+$520/yr ≈ 21% return-on-capital). Earlier same day (LATE LATE evening) — Finding #314 PR 3.b.1 SHIPPED: Capital simulator foundation — NEW scripts/phase3_simulator/capital_simulator.py (~365 lines). Per-event timestep simulator that models capacity-constrained admission across overlapping positions. CapitalSimSignal (live + counterfactual signal input) + SizingVariant (declarative variant spec with extra_skip_predicates + mcap_pct + gate_volume_min) + AdmissionDecision (audit trail) + CapacitySimResult (aggregate). simulate_one_variant() walks chronologically-sorted signals through a heap of (exit_dt, capital_reserved) tuples; per signal: free expired positions → check variant skip rules → compute position size (variant_pct/prod_pct ratio scaling) → capacity check (current + new ≤ trading_capital × exposure_cap) → admit or skip with reason. Convenience constructors signal_from_live_trade() + signal_from_counterfactual_market() (latter applies per-tier empirical scaling per Audit Fix v3, lazy-imports EMPIRICAL_POSITION_BY_TIER from replay_production_and_candidates). 30 NEW unit tests; 282/282 phase3_simulator PASS local. NO PRODUCTION BEHAVIOR CHANGE (offline-only foundation). Subsequent commits: PR 3.b.2 (re-validate #311 capacity-aware) + PR 3.b.3 (4 SKIP rule candidates with bias gauntlet) + PR 3.b.4 (sizing variants sweep). Earlier same day (LATE LATE evening) — Finding #311 Re-validation Note — THIRD real-money strategy ship of the day. After data-driven first-principles analysis (operator-pushed back on initial proposal that misused $10k-notional hypothetical_pnl): kept Mar 9 mcap × conviction ordering (live LONG WR Large 45.7% n=35 > Mid 34.3% n=70 still validates), added volume-tier bump (live LONG WR jumps 36% NULL → 64% $2-5k → 83% $5k+) — new scale 0-8 (was 0-6). Capital cap raised 1.40 → 2.00 (matches Reg T overnight ceiling, +43% gross capacity; trading_capital base unchanged ~$2.5k). NYSE/NASDAQ branching dropped (verified 100% dead post Finding #185 Feb 15 unification — 126/127 trades had primary_exchange=NULL anyway). 617/618 tests PASS. Operator-flagged follow-up: re-validate $1k gate (Finding #311) against new volume-tier data — $1k-$2k bucket has historically been the only LOSING bucket (n=10, avg -$0.77, WR 30%). DEFERRED to Finding #313-followup once simulator framework is fully ready: 4 candidate SKIP rules (Mid+L80-90+$1-2k, Large+L80-90+$1-2k, Large+L80-90+$10k+, Large+SHORT+$2-5k). Earlier same day (LATE evening) — Finding #312 SHIPPED: v2 #6 Daily 3-stop session breaker + DirectionPolicySpec doc-bug fix — SECOND real-money strategy ship of the day. NEW src/earnings/session_stop_breaker.py (pure-decision module: SessionBreakerAction enum + decide_session_stop_breaker() + COUNT_STOPS_TODAY_SQL constant). signal_generator._build_trading_signal now calls _count_todays_stops() after Queue Rule #1 long_downtrend; if ≥ Config.SESSION_STOP_BREAKER_THRESHOLD (default 3) stops fired today (ET calendar day) → return ('session_3_stop_breaker'). Defaults SESSION_STOP_BREAKER_ENABLED=True. Historical evidence: 9 sessions in production with ≥3 stop-exits in a single ET-day (2026-02-03 (3), -02-05 (3), -02-10 (4), -02-19 (5), -02-26 (5), -03-03 (3), -03-05 (4), -04-22 (3), -04-29 (3)). Stop classification uses exit_reason ILIKE '%stop%' per Finding #233 corrected stop-rate (covers stop_loss:hardware + stop_loss:hardware_reconciled + instant_exit:stop_loss_backup; avoids broken stop_triggered boolean). Lift: +$1/wk + ~-$50 Max DD (heuristic; defensive rule). Plus DirectionPolicySpec correction in scripts/phase3_simulator/rules.py: production_strategy_may30() factory now accurately models prod thresholds. Was: long_threshold=0.55, short_threshold=0.45, conviction_fn="abs_distance_x2". Now: long_threshold=0.70, short_threshold=0.30, NEW long_max_threshold=0.90 (Finding #202 over-confident skip), conviction_fn="raw_prob" (production: ConvictionCalculator returns BEAT-prob directly, NOT |prob-0.5|×2). Dashboard Live Config Direction row + Selection row updated to match. 79 tests PASS local (22 session_stop_breaker pure + 8 integration + 38 phase3_simulator + 11 regression). Earlier (May 30 evening) — Finding #311 SHIPPED: gate_uniform_$1000 production change — first real-money strategy ship since Mar 9, 2026. SIGNAL_MIN_VOLUME_TOTAL default lowered $2,000 → $1,000 in src/earnings/config.py. Operator-confirmed after thorough correctness audit found 6 errors in earlier simulator outputs (E1-E6), all fixed via per-tier empirical scaling (Large $537, Mid $339, Small $205) replacing hardcoded $245 + memory K outliers standardized across production+candidates. Phase γ+δ replay shows: Δ E[wk] +$21/wk (~+$1,092/yr), max DD impact -$46 (still within -$735 target = 30% of $2,450 capital), outlier alarm fixed (production ex-top-5 -$152 → +$582 post-admit). All 9 strategy components formalized in production_strategy_may30() factory (renamed from may29; backward-compat alias preserved). Earlier (May 30) — Phase β + γ + δ + ζ shipped: full unified strategy framework (5-KPI engine + per-tier scaling audit fix + Pareto frontier verified + simulator-driven Top-5 in cockpit). Earlier (May 30 morning) — Phase ε.1 through ε.5 + α: Operator Cockpit (4 layers: target strip + 5 KPIs + live config + Top-5) + Section Index navigation + Strategy formalized as 9-component object. Earlier (May 29 early morning — Finding #310.c shipped: Phase 3 simulator PR 2.c FINAL — gate-retune sweep machinery + dashboard section + admission gauntlet (PR 2 COMPLETE) = NEW scripts/phase3_simulator/gate_retune_sweep.py (~280 lines) with ShipEligibility/VariantSweepResult/SweepResult dataclasses + evaluate_conservative_ship_eligibility() Q3 (C) 3-condition check + run_gate_retune_sweep_on_universe() deterministic + run_gate_retune_sweep() SQL-loading. NEW gauntlet.apply_admission_gauntlet() entry point reusing existing 5-check helpers via duck-typed AdmissionResult (added @property symbol + @property entry_time). NEW output.write_sweep_results_json() schema=phase3_sim_gate_retune.v1. Extended runner.py --run-gate-retune-sweep. Added strategy_performance_analysis.build_phase3_sim_gate_retune() helper. Added new dashboard 🧪 Phase 3 — Gate Retune Sweep section + renderPhase3SimGateRetune() JS. 25 NEW unit tests; 250/250 PASS local. PR 2 COMPLETE. NO production behavior change. Operator AskQuestion per qualifying variant is next step. PR 3 (sizing + priority + exposure-cap) begins next session. Earlier (May 29) — Finding #310.b shipped: Phase 3 simulator PR 2.b — counterfactual_admission.py (load opp_log low-volume markets + per-variant admission filter) = NEW scripts/phase3_simulator/counterfactual_admission.py (~230 lines) with CounterfactualMarket frozen dataclass + load_counterfactual_markets() SQL loader (262 usable rows out of 540 — USABLE_PRICES_SOURCES filter excludes 278 with 'none' source = Polygon fetch failed = $0 placeholder) + evaluate_admission(variant, market) per-variant filter + evaluate_variant_admissions() per-variant scoring + AdmissionResult dataclass + summary_dict() quick aggregate. §A pre-coding INFERENCE DISCIPLINE catch (6th this session-arc): original PR 2.b design assumed counterfactual replay would re-fetch bars + re-compute stops + re-execute simulator. Pre-coding schema inspection revealed polymarket_opportunity_log already has hypothetical_entry_price + hypothetical_exit_price + hypothetical_stop_hit + hypothetical_pnl + hypothetical_pnl_pct + intended_* fields pre-populated by compute_counterfactual_pnl.py (Finding #308 re-run). Massive scope reduction. Pre-coding evidence: 262 usable markets (all in collapse period, max volume $1,953 → no production-gate admission); $1k admits 96 / $500 admits 146 / $250 admits 181 / $100 admits 196 / $0 admits 262; aggregate +$3,180 if all admitted (avg +$12.14/trade). Strong signal pending PR 2.c gauntlet vetting. 20 NEW unit tests in 6 classes; 225/225 PASS local Python 3.11. NO production behavior change. PR 2.c FINAL next: gauntlet per-variant×per-segment + output schema + runner --variant-set + 3-condition conservative-ship check + phase3_sim_gate_retune dashboard section + run on prod + AskQuestion ship decisions. Earlier (May 28-29) — Finding #310 shipped: Seasonality preflight + structural-collapse discovery + PR 2.a declarative scaffolding (gate_variants.py + 11 variants + period segmenter + week-of-month classifier) = OPERATOR-REQUESTED preflight before PR 2 coding REFUTED the pure-seasonality hypothesis (Dec 2025 off-season was healthy 98.1% gate-pass + $14,522 median; Mar+2026 off-season collapsed to 27.8% + $0 median → REAL STRUCTURAL collapse starting March 2026, not seasonality). Operator AskQuestion 3-question pattern expanded PR 2 scope: Q1 SEGMENTED (healthy < 2026-03-01 / collapse / full), Q2 THRESHOLD + SEASONAL AXIS (11 unique variants = 6 thresholds × 2 modes uniform/seasonal_week with off-peak factor 0.5), Q3 SHIP CONSERVATIVE (ship only if gauntlet PASS on full-pop + ≥20 admitted markets in collapse + ex-top-2 stays positive). PR 2 split into 3 sub-commits: 2.a SHIPPED (declarative scaffolding + 51 unit tests; 205/205 PASS); 2.b NEXT (counterfactual_admission.py — synthesize Trade from opp_log skip_reason='low_volume' rows + delegate to replay_trade); 2.c FINAL (gauntlet aggregator + output schema + runner --variant-set + 3-condition ship check + phase3_sim_gate_retune dashboard section + run on prod + AskQuestion ship decisions). NO production behavior change. Earlier (May 28 LATE evening) — Finding #306.c shipped: Phase 3 simulator gauntlet.py + output.py + runner.py --run-gauntlet (fifth PR-1 ship of the day; THIRD/FINAL of 3 sub-commits for #306 — PR 1 COMPLETE) = NEW gauntlet.py (5-check bias gauntlet per memory §K with verdict dispatcher in priority order + VariantStats/CheckResult/GauntletResult frozen dataclasses + true_simulator_compliant=True) + NEW output.py (deterministic JSON schema per design doc §7.2 with sort_keys + mask_volatile_fields for reproducibility diffs + derive_ship_recommendation verdict→SHIP/DEFER/DROP mapping) + UPDATED runner.py (--run-gauntlet flag for full pipeline + footer cleanup, removed "#306.a stops here" staleness). 33 NEW tests; 154/154 PASS local Python 3.11. §10.3 H-rule self-validation acceptance gate: PASS (synthetic H-rule shape returns FAIL-EX-TOP-2). §10.2 reproducibility acceptance gate: PASS (2 runs same input = 0-byte diff after masking generated_at). PR 1 IS COMPLETE: all 5 PR 1 commits shipped (#306.a + #306.b + #306.c + #307 + #308); all 4 PR 1 acceptance gates PASS (§10.1 cross-check 100% match per #306.b §G probe + §10.2 reproducibility + §10.3 H-rule + Phase 0 §0.6 corrected per #308). PR 2 (volume-gate retune sweep) can begin next session. NO production behavior change (offline-only). Earlier (LATE evening) — Finding #306.b shipped: Phase 3 simulator cost_model + replay engine + reference_adapter + cross_check_baseline script (fourth PR-1 ship of the day, second of 3 sub-commits for #306) = NEW cost_model.py (CostEstimate + estimate_cost with proxy-flat-1pct default per Q2; latent pm_v2_orderbook path for PR 2+) + NEW reference_adapter.py (re-exports per Q1 HYBRID + _assert_reference_module_stable() signature-drift guard fired at import time with memory.md §M-pointing diagnostic) + NEW replay.py (ReplayContext + ReplayResult dataclasses + replay_trade + replay_population) + NEW cross_check_baseline.py (operator-runnable script implementing design doc §10.1 acceptance gate). §A pre-coding INFERENCE DISCIPLINE catch (fifth this session-arc): Trade.scheduled_exit_dt(cal) was MISSING from population.py — the reference's run_trade calls it, so the delegation would AttributeError. My #306.a TestReferenceParity only checked dataclass FIELDS, not METHODS. Fixed both: added scheduled_exit_dt + new TestReferenceParity.test_method_names_match_expected + test_scheduled_exit_dt_consistency_amc. 37 NEW tests; 121/121 PASS local Python 3.11 (79 phase3_simulator + 42 prior regression). NO production behavior change. §10.1 cross-check execution is part of #306.b §G probe runway. Earlier (LATE evening) — Finding #306.a shipped: Phase 3 simulator foundation skeleton (third PR-1 ship of the day, first of 3 sub-commits for #306) = NEW scripts/phase3_simulator/ package (__init__.py + population.py Trade+load_trades duplicated per Q1 HYBRID + rules.py declarative RuleSet dataclasses + baseline_ruleset factory + runner.py CLI stub) + NEW tests/unit/phase3_simulator/ (42 NEW tests: 18 population including 4 CHANGE-CONTRACT cross-check tests vs analyze_finding_a_full.Trade + 24 rules). 84/84 PASS local Python 3.11. NO production behavior change (offline-only simulator package; nothing in src/earnings/ or src/data_tracker/ references it). 3 operator AskQuestion decisions resolved BEFORE coding: Q1 = (C) HYBRID, Q2 = (A) FLAT 1% proxy spread (lands in #306.b cost_model.py), Q3 = (B) THREE INCREMENTAL MILESTONES. #306.b next = cost_model+replay+reference_adapter+baseline cross-check; #306.c final = gauntlet+H-rule self-validation+reproducibility+output+dashboard. After #306.c, PR 1 COMPLETE and PR 2 (volume-gate retune sweep) begins. Earlier (LATE evening) — Finding #308 shipped: Phase 3 simulator PR 1 second commit (ops-only) = compute_counterfactual_pnl.py re-run (78 pending rows processed, generate_signal 67.5%→69.1%, integrity 47/47 OK preserved) + structural-ceiling discovery (480+39 rows with NULL hyp_pnl are all direction_would_be='NONE' conviction-deadband signals → STRUCTURALLY un-simulatable, not a coverage gap; Phase 0 75% target was incomplete framing). Phase 3 simulator's signal_emitted 100% subset is sufficient for all 4-bundle analyses; no Phase 3 work blocked. Earlier (LATE evening) — Finding #307 shipped: Phase 3 simulator PR 1 first commit = snapshot_schema='pm_v2' conditional-label fix at signal_generator.py:1041 (Option A MINIMAL per design doc §9) + integrity rule null_rate:opp_log:pm_v2_post_deploy updated in lockstep (integrity_monitor.py:541 col changed snapshot_schema → method, same intent unaffected by new conditional semantic — memory.md §M cross-interface catch). Lockstep fix for original Finding #273 (deferred Apr 27 + re-confirmed May 28 afternoon). 6 new unit tests + 42/42 pre-commit regression (no regressions). PR 1 §A pre-coding revealed Bug #2 + #3 are ALREADY FIXED Apr 26 (Findings #234/#237 — third INFERENCE DISCIPLINE catch this session); operator AskQuestion approved revised smaller PR 1 scope = #306 foundation + #307 #273 fix (this commit) + #308 Bug #6 hyp_pnl re-run only (~3-5 days vs original 5-8). Design doc §3 + §12 + §15 updated to reflect corrected gate-1 understanding. The 1,055 pre-fix mislabeled rows remain as historical baseline (not retroactively unlabeled per §C surgical-changes + audit-trail discipline). Earlier (2026-05-28 evening) — Phase 3 unified simulator design doc shipped at docs/PHASE_3_SIMULATOR_DESIGN.md (867 lines, 16 sections); companion to docs/STRATEGY_ANALYSIS_ARCHITECTURE.md Phase 0 baseline. Operator chose all-4-bundle scope via AskQuestion → design doc concretizes 5-PR ship plan (~3-5 weeks): PR 1 foundation + Finding #273 fix lockstep + Bug #2/#3/#6 backfill; PR 2 volume-gate retune sweep (highest leverage per May-28 PM volume-collapse evidence); PR 3 sizing + priority + exposure-cap variants; PR 4 regime classifier v2; PR 5 depth-gate v2 decomposition. Phase 3 gates from Phase 0 doc §0.6 VERIFIED MET (equity_bars_1m 185 days/418 symbols/1.9M bars; actual_beat 94.9%; opportunity_log hyp_pnl 100% signal_emitted; Bug #5 stop_triggered fixed; remaining sp500/conv_at_exit/depth-stage gaps addressed in PR 1). 8 open operator-decision points in design doc §15 must resolve before PR 1 starts (Finding #273 fix scope, sp500 backfill vs forward-only, etc.). Earlier (2026-05-28 LATE afternoon) — Finding #305 shipped: RuleB (±90 min no-stop window) Phase 1 DRY-RUN — SECOND Phase-3 strategy rule landed ~3 hours after Rule #1 ship. Hardware cancel-and-rearm pattern (NOT soft-gate per Finding #228 lesson). Code: new src/earnings/ruleb_window_gate.py module (pure decision function, 8-value RuleBAction enum, fail-soft derive_earnings_release_dt helper) + new orchestrator methods _process_ruleb_window_gate / _ruleb_fire_cancel / _ruleb_fire_rearm / _ruleb_log_once + main-loop hook + 4 new env vars in Config (RULEB_ENABLED default False, RULEB_MODE default 'dry_run', RULEB_WINDOW_MINUTES default 90, RULEB_HARD_FALLBACK_MIN_BEFORE_EXIT default 30). Tests: 25 new in tests/unit/test_ruleb_window_gate.py, 36/36 PASS pre-commit on prod Python 3.12 venv (25 RuleB + 7 Rule #1 + 4 prior signal-test regression). Phase 1 RULEB_ENABLED=false default = NO production behavior change until operator explicitly enables; Phase 2 active-mode flip pending operator's review of dry-run logs over ~few days. Structural inconsistency caught during pre-coding (INFERENCE DISCIPLINE + NO SHORTCUTS): original Playbook §6.7 spec described a soft-gate that would deliver $0 EV (same as FA_soft retraction Finding #228); operator chose Path (A) hardware cancel-and-rearm via AskQuestion after agent surfaced inconsistency with comparison matrix + recommendation. State is DERIVED per-cycle (no DB column, no migration) per memory.md "IBKR is source of truth for live state" + §C surgical-changes. Bias-gauntlet PASS robustly on May-28 fresh sim n=121: ex-top-2 +$127/100 STRENGTHENED from Apr-25 +$123/100 baseline. Phase 1 known limitation flagged: restored positions (signal=None per position_manager:1155) cannot derive earnings_release_dt → graceful SKIP_NO_EARNINGS_DT; Phase 2 may add restart-safe lookup if dry-run shows this happens in practice. Earlier (2026-05-28 afternoon) — Finding #304 shipped: Queue Rule #1 (Skip LONGs in downtrend regime) — FIRST Phase-3 strategy rule landed ~50h after the May-26 un-gate. Code change: new _safe_get_regime() helper + skip block in _build_trading_signal at src/earnings/signal_generator.py (~40 lines). Tests: 7 new in tests/unit/test_signal_regime_gate.py (LONG×downtrend skip + 6 fail-soft/direction-asymmetry/regime-variation negative controls). 11/11 PASS (4 existing signal-test + 7 new) on prod's Python 3.12 venv pre-commit, no regressions. Bias-gauntlet PASS on May-28 fresh data: 22 LONG-downtrend trades / 5W (23 %) / −$136.30 net, ex-top-2 −$175 (MORE negative than headline → robust), 22/22 distinct tickers, full-population spillover positive. Sequence of work today: Finding #273 (originally next per "D then A") was reconsidered via AskQuestion to (E) DEFER per its original lockstep narrative — evidence surfaced #273's bug surface is 16× bigger than originally documented (1,055 NULL pm_v2 rows across 3 stages vs FINDINGS.md's "8 rows on 1 stage") AND fix scope is non-trivial without Phase 3 simulator schema knowledge. Net: Rule #1 ships first (this entry), Finding #273 stays deferred to lockstep with Phase 3 simulator. scripts/strategy_performance_analysis.py:build_action_queue queue rank-1 row flipped IN-QUEUE → IMPLEMENTED with implemented_date="May 28, 2026" + implemented_findings=["#304"]. Operational note: at current 1.9 % PM volume-gate pass rate (Finding #279 amendment), Rule #1's natural firing rate is ~0.4 % of all earnings markets — likely zero firings per month at current signal density; the rule's value is bias-gauntlet-locking + correct behavior in the next high-volume regime, not fast live evidence. Earlier (2026-05-28 afternoon) — Finding #279 Phase B amendment shipped (PM earnings-market volume collapse evidence folded into Phase B scope) as first piece of Phase 3 Strategy Review work; doc-only commit, no code/live-trading change. Triggered by operator's AskQuestion option (B) "fold #304 into #279" over a standalone Finding #304 entry. Pre-amendment data-gathering pass: re-ran scripts/strategy_performance_analysis.py (fresh 116 clean trades / +$212.21 net / WR 37.07 % / stop rate 51.72 % — 0 new clean closures since May 7 = 21-day strategy-execution drought; HPQ entered yesterday May 27 is the only post-drought entry, crossing the gate via Finding #282 Gamma-cumulative fallback path); re-ran scripts/analyze_finding_a_full.py (n=121 fresh, RuleB ex-top-2 strengthened +$123/100 → +$127/100, RuleG headline higher but ex-top-2 +$105 weaker, FA_hard retraction still holds — rearm_gap_open arm n=24 at -$1,545/100); direct SQL on earnings_markets LEFT JOIN markets USING (condition_id) confirmed Jan→May volume collapse at much greater severity than the May-19 chat-dump suggested — avg ~109× drop ($33,475 → $306), median ~1,847× drop, $2k-gate pass rate 52× collapse (100.0 % → 1.9 % = 3 of 154 May markets); NO Mid-cap market crossed the $2k gate in all of May (0/74). Phase 3 first-ship sequence per operator AskQuestion option "D then A, then re-evaluate": Finding #273 (pm_v2 NULL bid/spread data-capture fix) NEXT, then IN-QUEUE Rule #1 (Skip LONGs in downtrend), then re-evaluate. Earlier (2026-05-28 LATE-LATE morning) — Finding #303 shipped (deploy_from_github.sh pre-pull hygiene for recurring working-tree drift). Earlier (2026-05-28 LATE morning) — Finding #302 shipped (polymarket-backup.timer collision with apt-daily-upgrade.timer, RESOLVED). Earlier (2026-05-28 morning) — memory.md 🚨 INFERENCE DISCIPLINE rule codified as companion to NO SHORTCUTS. Earlier (2026-05-27 morning) — factual correction to the May-17 reconnect entry: I had incorrectly inferred "IBKR allowed session resume without re-2FA" from the fast (~16s) port-up time; operator clarified May 27 that every Sunday reconnect DOES require operator 2FA approval on phone — IBC server-side logs are blind to the human-side push approval, and fast port-up is consistent with quick mobile approval, NOT session resume. The downstream speculation that "live Sunday automation may be feasible as fully unattended" was unfounded and has been removed from docs/AGENT_HANDOFF.md §11. Paper Gateway (port 4002, etfstack subtree) genuinely may not require 2FA — different IBKR security tier — but that property is paper-specific per cf56485 and does NOT generalise to live. Earlier (2026-05-17 morning) — Finding #297 (api_health_checker WARN-but-exit-0 cry-wolf fix) shipped closing the pre-existing systemd_last_run:polymarket-api-health.service FAIL surfaced during #296's §G probe; operator approved option (b) via AskQuestion vs widen-threshold / leave-as-is / build-age-aware-structural-fix. 1-digit behavior change (exit(2) → exit(0) for warning branch) + 16-line context comment + logger.warning for visibility. error → exit 1 and healthy → exit 0 paths UNCHANGED — real failures still alert. §E exhaustive grep: 1 other exit(2) in repo (backup_database.py:287 for real S3 upload failure — correct, NOT same antipattern). Today's prior op: IBKR Sunday 2FA reconnect handled manually (06:48 UTC restart → operator approved 2FA push on mobile → port 4001 open ~16s after restart → trader reconnected at 06:49:01 with account U2…13 / net_liq $27,534.56). NOTE: the original May-17 entry incorrectly inferred "session resume / no 2FA needed" from the ~16s port-up time; operator clarified May 27 that every Sunday reconnect DOES require operator 2FA approval on phone — the IBC server-side logs are blind to the human-side approval step and rapid port-up is consistent with quick approval, not session-resume. Corrected May 27, 2026. Integrity-monitor returned to 45/45 OK baseline. Earlier — 2026-05-11 evening — Finding #296 (_check_systemd_unit_last_result branch reorder) shipped as the lowest-risk close-out of the 3 May-10 "observed-but-not-actioned" maintenance items per AskQuestion operator option (A) "ship #2 only". The other two (Sunday-2FA partial-automation timer + Sunday-aware artifact:ibkr_snapshot threshold) explicitly deferred — they change operator workflow and alert semantics and need explicit approval beyond "close the maintenance backlog." Integrity-monitor still 45/45 OK. Project remains in steady-state observation mode for ~15 days until May 26, 2026 Phase 3 un-gate. See docs/AGENT_HANDOFF.md §11 May-11 evening block + docs/FINDINGS.md #296 for full details. Earlier — 2026-05-10 early afternoon — 🎉 ALL operator-approved follow-ups + 3 newly-discovered side-effect findings SHIPPED in 12 commits today. Plus IBKR Sunday 2FA reconnect handled (gateway exited at 06:00 UTC; systemctl restart ibgateway-ibc spawned fresh Gateway; 2FA approved via mobile push; trader auto-reconnected at 10:01 UTC). Final state: 8/8 critical services active, integrity-monitor 40/40 OK, account U2…13 reconnected with no positions. NO operator-approved follow-ups remaining. Project is in steady-state observation mode for ~16 days until May 26, 2026 when Phase 3 Strategy Revamp un-gates (Finding #240's 30-day equity-bars window completes). NO-SHORTCUTS rule was exercised heavily today — 5 design tensions surfaced via AskQuestion instead of silent interpretation, operator overwhelmingly chose deeper / more-thorough options. Architectural patterns established: with conn.begin_nested(): SAVEPOINT for batch-writes (3 active code paths now); try-wraps-with-engine.begin for single-row writes (2 active code paths now); never trust stale-doc rule counts — always curl /api/integrity for live count when adding rules. #293: Polymarket Website URL renamed /finance/earnings → /finance (cached 404 confirmed long-term); fixed api_health_checker.py URL + deleted dead EarningsMarketSyncer class + removed silently-broken cron entry; wrapper scripts/sync_earnings_markets.py preserved per Finding #220. #294: trades.outcome widened VARCHAR(10) → VARCHAR(64), trades.side 10 → 20 (operator-approved option B "defense-in-depth"). Migration 028 + ORM lockstep. Hypertable-safe (PostgreSQL VARCHAR widening is non-rewriting; 907 chunks). §G probe: no disruption + 60 live_poll writes/10min healthy + 0 new InterfaceErrors. Earlier today: 🎉 ALL 4 plan-(C) operator-approved follow-ups shipped: #290 + #291 + #289.a + #289.b, plus cold-bootstrap close-out #292 + #288.v3. Project is squeaky clean for May 26 Phase 3 commission. #289.b root cause: save_trade had inner try/except inside with engine.begin() block; on inner SQL exception the catch swallowed it at DEBUG level (invisible), connection went into failed-tx state, engine.begin's exit tried to COMMIT, pg8000 raised InterfaceError. PLUS the failed trade_id was never added to _seen_trade_ids so the SAME bad trade re-attempted every 5s — ~12-13k InterfaceErrors/day for 10+ days (~118k total). Fix: try/with INVERSION + WARNING-level visibility + cascade closure via _seen_trade_ids.add on error path. #289.a was operator-approved option (B) full systemd-timer migration after AskQuestion surfaced the trade-offs vs (A) crontab patch. Root cause: Ubuntu /bin/sh is dash, source is bashism, cron's chained source venv && python && >> log short-circuited at the dash failure for 14 days. §E found same bug in sync_earnings_markets cron — fixed via SHELL=/bin/bash at top of crontab; api_health migrated to dedicated systemd timer with bash-c source pattern. #291 was RE-SCOPED from original spec (3 progress:* rules) to a state-file mtime defense after live-system analysis showed the proposed rules would duplicate existing freshness coverage — operator-approved via AskQuestion per NO SHORTCUTS rule. #290: 5 files deleted (watchdog{,_v3}.py + 3 deploy/.service files), 7 stale-reference surgical edits, 4 historical forward-pointer annotations. Earlier same morning: cold-bootstrap of polymarket-trades-backfill.service COMPLETE; #288 Tier B fully validated end-to-end; Finding #292 caught by §G probe and shipped same session — graceful handling of Polymarket data-api /trades offset hard-cap at 3000 (TSLA Q3 2025 was 1/929 affected; 144 high-volume markets all have pre-#288 Dome coverage so zero historical loss); Finding #288.v3 doc-completeness gap closed — 5 trade_dedupe.source labels coexist not 4 (backfill 2.17M rows from one-off Jan 31 bulk import was previously undocumented). Earlier morning: Dome EOL Apr 28, 2026 migration COMPLETE; "NO SHORTCUTS — EVER" codified as the #1 project rule after operator-flagged Tier C v1 shortcut; Tier A (#287) + Tier B (#288) + Tier C (#289) + Tier C v2 (#289.v2) all shipped same session. 8 dead Dome-dependent files deleted; live_trades_collector.py cosmetic rename; 6 docs surgically updated; check_dome_api replaced with check_polymarket_data_api. 4 next-session items remain (#289.a + #289.b + #290 + #291). Earlier May 9: pre-Phase-3 housekeeping batch COMPLETE: Findings #270 + #286 + #274 + #273 + #269 all addressed. #270 StartLimit* section moved across 5 live unit files via §E expansion; #286 dashboard cold-start race fixed as forced detour from #270 §G probe (_empty_summary was missing 13 TC-ROI fields from #284); #274 logrotate maxsize 500M with size-vs-maxsize semantics correction; #273 formally deferred to Phase 3 lockstep ship (joins May 26 bundle alongside #278/#279/#283); #269 RESOLVED via smart Option A** — new polymarket-trades-backfill.timer (weekly Sun 06:00 ET) + delta backfill via Dome start_time (T1) + smart re-fetch policy for active and recently-resolved markets (T2) + earnings-only scope by default (T3), CLI flags for ops control, cold-bootstrap-then-enable runbook in OPERATIONS.md, integrity-monitor rule with 8-day freshness. Earlier May 9: Finding #285 Phase A greedy-fill + exposure-cap logging shipped; Phase B rule re-evaluation deferred-post-Phase-3 alongside #279. See docs/AGENT_HANDOFF.md §11 for full Apr 21 → May 9 changelog. Rule rankings in docs/STRATEGY_PLAYBOOK.md §2 still unchanged since Apr 25.)

What changed this week (Apr 21 → Aug 22, 2026)

What changed earlier (Apr 21 → May 9, 2026)

For everything in detail, see docs/AGENT_HANDOFF.md §11.


🚨 MAIN DIRECTIVES (NON-NEGOTIABLE)

0. NO SHORTCUTS — EVER (THE TOP RULE)

This is the single most important rule on this project. It supersedes everything else when there's tension.

A shortcut is any deviation from full, surgical completion of a task to save agent effort, even when rationalized as "pragmatic" or "good enough." Specifically forbidden patterns include: - "Add a banner / TL;DR / summary instead of editing the body" - "Surgically update only the highest-impact lines" when the user asked for "all" - "Several refs updated" without an exact count from grep - "§E same-pattern check" without an exhaustive grep across the entire active surface - "I'll fix it in a follow-up" without explicit operator approval - "This is dead code per §C, leave it" used to skip a same-pattern sweep

Anchoring example (May 10, 2026 — Dome→Polymarket migration Tier C v1): agent took a banner-only shortcut on DATA_ARCHITECTURE.md (45 inline Dome refs) and said "several updates" on OPERATIONS.md (24 refs) without exhaustive audit. Operator caught it: "the shortcuts are extremely disturbing and make me question everything you did before! we can never take shortcuts for this project!!!! huge risk to capital." Fixed in #289.v2; codified as a permanent rule in .cursor/memory.md "NO SHORTCUTS — EVER" section + here + docs/AGENT_HANDOFF.md §11 + docs/STRATEGY_PLAYBOOK.md §1.

Why on this project specifically: live trading bot with real capital. Shortcuts in docs leak into operator confusion when alerts fire, future-agent confusion picking up the project, silent technical-debt that masks real bugs. The same "I'll just take this small shortcut" pattern applied to trading logic (sizing, stops, signals, exposure, exits) absolutely could cost capital. Train the discipline on the small stuff so it's automatic on the big.

If tempted to shortcut: STOP and ASK the operator explicitly. They will almost always say "no, do it properly." Memory.md §1 "Don't assume. Surface tradeoffs." applies — surface the temptation, don't act on it silently.

See .cursor/memory.md "NO SHORTCUTS — EVER" section for the full rule + self-audit prompts.

1. Read ALL Documents Before Acting

2. No Tech Debt in Database

3. Capital from IBKR, Not Hardcoded

4. Dashboard Shows What Trader Sees

5. Always Update Documentation

6. Never Assume, Always Ask

7. Position Verification (Added Jan 11, 2026, Updated Feb 7, 2026)


📁 KEY FILES & LOCATIONS

Core Trading System

src/earnings/
├── trading_orchestrator.py    # Thin coordinator - creates and wires managers
├── trading_mode_persistence.py # Shared mode persistence + self-healing default
├── position_manager.py        # Position CRUD, DB persistence, stop order tracking
├── exit_manager.py            # All exit paths: scheduled, instant, batch, stop backup
├── exposure.py                # Freed-capital + reservation free-book (one helper, #401)
├── reconciliation_manager.py  # IBKR/DB reconciliation, table sync
├── trading_utils.py           # Shared utilities: price, time, scheduling
├── position_state.py          # Position state machine (PENDING/OPEN/CLOSING/CLOSED/FAILED)
├── ibkr_position_cache.py     # Thread-safe IBKR position cache (5s TTL, source of truth)
├── event_bus.py               # Publish/subscribe event system (real-time stop fill detection)
├── risk_manager.py            # Position sizing, circuit breakers, stop-loss
├── signal_generator.py        # Signal generation, prioritization
├── polymarket_opportunity_log.py  # Research DB log (all PM candidates; Finding #221)
├── counterfactual_pricing.py     # Polygon/yfinance counterfactual sim for opportunity log
├── order_executor.py          # IBKR order execution
├── batch_order_manager.py     # Concurrent batch order execution
├── earnings_detector.py       # Earnings release detection
├── conviction_calculator.py   # Orderbook → conviction
├── earnings_timing.py         # SINGLE SOURCE OF TRUTH for BMO/AMC timing
├── timing_data/overrides.json # Persistent timing overrides
├── config.py                  # Centralized configuration with validation
├── types.py                   # Shared types (ActivePosition, MarketStatus, Result)
├── audit_logger.py            # Database audit trail
├── alerts.py                  # Telegram notifications
└── market_calendar.py         # NYSE calendar (2024-2028 holidays)

Architecture Note (Phase 4A - Feb 7, 2026): The TradingOrchestrator was split from a 4878-line monolith into 6 focused modules using dependency injection. Each manager receives only its specific dependencies via __init__. The orchestrator creates and wires all managers, then delegates to them.

IBKR Integration

src/ibkr/
├── client.py                 # IBKRClient for account/order operations
├── gateway_monitor.py        # Background health monitoring
└── fee_calculator.py         # Trade cost calculations

Dashboard

earnings_dashboard/
├── app.py                       # FastAPI application
├── secret_redaction.py          # Finding #467: response filter (defense in depth)
├── identifier_masking.py        # Finding #467.D: account/order/wallet/path masking
└── templates/dashboard.html     # Jinja2 template

Dashboard Reality Overlay: ibkr_reality panel compares DB positions vs live IBKR and verifies stop orders. Live IBKR data is read from the trading service snapshot file. Freshness is src/earnings/ibkr_snapshot.is_stale (Finding #472) — not IBKR_SNAPSHOT_INTERVAL_SEC. Public dashboard defense in depth (Finding #467): the dashboard is intentionally public. install_secret_redaction(app) (one line, right after app = FastAPI(...)) redacts secrets from every response; identifier_masking.py masks IBKR/wallet/host identifiers at each payload's source. FastAPI's auto-docs are disabled; three inert unauthenticated write endpoints were removed. See docs/FINDINGS.md #467.

Database Sync

src/db/
├── earnings_sync.py          # EarningsMarketSync: earnings_markets + MCAP backfill (Finding #219)
└── __init__.py

market_cap_category: Filled via sync enrichment, backfill_upcoming_null_mcaps(), and yfinance_limiter (Yahoo + Finnhub with FINNHUB_API_KEY). Hourly Gamma sync (src/data_tracker/collectors/earnings_market_fetcher.py) runs the backfill after each run. See docs/DATABASE_DIRECTIVES.md and Finding #219 in docs/FINDINGS.md.

Entry Points

run_earnings_trader.py        # Main trading service entry point
run_dashboard.py              # Earnings dashboard entry point
run_gateway_monitor.py        # IBKR gateway monitor service entry point

Deployment

deploy/
├── earnings-trader.service   # systemd service for trader
├── install_trader_service.sh # Installation script

Source Control & CI

Dashboard, Postgres, and strategy reporting (Mar 2026)


⚙️ CONFIGURATION VALUES

Position Sizing (SSOT: src/earnings/config.py + STRATEGY.md §4; updated Aug 21, 2026)

S2c equal-split (Finding #366, DEPLOYED Aug 21, 2026): with trading capital ≥ S2C_MIN_ACTIVATION_CAPITAL ($9k), enter every qualifying signal, split the free exposure budget equally, shrink to fit, never above S2C_MAX_POSITION_PCT (25%) per name. Below the gate: FLAT 25% all mcap tiers (#314.f; history: 30/22/15 per #211 retired Jul 6, 2026). Stop-losses by tier (Finding #202): Large 3% · Mid 4% · Small 5%.

Fractional Shares: ✅ Enabled - System uses fractional shares for NYSE/NASDAQ/AMEX stocks (4 decimal precision). Falls back to whole shares for ineligible stocks.

Exit Verification: ✅ Enabled (Jan 11, 2026) - Every exit order verifies IBKR position is flat before marking DB closed. Alerts on verification failures.

Position Reconciliation: ✅ Enabled (Jan 11, 2026) - On startup, reconciles DB positions with IBKR. Detects orphaned positions and external trades.

Circuit Breakers (SSOT: src/earnings/risk_manager.py class docstring; updated Aug 21, 2026 — CLB-1/#366)

Trigger Action
Daily loss 5% (DAILY_LOSS_LIMIT) Sole loss brake — stop for the day; DB-anchored; resumes at true next ET open (A-6/#366)
Day P&L ≤ −2.5% / ≤ −4% Sizes ×0.50 / ×0.25 (re-keyed from the old loss-streak tiers)
Drawdown 30% / 40% / 50% Reduce sizes 50% / 75% / full pause
VIX > 30 / > 35 Reduce sizes 50% / no new positions
S&P 500 < -5% Pause all entries
Session-stop breaker: 4 stop-exits per ET day Pause signal generation for the session (#312; threshold 4 per #335.b)

(The consecutive-loss breaker — 3/5/7-loss pauses — was RETIRED by CLB-1, Finding #366, deployed Aug 21, 2026.)

Config Overrides (Env Vars)

Trading Schedule (ET)

Time Action
10:32 AM BMO Exit (1h after open - captures first-hour gains)
10:34 AM AMC Entry (1h 4 min after open)
3:57 PM AMC Exit (1 min buffer before BMO entry)
3:58 PM BMO Entry (2 min before close)

Signal Prioritization — B-4 3-tier table, 0-3 (Finding #366, DEPLOYED Aug 21, 2026; SSOT: src/earnings/priority.py + STRATEGY.md §3.1)

Rank 3 = LONG 70-75% any mcap · rank 2 = LONG 80-85% large/mid · rank 1 = every other qualifier (incl. qualifying SHORTs and B-5 pilot small-cap LONGs) · 0 = SKIP. No volume dimension, no market-cap spine — both refuted by #357 (the #313 volume→win-rate gradient inverted). History: scale 0-6 (#211) → 0-8 (#313) → 0-10 (#334) → 0-3 (B-4/#366).


🗄️ DATABASE SCHEMA

Key Tables

Table Purpose
markets All Polymarket markets (4,719 rows) - volume only, no yes_price
earnings_markets Curated earnings markets (414 rows)
trading_positions Active/closed positions
trade_audit_log Detailed trade records
audit_events System event logging
price_snapshots Live price history - CONVICTION SOURCE (yes_price)
orderbook_snapshots Orderbook depth history
trades Historical trade data

⚠️ CRITICAL: Conviction (beat probability) comes from price_snapshots.yes_price, NOT markets table. When querying for future entry times (BMO tomorrow), use the LATEST available data.

earnings_markets Table


🔧 SERVICES & DEPLOYMENT

Server: [hidden] (DigitalOcean)

Systemd Services

systemctl status earnings-trader    # Trading bot
systemctl status polymarket-dashboard  # Dashboard
systemctl status ibkr-monitor       # Gateway health

Cron Jobs (post-#289.a + #293 cleanup, May 10, 2026)

SHELL=/bin/bash    # Required so chained `source venv/bin/activate` works (Finding #289.a)
# Note: api_health_checker MIGRATED to systemd timer per Finding #289.a (May 10, 2026):
#   polymarket-api-health.timer + polymarket-api-health.service. Daily 08:00 UTC.
# Note: sync_earnings_markets.py cron REMOVED per Finding #293 (May 10, 2026) —
#   was silently broken 14 days due to /bin/sh dash bashism, AND the
#   EarningsMarketSyncer it invoked used a broken Polymarket Website URL. Hourly
#   `polymarket-earnings-sync.timer` (EarningsMarketFetcher, Gamma-based) is the
#   canonical earnings_markets writer. Finding #480 later retired the wrapper
#   and src.db.earnings_sync manual entry points before DB access.

Key Paths (Server)

[hidden]/              # Application root
[hidden]/venv/         # Python virtual environment
[hidden]/logs/         # Log files
[hidden]/trading_mode.txt  # Persisted mode (testing_prod)

Trading Mode Source of Truth (Feb 2026 hardening)

IBKR Gateway

Module quick reference (moved here from .cursor/memory.md, Finding #385)

IBKRClient — correct connection parameters for scripts:

from src.ibkr.client import IBKRClient

client = IBKRClient(
    paper=False,           # Live trading (NOT paper)
    use_gateway=True,      # IB Gateway (NOT TWS)
    client_id=99           # trading service owns client_id=1
)
# Auto-selects port 4001 (GATEWAY_LIVE_PORT)
Mode Port Use
TWS Paper 7497 Local paper trading
TWS Live 7496 Local live trading
Gateway Paper 4002 Server paper
Gateway Live 4001 Server live (USE THIS)

RiskManager — correct parameters:

from src.earnings.risk_manager import RiskManager, TradingMode

rm = RiskManager(
    total_capital=capital,
    trading_mode=TradingMode.TESTING_PROD  # NOT mode=, use trading_mode=
)
position_size = rm.calculate_position_size(signal, stock_price=price)

SignalGenerator:

from src.earnings.signal_generator import SignalGenerator
sg = SignalGenerator(os.environ["DATABASE_URL"])
signals = sg.get_bmo_signals_for_tomorrow()  # List[TradingSignal]

Server-side script templates (SSH + venv + DATABASE_URL) and the conviction-tracker runbook live in docs/OPERATIONS.md.


📊 CURRENT STATE (live baseline — see docs/AGENT_HANDOFF.md §11, last updated Sep 26, 2026 Reliability Batch A)

Trading Mode: TESTING_PROD (phase: Phase 1 since Aug 21, 2026 — #367)

Account

System Health

Recent Fixes Applied


📚 DOCUMENTATION FILES

File Purpose
docs/AGENT_HANDOFF.md Onboarding bootstrap (read first); §11 = current state + ranked outstanding work
docs/STRATEGY_PLAYBOOK.md Forward-looking source of truth. Ranked Action Queue (mirrored in dashboard) + verified findings + retracted findings + implementation notes per rule
docs/STRATEGY_AUDIT_v3.md Audit trail — every hypothesis tested + audit-process learnings; §10 (Apr 25) = Finding A retraction
docs/FINDINGS.md Canonical narrative for every numbered finding (per memory.md §J). Append-only — the latest finding is at the insertion point noted in its header (never cite "latest #N" in prose; it rots)
docs/STRATEGY.md Long-form trading strategy (rules, schedules, sizing, exchanges)
docs/OPERATIONS.md Deployment & operations; runbooks for resolver + simulator + report regeneration
docs/EDGE_CASES.md Holiday/early close handling + extended-hours stop fill mechanics (Apr 25)
docs/FINANCIAL_PROJECTIONS.md / docs/FINANCIAL_PROJECTIONS_REVISED_87_TRADES.md Historical ROI projections (superseded by Playbook §1 for current numbers)
docs/IBKR_SETUP.md Gateway configuration
docs/IBKR_CONTINUITY.md Weekly 2FA & session management
docs/DATA_ARCHITECTURE.md Database schema & data sources + outcome resolver + Finding-A simulator
docs/API_REFERENCE.md Polymarket API docs + Dashboard endpoints
docs/DATABASE_DIRECTIVES.md No tech debt policy
docs/PAPER_TRADING_TEST_PLAN.md Testing checklist
docs/SYSTEM_REVIEW_2026_01_06.md Earlier health audit
docs/STRATEGY_IMPROVEMENTS.md DEPRECATED Feb 8 doc; redirects to STRATEGY_PLAYBOOK

🔑 API ENDPOINTS (Dashboard)

Core

IBKR & Trader

Control

Documentation


🚀 QUICK COMMANDS

Check System Health

ssh root@[hidden] "systemctl status earnings-trader polymarket-dashboard ibkr-monitor"

View Trader Logs

ssh root@[hidden] "tail -50 [hidden]/logs/earnings_trader.log"

Restart Services

ssh root@[hidden] "systemctl restart earnings-trader polymarket-dashboard"

Check Dashboard State

ssh root@[hidden] "curl -s http://127.0.0.1:8080/api/state | python3 -m json.tool"

Database Query

ssh root@[hidden] "cd [hidden] && source venv/bin/activate && python3 -c \"
from sqlalchemy import create_engine, text
import os
engine = create_engine(os.environ['DATABASE_URL'])
with engine.connect() as c:
    r = c.execute(text('SELECT COUNT(*) FROM earnings_markets WHERE earnings_date >= CURRENT_DATE'))
    print(f'Upcoming earnings: {r.fetchone()[0]}')
\""

⚠️ KNOWN CONSIDERATIONS

  1. IBKR 2FA (live): Sunday ~06:00 UTC AutoRestart + IBKR mobile push (primary); VNC is fallback only (docs/IBKR_CONTINUITY.md — history: docs once claimed VNC was the primary path)
  2. Paper Gateway (port 4002): etfstack paper path — see docs/IBKR_SETUP.md (history: "intentionally down for TESTING_PROD" was stale; live earnings uses :4001 only)
  3. Gateway Monitor needs ~30 seconds after dashboard restart to populate IBKR state
  4. Earnings markets only available 1-2 weeks ahead (Polymarket limitation)

🔄 HOW TO USE THIS DOCUMENT

When starting a new conversation:

  1. Reference this file: "Please read docs/CONTEXT.md first"
  2. State your task: Be specific about what you want to accomplish
  3. Mention constraints: Any specific rules or preferences

Example prompt:

Please read docs/CONTEXT.md to understand the project context.
I want to [specific task]. Remember the main directives:
- Read ALL files before acting
- No tech debt in database
- Capital from IBKR, not hardcoded

This document is the single source of truth for AI context bootstrapping.