feat(replay): R0 snapshot recorder — archive per-cycle decisions into signals

The signals and markets tables existed since Phase 2/5 but never had a
writer; the replay engine (phase plan line 2.1) needs a per-(market, cycle)
archive of what the strategy saw and decided. This wires them up:

- signals: one row per evaluated market per cycle, now carrying INPUTS
  (news_sentiment, feat_*_lo, volume_24h, days_to_resolution) plus the
  existing outputs (probs, edges, gates, skip_reason). skip_reason is
  granular: unsupported/no_signals/prior_extreme/family/edge_net/
  confidence/reentry_guard. news_budget_skipped distinguishes "GNews not
  asked" (5-query budget) from "no news".
- ext_snapshots: one row per cycle with the ExternalSignals snapshot;
  signals rows join on cycle_ts.
- markets: metadata upserted each cycle (replay rebuilds Market from it).
- Retention: prune > SIGNALS_RETENTION_DAYS (default 90) once a day.
- SIGNAL_RECORDER_ENABLED (default true) gates all DB writes; every write
  is try/except — the recorder can never break trading.

Strategy changes are purely additive (record accumulation at each exit
path of evaluate()); no weights, thresholds, gates or sizing touched,
per the freeze in the current phase plan.

Tests: 10 new deterministic tests (85 total passing). Schema migration
dry-run validated against prod postgres inside a rolled-back transaction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-07-02 08:52:07 +00:00
co-authored by Claude Fable 5
parent 117d2b33b2
commit 919fe1617a
5 changed files with 506 additions and 0 deletions
+52
View File
@@ -318,3 +318,55 @@ CREATE TABLE IF NOT EXISTS manifold_eval_cooldown (
);
CREATE INDEX IF NOT EXISTS idx_mfld_cooldown_retry ON manifold_eval_cooldown(retry_after);
-- ─────────────────────────────────────────────────────────────────────────────
-- Replay R0: snapshot recorder — the archive the replay engine reads from
--
-- The signals table (Phase 2/5 schema) never had a writer; R0 makes it the
-- per-(market, cycle) decision archive. One row per evaluated market per
-- cycle, carrying both the INPUTS the strategy saw (external signals, news
-- sentiment, per-feature log-odds) and the OUTPUTS it produced (probs, edges,
-- gates, skip_reason). A replay run rebuilds Market/ExternalSignals from
-- these rows plus ext_snapshots and re-executes evaluate() deterministically.
--
-- cycle_ts groups all rows of one trading cycle and joins them to their
-- ext_snapshots row (same timestamp; no FK to keep writes independent).
-- days_to_resolution is persisted so replay does not depend on wall-clock.
-- news_budget_skipped distinguishes "GNews had nothing" from "GNews was not
-- asked this cycle" (5-query budget) — without it politics replay would treat
-- budget starvation as absence of news.
-- Retention: rows older than SIGNALS_RETENTION_DAYS (default 90) are pruned.
-- ─────────────────────────────────────────────────────────────────────────────
ALTER TABLE signals ADD COLUMN IF NOT EXISTS cycle_ts TIMESTAMPTZ;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS category TEXT;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS prior_prob DOUBLE PRECISION;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS raw_final_prob DOUBLE PRECISION;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS days_to_resolution INTEGER;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS volume_24h DOUBLE PRECISION;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS news_sentiment DOUBLE PRECISION;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS news_budget_skipped BOOLEAN;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS guardrail_applied BOOLEAN;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS guardrail_changed_decision BOOLEAN;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS feat_fg_lo DOUBLE PRECISION;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS feat_mom_lo DOUBLE PRECISION;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS feat_news_lo DOUBLE PRECISION;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS feat_mfld_lo DOUBLE PRECISION;
ALTER TABLE signals ADD COLUMN IF NOT EXISTS feat_btc_dom_lo DOUBLE PRECISION;
CREATE INDEX IF NOT EXISTS idx_signals_cycle ON signals(cycle_ts);
-- One row per trading cycle: the ExternalSignals snapshot every market in
-- that cycle was evaluated against. Written once per cycle before the
-- evaluation loop; signals rows join on cycle_ts.
CREATE TABLE IF NOT EXISTS ext_snapshots (
cycle_ts TIMESTAMPTZ PRIMARY KEY,
btc_price DOUBLE PRECISION,
btc_change_24h DOUBLE PRECISION,
eth_price DOUBLE PRECISION,
eth_change_24h DOUBLE PRECISION,
btc_dominance DOUBLE PRECISION,
fear_greed_index INTEGER,
fear_greed_label TEXT,
total_market_cap_change DOUBLE PRECISION,
valid BOOLEAN
);