Commit Graph
15 Commits
Author SHA1 Message Date
momentum devandClaude Opus 4.8 e74539d449 Resiliency + risk + monitoring: SIP backfill, freshness watchdog, gross cap, naked alarm
From the multi-agent audit (ship-now, low-overfit-risk cluster):

INGESTION RESILIENCY
- client._request: 429/5xx now retry with Retry-After + exponential backoff
  (previously hard-failed, feeding silent-stale ingest).
- SIP backfill feed (settings.backfill_feed='sip'): live realtime stays IEX, but
  backtest/backfill ingestion uses the full consolidated tape — IEX sees only
  ~2-5% of true volume (verified: AAPL 22,797 vs 452,704), which was silently
  corrupting every rvol/$-volume decision and backtest. eod_replay wired to it.
- step_ingest freshness watchdog: emits a STALE event + board note when the
  newest bar is older than INGEST_STALE_SECONDS during RTH (45% of passes
  historically returned 0 rows with no error). Optional hard arm-block
  (INGEST_STALE_BLOCK, off by default).

RISK
- Portfolio gross-notional cap (TRADING_MAX_GROSS_NOTIONAL_PCT=0.60): both sizing
  paths now cap each entry to the remaining gross budget, so 6 x 16% can't pile
  the book into ~96% gross of correlated small-caps. Blocks/shrinks late entries.
- Fixed the duplicate TRADING_MAX_CONCURRENT_POSITIONS in .env.example.

MONITORING (UI)
- Naked/unprotected-position alarm: _unprotected_positions joins the latest
  position + order snapshots (order sync now flattens legs) and the TUI board
  shows a red UNPROTECTED banner the instant a held long lacks stop coverage —
  the exact blind spot when APWC/NOWL went naked. Verified it flags today's
  3 overnight positions.

Tests: gross-notional cap blocks when budget full; 50 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:18:36 -04:00
momentum devandClaude Opus 4.8 6e99627da8 Journal: display trade times in ET (was UTC)
The journal/board reconstructs from broker timestamps (UTC) and printed the raw
UTC time, so a 10:12 ET entry showed as 14:12 on a US-market journal. Add _to_et
and convert both the today-filter and the displayed time to America/New_York.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 16:23:29 -04:00
momentum devandClaude Opus 4.8 bace33edfd Board/journal: broker-authoritative realized in the P&L header
The header's "matched" subtotal used the FIFO round-trip sum, which doesn't
equal the broker's average-cost day P&L while positions are partially open — so
"matched + open" never tied to the headline day P&L (e.g. -1340 + open vs the
broker's -627).

Show broker-authoritative realized instead: matched = day P&L - open unrealized,
so matched + open == day P&L by construction. Per-trade rows stay FIFO-accurate
(each is a real fill pair); only the header subtotal is broker-truth. Factor the
open-unrealized sum into a shared helper used by both the live board and the
`journal` close capture. Falls back to the FIFO sum when no equity snapshot
exists.

Verified live: board + journal both reconcile (matched + open == day P&L).
Tests: 3 new (reconciliation invariant + FIFO fallback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:42:35 -04:00
momentum devandClaude Opus 4.8 27b0748b71 DB: auto-reconnect on dropped connection (board crashed on idle drop)
A long-lived `momentum watch` connection got closed by Postgres on idle/restart
overnight, and the next query raised OperationalError and crashed the board.
PgConnection now reconnects ONCE on OperationalError/InterfaceError (skipped for
isolated test schemas); the watch loop also catches transient errors and shows
'reconnecting…' instead of dying. Hardens the live loop's event store too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 07:04:49 -04:00
momentum devandClaude Opus 4.8 8a24226cfb Trade journal: momentum journal command + nightly close capture
momentum journal prints the day's trades (time/symbol/qty/entry/exit/P&L) +
broker-accurate day P&L + win summary. journal.sh appends it to
data/trade_journal.log; cron 16:02 weekdays keeps a durable daily record.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:54:24 -04:00
momentum devandClaude Opus 4.8 c35224af94 Board: per-day trade history + accurate day P&L; manual symbol weights
- momentum watch: replace 'open positions' with TODAY'S trade history (FIFO
  round-trips from the broker orders snapshot: time/symbol/qty/entry/exit/P&L +
  open lots), resets each day. Headline day P&L is broker-accurate (equity -
  last_equity, now carried on AccountSummaryUpdatedEvent), shown in the header
  and the panel title. sync_orders limit 50->500 so the log captures the whole
  day across restarts.
- SYMBOL_WEIGHTS: optional manual per-symbol priority nudge applied to the
  armed-trigger rank score (small; doesn't override gap/volume logic).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:33:22 -04:00
momentum devandClaude Opus 4.8 906237bfe3 News/catalyst ranking + nightly cron for the EOD replay
News (you were right it's relevant): recent_news_map() pulls fresh catalysts per
ticker from the ingested RSS feeds; step_arm tags each armed candidate and BOOSTS
catalyst-backed gappers up the rank (NEWS_RANK_BOOST) — a fresh PR/FDA/offering
is why a small-cap runs, so it surfaces the hotter name earlier and higher. The
board shows a 📰 marker + the headline. Feeds verified live (PR Newswire /
Business Wire / GlobeNewswire) — pulling real catalysts (FDA approvals, offerings).
Forward-tested only (news isn't OOS-backtestable).

eod_replay.sh + crontab: replay at 16:05 (grow dataset), tune at 17:30.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:14:57 -04:00
momentum devandClaude Opus 4.8 476d30e3d1 Board: show entry/last price + uPnL $ and % on open positions
So you can confirm the bought price next to the unrealized P&L.

Profit-lock tiers verified out-of-sample (lock_mid: 8:3,15:9,25:18,40:30 on the
prior-low trail) — test avgR 0.127 vs 0.123 baseline, win 55% vs 46%, not
overfit; already the .env default. The flashy lock_only_mid (+0.40R in-sample)
was rejected as overfit (collapsed to 0.187 test).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 16:12:04 -04:00
momentum devandClaude Opus 4.8 6dfc074c6d Profit-lock checkpoints + fix uPnL display
- strategy/exits.py: percentage profit-lock tiers (TRADING_EXIT_PROFIT_TIERS,
  e.g. 8:3,15:9,25:18,40:30). Once a trade has been up gain%, the stop is pinned
  to entry*(1+lock%) so a winner can only give back to a locked-in minimum gain
  and a fade sells you out IN PROFIT. Shared by backtest + live, active-trades
  only (ratchets the broker stop up via replace, never cancel).
- momentum_cli.py: open-positions uPnL was reading 'unrealized_pl' but the sync
  stores 'unrealized_pnl' -> always blank. Read the right key (+ green/red).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:57:27 -04:00
momentum devandClaude Opus 4.8 2a315d87d6 Exit manager: find held stop legs + trail-breach market exit
Two fixes found by running the manager against the live book:
- _stop_leg queried status="open", but a bracket's protective stop is a 'held'
  child of the FILLED entry, so it was never found -> stops never trailed.
  Query status="all" and match active leg statuses.
- when the trailed stop would sit ABOVE current price (price fell back through
  the prior swing low), a sell-stop there is rejected; that IS the trail firing,
  so exit at market instead.
Also drop the hardcoded "6" in the board title (armed set is now 10).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:28:25 -04:00
momentum devandClaude Opus 4.8 fb377ac321 Fast armed-trigger path + live, self-explanatory board
The "always late / miss the move" problem was structural: entries waited for a
1-min bar to CLOSE above the breakout then walked the ingest->watch->execute
cadence (~2 min), and rested a limit AT the trigger that a runner never fills.

Add a top-6 armed-trigger book (runtime/triggers.py): keep the most-promising
gappers ready with a pre-computed opening-range trigger/stop and FIRE the
instant LIVE price crosses, via TradingExecutionService.submit_breakout_now —
a marketable order (capped above the trigger) that actually fills — reusing the
same risk gates (daily-loss breaker, max-concurrent, 1%-risk sizing, dedup).

- structure.py: opening_range() helper (shared with the ORB detector)
- run_live_paper.py: step_arm (rank + arm, 20s) + step_trigger (price-cross
  fire + snapshot, 4s)
- momentum_cli.py: rewrite the watch board — market clock + countdown, loop
  heartbeat, the 6 armed triggers with live price / gap / rvol / trigger /
  distance / stop, and a plain-English "what it's hunting" line
- tests: opening range, trigger book state machine, submit_breakout_now gates

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 07:28:53 -04:00
momentum devandClaude Opus 4.8 3e2b4f562e Harden discovery: surface screener failures + regression test
- discover_active_symbols logs the screener error instead of swallowing silently.
- run_discovery records an explicit error when the universe is empty (surfaces in
  the discovery event + Grafana).
- momentum doctor now FAILs when the latest discovery universe is 0 (the morning's
  dead screener would have shown red within a minute).
- tests/unit/research/test_discovery.py: pins get_most_actives top<=100 and the
  ETF/price-band filtering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 13:23:21 -04:00
momentum devandClaude Opus 4.8 11f1dd8b15 Live monitoring board: momentum watch
Full-screen auto-refreshing board (rich.Live) of what the system is evaluating
in real time: per-symbol status (ready/late/blocked), score, gap%, RVOL and the
blocking reason, ranked by score — plus ready signals, open positions, and risk
events. 'momentum watch' (refresh) or '--once' (single frame). Reads the
Postgres event store.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 08:54:09 -04:00
momentum devandClaude Opus 4.8 a16a8cd29d WS3: surface evaluation internals (gap/rvol/per-criterion reasons)
- SetupEvaluationResult.criteria_detail: per-criterion {name,passed,reason}.
- watcher CriteriaEvaluatedEvent now carries gap_pct, relative_volume, status,
  reason, and the per-criterion detail (was only passed/failed names).
- CLI: 'inspect criteria SYMBOL' shows status + gap/rvol + each criterion reason;
  new 'inspect signals' = ready board + per-symbol gap/rvol/status.

Verified on real 2026-06-15 bars: RGNT shows 9/9 with reasons (rvol 26.2x),
'late' explained by the 10:30 ET cutoff. 67/67 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 07:22:55 -04:00
momentum devandClaude Opus 4.8 9e79c50751 WS2: momentum inspect/doctor CLI (per-section verification)
momentum_cli.py (typer + rich):
- doctor: PASS/WARN/FAIL across db / schema / research / ingestion / discovery /
  event_store, with honest known-gap WARNs (circuit breaker not implemented,
  quality_score hardcoded, ETF universe).
- inspect events / bars / discovery / criteria: read the Postgres event store +
  bar tables. 'inspect bars' shows per-symbol counts + freshness and flags
  0-bar (market-closed/thin) vs broken.

Verified against live Postgres: 6 pass / 4 warn / 0 fail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 00:38:17 -04:00