9 Commits
Author SHA1 Message Date
momentum devandClaude Fable 5 2be23673be feat: reasoning-over-time drill-down — score/rvol timelines + criteria lanes + worded reasons
Clicking a watch-state row now shows HOW the verdict evolved, not just the
latest flags:
- setup score across the session vs the 60% gate (SVG line)
- relative volume vs its 2.5x floor
- per-criterion pass/fail lanes over the whole session (the variance of
  each logical step at a glance)
- each criterion's latest reason in words (rvol 4.9 (min 2.5), gap 80.9%,
  price vs breakout ...) instead of bare pass/fail marks

Backend: query_symbol_criteria gains history[] (server-side strided to
~240 samples) + detail[]. The generic LIKE scan took 12s/click over weeks
of criteria rows; now a partial expression index on the extracted symbol
(idx_events_criteria_symbol, built at boot) + 24h window + SQL-side
stride = <1s. Latest-verdict fallback keeps stale symbols and historical
views working (covered by existing projection tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 19:37:35 -05:00
momentum devandClaude Fable 5 5875bbd5f1 fix(events): emit self-heals on a dropped DB connection (the Friday blind-loop bug)
The 2026-07-17 20:23 postgres blip left the live loop writing zero events for
hours: emit() executed on a RAW cursor, bypassing PgConnection._run's
reconnect. cursor() doesn't round-trip so the dead conn went unnoticed until
the raw execute failed with no retry, and self._conn was never rebuilt.
Routing emit through the facade execute() wraps the round-trip in _run, so a
DB blip reconnects on the next write instead of running blind. 2 reconnect
tests + 34 storage tests green. (Deeper 'halt trading when persistently
unable to write' safety guard queued separately.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:37:26 -05:00
momentum devandClaude Fable 5 fbb84ae166 operator-plane: cheapen SSE change detector (shared once/sec signal)
Every SSE client polled COUNT(*) on a fresh EventStore connection each ~1s
(re-applying the 473-line schema unless PG_SKIP_SCHEMA=1). Replaced with one
process-wide _ChangeSignal: a single long-lived connection reads max(timestamp)
(idx_events_timestamp tail seek) at most once per second GLOBALLY behind a lock,
so N clients cost one query/sec. EventStore.max_timestamp() backs it; reader is
injectable for the query-budget test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 00:05:08 -04:00
momentum devandClaude Fable 5 af8137dfd7 delete: dead DDL (10 tables), dead projections, pinned dead code; README/pyproject prune
pg_schema.sql: drop CREATE TABLE for 10 zero-reference tables (operator DROP
list in plans/002-deletions.log — production tables untouched until run).
projections.py: remove 4 zero-consumer query functions.
Clear all 'plan 002' ruff pins from the CI gate (files now F-clean);
api/main.py:F821 pin remains for plan 007. README repo-map/test-count fixed;
pydantic-settings removed (zero imports). Full receipts: plans/002-deletions.log

Suite: 351 passed (16 schwab/dead-module tests removed), 15 failures = the
known WSL-loopback dashboard set only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:09:35 -04:00
momentum devandClaude Opus 4.8 9e730a3726 Remove DuckDB remnants (datastore is Postgres)
The duckdb library is already gone (not a dependency, not importable, no
`import duckdb` anywhere) — these were leftover text references that misled.

- Delete storage/schema.py: 418 lines of DuckDB DDL with zero importers. The
  Postgres schema is created by db_pg._apply_schema + event_store._ensure_schema.
- Neutralise stale default db_path strings (./data/momentum*.duckdb) in
  event_store, config, run_live_paper, dashboard_api — the connection layer
  ignores the path (DATABASE_URL is the datastore), so these phantom file paths
  only created confusion. Dropped the now-pointless os.makedirs for them.
- research/multi_schema: the "three DuckDB files" are one Postgres DB now (all
  resolve to the same connection); doc + constants updated to match reality.
- Fix stale "DuckDB handle" comments in api/main.

Kept the DuckDB mentions in storage/db_pg.py + storage/db.py — they document
why the Postgres facade mimics DuckDB's ? placeholders and :memory: semantics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:43:29 -04:00
momentum devandClaude Opus 4.8 78bdf26141 Perf: composite (event_type, timestamp) index + test-schema sweep + README
DB latency: every projection runs `WHERE event_type=? ORDER BY timestamp DESC
LIMIT n`. With only a plain timestamp index, the latest of a STALE type
backward-scans the whole events table — measured 373ms / ~52k buffers on 235k
rows. Add a composite index in event_store._ensure_schema:

    CREATE INDEX idx_events_type_ts ON events(event_type, timestamp DESC)

-> same query is now a 0.018ms index seek (3 buffers). Applied to the live DB
CONCURRENTLY; also dropped two redundant legacy indexes (idx_events_type, now
covered by the composite prefix; idx_events_correlation, a dup of
idx_events_session_id) to cut write overhead.

tests/conftest.py: sweep the throwaway `mem_*` schemas EventStore(":memory:")
creates — 170 had leaked into the shared Postgres, bloating the catalog.

README: rewritten around the current system — PostgreSQL event store, a full
command reference (run_live_paper / momentum CLI / eod_replay / sweeps / tuning /
cron), config knobs, and a database & performance section (the index above,
extension guidance, retention).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 17:09:55 -04:00
momentum devandClaude Opus 4.8 d5d79cccf7 Threaded fast trigger + incremental ingest + naked-stop guard
Three fixes from the first live session:

1) Naked-stop bug (critical): the entry-timeout guard cancelled an entry it
   thought was unfilled AFTER it had really filled (our order_filled events lag
   the 60s sync). Cancelling a bracket parent cancels its stop/TP legs, so the
   position went NAKED and bled 22% with no floor. Guard now checks the broker's
   LIVE positions (get_positions) before cancelling and never cancels a filled
   name. Regression test added.

2) Speed: ingest re-fetched a 240-min window for ~80 symbols every minute (~46s
   measured), and the single-threaded loop froze the 4s trigger that whole time
   -> breakouts caught once a minute, not in real time. Ingest is now
   incremental (heavy first pass covers the session so the opening range is
   present; then ~20-min refreshes, closed bars never change). Loop passes
   dropped from ~70s to ~2-5s.

3) Threaded trigger: the fast breakout detector/fire loop now runs on its own
   thread, fully decoupled from the slow scheduler. EventStore, the execution
   service, and the trigger book are made thread-safe (locks) so the thread and
   main loop can't race the shared psycopg2 connection or in-memory state.
   Verified live: fired EZGO/NEOV, all positions retain active stops.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:39:50 -04:00
momentum devandClaude Opus 4.8 c4669fe0ce WS0: migrate datastore DuckDB -> PostgreSQL (psycopg2)
- storage/db_pg.py: thin psycopg2 connection shim (?->%s translation,
  autocommit, .df(), and per-test isolated schema for ':memory:').
- storage/db.py: delegate get_connection/get_memory_connection to db_pg.
- storage/pg_schema.sql: Postgres schema, applied on connect.
- research/query.py runs UNMODIFIED on Postgres via the shim.
- ingestion upserts: INSERT OR REPLACE -> INSERT ... ON CONFLICT DO UPDATE.
- event_store: drop invalid btree-on-JSON-text index.

Full logic suite: 67/67 green against Postgres (was DuckDB).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:48:35 -04:00
momentum devandClaude Opus 4.8 007cd95c79 Baseline before Postgres migration (WS0)
Snapshot before the DuckDB->PostgreSQL migration. data/, archive/, the 232MB
rebuild zip, the Windows .venv, and .env are excluded via .gitignore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:39:29 -04:00