signal_log: persist agent panel verdict + per-persona takes to SQLite

Add panel_verdict, panel_confidence, panel_rationale, panel_takes_json
columns to the signals table (additive, idempotent ALTER TABLE migration
for existing DBs). _report_row now reads the panel fields attach_panel
writes onto the SignalReport, so LLM panel verdicts stop evaporating on
process exit. Also documents the Oanda re-pin (round_trip_cost stays
$0.30 for cross-book comparability and the live TP gate; $0.65 is the
measured headline) and adds the feed-divergence / parity / shadow-scan
study tooling referenced by CLAUDE.md.
This commit is contained in:
Harwinder Sekhon
2026-08-14 20:41:54 -04:00
parent 7ad5129f2a
commit 37ff5b383d
9 changed files with 874 additions and 19 deletions
+33 -3
View File
@@ -60,20 +60,34 @@ CREATE TABLE IF NOT EXISTS signals (
news_description TEXT,
news_summary TEXT,
events_json TEXT,
headlines_json TEXT
headlines_json TEXT,
panel_verdict TEXT,
panel_confidence REAL,
panel_rationale TEXT,
panel_takes_json TEXT
);
CREATE INDEX IF NOT EXISTS idx_signals_sym_tf_ts
ON signals (symbol, timeframe, logged_at);
"""
# Additive migration: older DBs created before the panel columns existed
# get them added here. Idempotent — skips columns already present.
_PANEL_COLS = (
("panel_verdict", "TEXT"),
("panel_confidence", "REAL"),
("panel_rationale", "TEXT"),
("panel_takes_json", "TEXT"),
)
_INSERT = """
INSERT INTO signals (
logged_at, source, symbol, timeframe, signal, score, confidence,
quality, action, price, spread, market_regime, volatility_regime,
mtf_json, setup_json, reasons_json, warnings_json,
event_risk, event_description, news_sentiment, news_description,
news_summary, events_json, headlines_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
news_summary, events_json, headlines_json,
panel_verdict, panel_confidence, panel_rationale, panel_takes_json
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
@@ -207,6 +221,8 @@ def _report_row(
events_json, headlines_json = _context_news_json(context)
signal = getattr(report, "final_signal", None)
panel_takes = getattr(report, "panel_takes", None) or []
panel_takes_json = _dumps(panel_takes) if panel_takes else None
return (
datetime.now(timezone.utc).isoformat(),
source,
@@ -232,6 +248,10 @@ def _report_row(
getattr(report, "news_summary", "") or "",
events_json,
headlines_json,
getattr(report, "panel_verdict", None),
_finite(getattr(report, "panel_confidence", None)),
getattr(report, "panel_rationale", None),
panel_takes_json,
)
@@ -251,11 +271,21 @@ class SignalLogger:
self._lock = threading.Lock()
with self._lock:
self._conn.executescript(_SCHEMA)
self._migrate_panel_columns()
@property
def path(self) -> str:
return self._path
def _migrate_panel_columns(self) -> None:
"""Add panel_* columns to pre-existing tables (idempotent)."""
existing = {r[1] for r in self._conn.execute("PRAGMA table_info(signals)")}
for name, col_type in _PANEL_COLS:
if name not in existing:
self._conn.execute(
f"ALTER TABLE signals ADD COLUMN {name} {col_type}")
self._conn.commit()
def log(
self,
report: Any,