Files
ten31-signal-engine/signal_engine/signals/resolver.py
T

28 lines
1.7 KiB
Python

"""Resolver — the SEPARATE forward pass that closes the loop (§6.2, §6.3).
ARCHITECTURALLY ISOLATED from the scorers: it has no shared write path with them. Scorers write
candidate_scores + ledger rows with outcome columns NULL and a FROZEN discourse_metric. The resolver
runs later (larger as_of), reads ledger rows whose date_logged < as_of_now, and writes ONLY
resolution_date / discourse_outcome / external_outcome / lead_time_days. It is FORBIDDEN from touching
discourse_metric — that is the structural reason the ledger can't reward noticing what already happened.
Implementation note: real resolutions need forward time (the clock can't be backfilled). For the
backtest, the discourse leg can be resolved by re-running the discourse metric forward from date_logged;
the external leg (price/filings/human check, §6.5) is filled as that evidence arrives. Stubbed now to
lock the architecture; filled out for the forward pilot.
"""
from __future__ import annotations
def resolve_discourse_leg(conn, sc, cfg, *, as_of_now: str) -> int:
"""For each ledger row logged before as_of_now without a resolution, re-measure discourse forward
and set discourse_outcome + lead_time. (Forward-only; never reads/edits discourse_metric.)
Returns count resolved. STUB — implemented for the forward pilot."""
rows = conn.execute(
"SELECT signal_id, date_logged FROM ledger WHERE resolution_date IS NULL AND date_logged < ?",
(as_of_now,),
).fetchall()
# TODO(forward-pilot): re-run windowed independence from date_logged→as_of_now for each row's
# origin derivative; set discourse_outcome in {up_cross_cluster,up_single_cluster,flat,down}.
return 0