Files
momentum-system/plans
momentum devandClaude Fable 5 5bf089cdcc research: orderflow H1/H2/H4 verdicts — all refuted in substance; H2's formal PASS is a bar artifact
H2 exposed an adjudicator gap: on a -0.21R/fire baseline any keep-or-zero
gate zeroing ~44% of fires clears +0.05 mechanically; its own pre-registered
strata check inverts (choppy beats persistent within both gap buckets =
Simpson's). Queued: random-gate null for keep-or-zero bars. H3 accruing as
designed (n=29, skip-on-no-coverage working).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:24:47 -05:00
..

Implementation Plans — next enhancement milestone

Generated by the improve skill on 2026-07-13 at commit d54a2a9, from an 8-cluster recon sweep (~95 evidence-cited findings), a 5-frame ideation pass (48 raw ideas → 14 merged candidates, all 14 verified sound by a fresh-context spot-check against HEAD), and a design-fit review of the dashboard additions. Companion design artifacts: docs/ideation/2026-07-13-next-milestone-ideation.html (ranked ideas + bases), docs/ideation/momentum-architecture-current.{drawio,png}, docs/ideation/momentum-milestone-featureset.{drawio,png}.

Execute in the order below unless dependencies say otherwise. Each executor: read the plan fully before starting, honor its STOP conditions, and update your row when done.

Execution order & status

Plan Title Priority Effort Depends on Status
001 Production safety rails — CI test gate, dead-man's switch, pg_dump backup P1 S — DONE
002 Dead-weight deletion — schwab/, grafana/, dead modules/tables/projections P2 S 001 DONE
003 Money-path hardening — daily self-restart, positive-evidence contract, reconcile, cage count P1 M 001 DONE
004 Learning flywheel — one population module, scheduled eod_replay + nightly_tune, atomic writes P1 M 001 (002 rec.) DONE
005 Entry-path unification + decision receipts P2 M 001, 003, 004 DONE
006 Experiment OS — pre-registered counterfactual experiments over logged fires (flagship) P1 L 004 (005 rec.) DONE
007 Operator control plane — dashboard truth + kill switch + effective config (step 1 = prod bug fix, ship early) P2 M 001, 003 (006 for ledger data) DONE
008 Conversion-signal model — measure if the labeled data separates winners P1 L 006, 004 MEASURED 2026-07-14 (corrected): the 0.514 was the liq1-dominated MIX. Scoped to live v1: CV AUC 0.641 — REAL signal, but it is ~entirely the VWAP gate the bot ALREADY runs (above-VWAP +1R 35.4% vs below 12.7%). Residual within above-VWAP = 0.526 ≈ noise. RVOL-fallback added (read-only test 2026-07-14): 0.526→0.535, within noise — RVOL adds nothing beyond VWAP. Verdict: VWAP is the whole story for current features; no second indicator. Only market-context + catalyst remain untested (forward-accrual only, negative prior). The lever is a different setup (premarket), not more ORB filters.
010 Premarket shadow entry — prove a FILL-ABLE edge before any live premarket order P2 L 006, 009 NO-GO (Step 1 measured 2026-07-15): premarket is a paper-fill mirage. pm1+fat-tail-exit expR: −0.50R @0.5% fill, −1.27R @1% (realistic thin book), −2.74R @2%, vs the ORB's −0.11R. The 22-min fat tail can't outrun the 1-2% premarket spread. STOP — do not build the live shadow track. The ORB's 5-min wait exists to trade in penny-spread regular-session liquidity.
009 Feature-population prerequisite — instrument v1/pm1 (market context, RVOL fallback, catalyst-on), then re-run 008's probe P2 L 006, 008 code DONE, awaiting supervised rebuild+probe (market context + thin-name RVOL fallback + SPY/QQQ ingest + enrichment-on + committed probe all landed & tested; Steps 4-5 rebuild/AUC run supervised). 2026-07-16: the 07-15 nightly job never fired (docker outage overlapped 20:00 ET) — 07-15 was a data hole (0 setup_events, no SPY ingest, no regime row). BACKFILLED supervised: SIP refetch + 50 v1 setups + 6 pm1 fires + SPY/QQQ ingest + regime row for 07-15. Accrual clock unbroken; verify tonight's 20:00 scheduler fires.
011 Regime gate — a FREE-data daily signal deciding whether the ORB trades today; metalabel P(our ORB profits this session), proven in the harness (walk-forward) before any live gate P1 L 006, 004, 009 BACKTESTED ON PROD (2026-07-15) — VIABLE as a SHADOW damage-control signal, NOT yet a live gate. 266 walk-forward sessions: trade-always −0.111R/session (36% hit) → meta_v1 GATED −0.004R/session (48% hit), trading only the best ~32% of days; delta +0.107R in-sample, +0.128R OOS (recent 80 sessions), improves across 3 folds. threshold_v1 rule gates too little to matter. Leaky full-fit +0.61R ≫ walk-forward +0.078R confirms the walk-forward discipline was load-bearing; OOS holding = the edge is real. Verdict: turns a bleeding strategy roughly FLAT by sitting out bad regimes. FIRE-WEIGHTED harness (the LIVE v1 population — gap≥10%, vol≥300k — 2041 fires): BOTH experiments PASS their pre-registered bars (first PASS of the milestone) — regime_meta_v1 expR_delta +0.0965R, baseline −0.085R/fire → GATED +0.0117R/fire (POSITIVE on the real population, keeps ~33%); regime_threshold_v1 +0.083R but keeps only 26 fires (its pass = 'sit out 99%', thin/ignore). So on the actual tradeable population the gate is mildly POSITIVE, not just flat. DEPLOYED (shadow): scheduler runs build-features+score nightly; admin /api/regime/run button triggers on demand. LIVE-GATED IN PAPER (Step 5 shipped 2026-07-15, commit 0ac09d3): _regime_reject blocks fresh entries on unfavorable sessions, TRADING_REGIME_GATE_ENABLED=1 in both compose, fail-open everywhere. Blocker-1 (tape_regime not pre-open) resolved by DELETING tape_regime — measured 0.000 importance; LIVE feature set = breadth_rvol(vol_upto/avg, non-look-ahead) + spy_lagged + trailing. Takes effect on next deploy. Real-money promotion still a separate owner decision. 2026-07-16 AUDIT + FIX: the shipped live gate was structurally INERT — live_session_favorable read today's breadth_rvol from today's OWN setup_events rows, which the labeler only writes at 20:00 ET, so the fail-open guard returned None every live session (verified: no meta_live score + 1288 all-pass gate receipts on 07-16; the 07-15 'validation block' p=0.080 was computed PRE-guard on a zero-filled feature — an artifact, not a validation). Also found breadth_rvol was UNIT-CONTAMINATED: _session_context pooled all setup versions, and pm1 stores RAW cumulative premarket volume in relative_volume (median 261k) vs ratio tracks (~0.8) — ~240/267 historical sessions were pm1-raw-volume, and plan 009's v1 RVOL fallback was about to flip forward sessions to ratio units (07-15 rebuilt = 0.18), silently breaking the feature. FIX (same-day): breadth_rvol redefined pm1-ONLY (median premarket volume — the dominant historical source, unit-consistent, strictly pre-open knowable); live gate recomputes today's pm1 fires IN MEMORY from premarket minute_bars (pure compute_premarket_setups, no insert); _regime_reject retries fail-open verdicts every ~5 min instead of caching None all day (definite verdicts still once/day). regime_features rebuilt (267 sessions, labels unchanged), scores rewritten, fire-weighted re-validation vs the PRE-REGISTERED bars: regime_meta_v1 expR_delta +0.0570 (bar +0.05, n=2058, PASS — weaker than the contaminated +0.0965 but real), keeps 37%, baseline −0.078→gated −0.021; recent-80-session delta +0.149R/session (always −0.141 → gated +0.008, trading 29% of days). threshold_v1 +0.0766 but keeps 1% (sit-out-everything, ignore as before). Live check 07-16 on fixed code: breadth 612,819, verdict UNFAVORABLE — first genuine live decision. Adversarial review (19-agent, 9 findings) hardened the live path: previous_close falls back to the latest prior session's close when today's daily_bars row is absent intraday (else the gap basis silently flips to the premarket open = different fire population than training); verdicts before 9:50 ET are PROVISIONAL (re-scored ~5 min — ingest lag can truncate the premarket frame) and the first verdict at/after 9:50 is FINAL even when None (caps the scorer at a handful of runs/day instead of ~78 on quiet days — hot-path stall guard); NULL-breadth training rows excluded from the live fit (zero-fill taught a 'zero premarket volume' class the serve path can never produce); throttle datetime arithmetic moved inside the fail-open try. KNOWN ACCEPTED SKEW: live premarket volumes are scaled-IEX estimates vs the nightly SIP tape (up to ~25-50x low on symbols without a volume_ratio row) — regime_scores['meta_live'] vs the EOD label is the standing forward measure of whether this skew flips verdicts; revisit (intraday SIP refetch at 9:46+, 16-min embargo) if agreement is poor. Takes effect on next image deploy. Prior status below. CODE DONE + REVIEW-APPROVED (commit 5cef4aa), deploy-wired + backtested at 092d607. Built: research/regime.py (probe + walk-forward build-features/score), iv_regime_gate harness intervention (keep-or-zero, fail-open), regime_threshold_v1+regime_meta_v1 JSONs, 5 tests incl. the mutate-the-future leakage guard. Reviewer re-ran: 9 new + 35 existing experiments tests green, ruff clean, live paths untouched. HMM rung skipped (statsmodels absent). Research: docs/research/regime_notes.md. SUPERVISED next step (hits production Postgres — NOT done by the executor): python3 -m research.regime probe (Step-0 STOP gate) → build-features → score --model threshold_v1/meta_v1 → register+run+adjudicate the two experiments → record expR_delta + OOS/fold results here. Review findings to fold in at that point: (1) tape_regime = earliest setup's at-trigger (~9:35) reading, not strictly pre-open — fine for the shadow harness, must be lagged before Step-5 live promotion; (2) minor: _load_regime_favorable model_version has no None-guard (both JSONs set it, fail-open if absent). Honest prior unchanged: most promising AND most curve-fit-prone (~150-260 sessions); walk-forward + threshold-floor + OOS decide GO vs "no free-data regime signal at this sample size".

| 012 | Dashboard order-limits verbosity + dead-column cuts (owner request 2026-07-16) | P2 | S | — | DONE (2026-07-17, review-APPROVED after 2 revision rounds; commits b349518+1f3ddca on advisor/012-dashboard-order-limits in worktree .claude/worktrees/plan012 — awaiting owner merge). Step 3 revised mid-execution: lifecycle events never carried limit/stop, enriched client-side via client_order_id join to d.orders. | | 013 | Symbol drill-in: 24h candlestick + live buy/sell trade markers (TradingView Lightweight Charts v5.2, vendored) | P2 | M | 012 (soft — reuses its bracket-leg maps) | DONE (2026-07-17, review-APPROVED after 1 revision round; commits b4ad3d3+709e7a4 on worktree-agent-ae2ee0000d5e3a7eb — awaiting owner merge). Headless-browser render-verified. Executor surfaced 2 pre-existing out-of-scope defects: OrderFilledEvent has no side field (worked around via order_submitted sibling join in /api/chart) and GET /api/attribution 500s when DATABASE_URL unset (research/attribution.py hardcodes os.environ lookup). | | 014 | Live decision trace — gate-receipt feed (value→threshold per gate) + chart reject markers | P2 | M | 013 (chart overlay half; feed panel standalone) | DONE (2026-07-17, review-APPROVED first pass, zero revisions; commit dd81dbb on worktree-agent-acb00535650deac1a — awaiting owner merge). Decision Trace panel (lazy-load + SSE-throttled), /api/decisions (clamped 500, case-insensitive, server-side epoch), collapsed reject markers merged into the chart's single markers primitive. | | 015 | Backtest replay dashboard (/backtest) — theoretical entries/exits over past sessions under CURRENT settings, via Experiment-OS replay | P2 | L | 013 (chart component) | DONE (2026-07-17, review-APPROVED first pass; commit 8ac222a on worktree-agent-ae4d3f36e18c6e7a5 — awaiting owner merge). replay_window = assembly-only over harness primitives (same _entry_index/adverse-fill/R math); /api/backtest 45-session clamp + 10-min cache behind a dedicated lock; /backtest page with THEORETICAL banner + X-of-Y coverage line + per-trade drill-in (blue entry/purple exit markers). Deviations accepted: /api/chart gained backward-compatible date= anchor (72h cap couldn't reach old sessions); batching idiom taken from regime.py (plan's run() pointer was wrong). | | 016 | EDGAR dilution enrichment — unbreak silent nightly refresh (UTC current_date bug + missing float_cache DDL) + pre-register dilution_veto_v1 keep-or-zero experiment | P2 | M | 006 | DONE (2026-07-17, review-APPROVED first pass; f6999c7 merged). iv_dilution_gate keep-or-zero (fail-open on unfetched symbols, early results biased toward zero never inflated), dilution_veto_v1 self-registers via the experiments/*.json glob, nightly adjudication live. Prod backfill seeded same day (304 symbols swept, 245 filings / 91 symbols). FIRST ADJUDICATION 2026-07-17: FAIL (n=2044, expR_delta +0.0012 vs +0.05 bar) — but only 64/2069 fires (3%) had filing coverage (backfill covers recent actives only; historical fires read unflagged by fail-open design), AND the flagged fires ran BETTER than unflagged (baseline −0.038 vs −0.377, n=64): early evidence points AGAINST the dilution-trap thesis, not merely underpowered. Bar-design lesson: min_n counted ALL fires not flagged ones. Verdict terminal (no goalpost-moving); filings keep accruing nightly. IF revisited: dilution_veto_v2 with a flagged-fire min_n metric, after ~3-4 weeks coverage. |

Status values: TODO | IN PROGRESS | DONE | BLOCKED (with one-line reason) | REJECTED (with one-line rationale)

Expected drift (read before running any plan's drift check)

Every plan stamps d54a2a9 and its drift check diffs against that commit. Executed in order, later plans WILL see diffs from earlier ones (compose scheduler/db-backup blocks from 001/003/004/006, workflow test job from 001, trading_execution/ run_live_paper changes from 003/004/005, telegram_bot from 001). That is EXPECTED drift: verify only that the plan's quoted code excerpts still exist semantically (line numbers may shift); STOP only on changes NOT attributable to a completed plan in this series (check the status table above).

Dependency notes

  • 001 first, always: it is the gate that makes every other plan's image push test-verified. 007's step 1 (the POST NameError import fix) is a production bug fix that may be cherry-picked ahead of everything.

  • 002 before the refactor-heavy plans (004/005) so they diff against a smaller surface; its production DROP TABLE section runs only after 001's backup exists.

  • 003 before 005/007: both build on its clean-shutdown/flatten semantics. Note the documented interaction: 007's flatten_all command must not trigger 003's daily-restart exit (see 007 STOP conditions).

  • 004 before 006: every experiment population MUST come from strategy/population.py — running experiments against a look-ahead population re-creates the exact artifact class (vr1/liq1) this milestone exists to kill.

  • 005 before 006 is recommended (receipts enrich replay + rank_one_bullet needs rank), but 006's bar-path replay works without it — the rank experiment just starts underpowered.

  • Postgres provisioning (owner): the only new DDL in the milestone is plan 006's experiments / experiment_runs / experiment_verdicts tables (full DDL in plans/006-experiment-os.md). Plans 001–005/007 add no tables. The owner said they will provision the database once the plan is solid — that means: apply 006's DDL (or let the harness's idempotent schema-apply do it on first run), and after 002 lands, optionally run its operator DROP TABLE list post-backup.

  • 013 soft-depends on 012: the chart's entry/stop/target price lines reuse the bracket-leg maps (stopBy/targetBy/entryLimitBy) 012 builds in render(d). 013 alone must build them itself (STOP condition covers it).

  • 012/013/014/015 are UI/read-path only — zero overlap with plans 008-011's research/live-gate surface; safe to execute in parallel with any of them.

  • Recommended UI execution order: 012 → 013 → 014 → 015. 014's Decision Feed panel half can land before 013; its chart-overlay half can't. 015 consumes replay.py as-is — any plan that changes exit-sim semantics invalidates its honesty claim and must re-verify it.

  • 015's data supply is the nightly setup_events build (plan 009's accrual) — a missed scheduler night is a missing day on /backtest; the page surfaces it via the "sessions replayed X of Y" line.

Queued defects (surfaced by plan 013/015 executors — owner-confirmed 2026-07-17, plan files pending)

  • OrderFilledEvent has no side field (storage/event_schema.py; sole emit site alpaca_paper/execution.py passes none). /api/chart works around it by joining the sibling order_submitted row — fix at the emitter so every consumer gets it. Live-path change: needs its own plan + tests, do NOT drive-by fix.

  • GET /api/attribution 500s when DATABASE_URL unset — research/attribution.py:_connect() hardcodes os.environ["DATABASE_URL"], bypassing the storage facade's fallback chain everything else uses. Read-side, small.

  • Nightly scheduler's regime build-features step silently produced no row two nights running (07-15 full-outage, 07-16 step-level: setups+SPY landed, regime row didn't — backfilled supervised both times). Pull the scheduler container's stdout at 20:00 ET tonight; suspect the python -m research.regime CLI's store bootstrap (WATCHER_EVENT_DB_PATH default) diverging from DATABASE_URL in-container.

  • Adjudicator gap (2026-07-17, from orderflow H2): the keep-or-zero expR_delta bar is mechanically beatable on a negative-baseline population — zeroing ANY ~44% of −0.21R/fire fires yields ~+0.09 delta. Add a random-gate null (delta must beat flagged_share x |baseline| by the margin) or a kept-vs-zeroed separation bar before trusting future keep-or-zero PASSes. regime_meta_v1's PASS is unaffected (its evidence was the session-level walk-forward OOS, not just the fire bar).

Findings considered and rejected (do not re-audit)

  • Retest/pullback entry logic, scale-out partials, close-above-trigger confirmation from folklore — refuted in docs/research/entry_conversion_research.md (external evidence + in-house tests); the research doc's "explicit do-nots".
  • Selection levers (confirmation-speed gate, float gating, liquidity floors, VWAP-reclaim promotion, liquid-breakout, leading-gainer detector, premarket-volume slices) — refuted at year scale per project memory; the validated population stays as-is.
  • Reviving the NiceGUI controlroom — owner preference: :8765 is the dashboard.
  • LISTEN/NOTIFY for dashboard change detection — deferred, not rejected: the shared cached signal (007 step 2) is the sufficient lazy fix; NOTIFY is the named upgrade path.
  • TimescaleDB / pg extensions — not required at current scale (README's own assessment); revisit only if event/bar volume grows past what the hot-path index handles.
  • strategy/regime.py deletion — already deleted at HEAD; recon reference was stale. NOTE: plan 011 introduces a NEW, unrelated research/regime.py — an offline metalabel scorer (session-level P(ORB profits today)), not the deleted live regime module. Different file, different purpose; do not conflate.
  • Schwab account sync revival for human-vs-bot comparison — the only non-delete rationale for schwab/; owner may veto 002's schwab deletion if that comparison is still wanted (plan 002 grep-gates it either way).