Primera PR del motor de replay (línea 2.1 del plan de fase). Hallazgo que la motiva: las tablas signals y markets existen en el schema desde Fase 2/5 pero nunca tuvieron escritor (0 filas en prod). Sin archivo de decisiones no hay replay — y cada día sin recorder es muestra perdida.
Cambios
signals: 1 fila por mercado evaluado y ciclo, con INPUTS (news_sentiment, feat_*_lo, volume_24h, days_to_resolution) además de los outputs ya definidos (probs, edges, gates). skip_reason granular: unsupported/no_signals/prior_extreme/family/edge_net/confidence/reentry_guard. news_budget_skipped distingue "GNews no consultado" (budget 5/ciclo) de "sin noticias" — sin esto el replay de politics leería la inanición de budget como ausencia de señal.
ext_snapshots (nueva): 1 fila/ciclo con el snapshot de ExternalSignals; las filas de signals se unen por cycle_ts.
markets: upsert de metadatos por ciclo (el replay reconstruye Market desde aquí).
Retención: prune diario de filas > SIGNALS_RETENTION_DAYS (90 por defecto). Volumen estimado ~50 MB/mes.
SIGNAL_RECORDER_ENABLED (default true) gatea las escrituras; todas van en try/except — el recorder no puede romper el trading.
acted_on=true solo cuando la señal acabó en trade ejecutado (evaluate puede emitir señal que el risk sizing rechace).
Cumplimiento del freeze
Cambios en bayesian.py puramente aditivos (acumulación de registros en cada punto de salida de evaluate()). Cero cambios en pesos, umbrales, gates, exposure, sizing o flags de Manifold.
Validación
85/85 tests (10 nuevos en tests/test_signal_recorder.py).
Migración validada contra el postgres de prod en transacción con ROLLBACK (idempotente, IF NOT EXISTS).
## Contexto
Primera PR del motor de replay (línea 2.1 del plan de fase). Hallazgo que la motiva: las tablas `signals` y `markets` existen en el schema desde Fase 2/5 pero **nunca tuvieron escritor** (0 filas en prod). Sin archivo de decisiones no hay replay — y cada día sin recorder es muestra perdida.
## Cambios
- **`signals`**: 1 fila por mercado evaluado y ciclo, con INPUTS (news_sentiment, feat_*_lo, volume_24h, days_to_resolution) además de los outputs ya definidos (probs, edges, gates). `skip_reason` granular: `unsupported/no_signals/prior_extreme/family/edge_net/confidence/reentry_guard`. `news_budget_skipped` distingue "GNews no consultado" (budget 5/ciclo) de "sin noticias" — sin esto el replay de politics leería la inanición de budget como ausencia de señal.
- **`ext_snapshots`** (nueva): 1 fila/ciclo con el snapshot de ExternalSignals; las filas de signals se unen por `cycle_ts`.
- **`markets`**: upsert de metadatos por ciclo (el replay reconstruye `Market` desde aquí).
- **Retención**: prune diario de filas > `SIGNALS_RETENTION_DAYS` (90 por defecto). Volumen estimado ~50 MB/mes.
- **`SIGNAL_RECORDER_ENABLED`** (default true) gatea las escrituras; todas van en try/except — el recorder no puede romper el trading.
- `acted_on=true` solo cuando la señal acabó en trade ejecutado (evaluate puede emitir señal que el risk sizing rechace).
## Cumplimiento del freeze
Cambios en `bayesian.py` puramente aditivos (acumulación de registros en cada punto de salida de `evaluate()`). Cero cambios en pesos, umbrales, gates, exposure, sizing o flags de Manifold.
## Validación
- 85/85 tests (10 nuevos en `tests/test_signal_recorder.py`).
- Migración validada contra el postgres de prod en transacción con ROLLBACK (idempotente, `IF NOT EXISTS`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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>
chemavx
merged commit eb4f67414a into main2026-07-02 12:16:13 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Contexto
Primera PR del motor de replay (línea 2.1 del plan de fase). Hallazgo que la motiva: las tablas
signalsymarketsexisten en el schema desde Fase 2/5 pero nunca tuvieron escritor (0 filas en prod). Sin archivo de decisiones no hay replay — y cada día sin recorder es muestra perdida.Cambios
signals: 1 fila por mercado evaluado y ciclo, con INPUTS (news_sentiment, feat_*_lo, volume_24h, days_to_resolution) además de los outputs ya definidos (probs, edges, gates).skip_reasongranular:unsupported/no_signals/prior_extreme/family/edge_net/confidence/reentry_guard.news_budget_skippeddistingue "GNews no consultado" (budget 5/ciclo) de "sin noticias" — sin esto el replay de politics leería la inanición de budget como ausencia de señal.ext_snapshots(nueva): 1 fila/ciclo con el snapshot de ExternalSignals; las filas de signals se unen porcycle_ts.markets: upsert de metadatos por ciclo (el replay reconstruyeMarketdesde aquí).SIGNALS_RETENTION_DAYS(90 por defecto). Volumen estimado ~50 MB/mes.SIGNAL_RECORDER_ENABLED(default true) gatea las escrituras; todas van en try/except — el recorder no puede romper el trading.acted_on=truesolo cuando la señal acabó en trade ejecutado (evaluate puede emitir señal que el risk sizing rechace).Cumplimiento del freeze
Cambios en
bayesian.pypuramente aditivos (acumulación de registros en cada punto de salida deevaluate()). Cero cambios en pesos, umbrales, gates, exposure, sizing o flags de Manifold.Validación
tests/test_signal_recorder.py).IF NOT EXISTS).🤖 Generated with Claude Code