Initial commit: Ten31 Signal Engine (ingest, scoring brain, corpus seeds)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""Pre-registered confusion matrix on the §7.1 derivatives (DESIGN_v2 §1.3).
|
||||
|
||||
Measures PRECISION and RECALL, not recall alone. Uses the engine's already-stored candidate_scores
|
||||
(cleared_date + whisper_date) × the pre-registered external repricing (resolution.K2023.yaml). Reports
|
||||
the matrix at BOTH the cleared level (what the engine fired) and the whisper level (what it saw before
|
||||
the independence floor) — the delta is the empirical answer to the gate debate.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
import yaml
|
||||
|
||||
from .external import basket_index, fetch_eod, resolve_reprice, runway_at_signal
|
||||
|
||||
|
||||
def _engine_dates(conn) -> dict[str, dict]:
|
||||
"""For each under_acted node: earliest cleared as_of and earliest whisper as_of (n_conf>=4, a>0)."""
|
||||
rows = conn.execute(
|
||||
"SELECT node_id, conviction_id, as_of, cleared_evidence_bar ev, inputs_json "
|
||||
"FROM candidate_scores WHERE scorer='under_acted'"
|
||||
).fetchall()
|
||||
out: dict[str, dict] = {}
|
||||
for r in rows:
|
||||
k = r["node_id"] or r["conviction_id"]
|
||||
i = json.loads(r["inputs_json"])
|
||||
d = out.setdefault(k, {"cleared": None, "whisper": None})
|
||||
if r["ev"] and (d["cleared"] is None or r["as_of"] < d["cleared"]):
|
||||
d["cleared"] = r["as_of"]
|
||||
if i.get("n_confirmed", 0) >= 4 and i.get("a_corrob", 0) > 0:
|
||||
if d["whisper"] is None or r["as_of"] < d["whisper"]:
|
||||
d["whisper"] = r["as_of"]
|
||||
return out
|
||||
|
||||
|
||||
def _lead_days(repricing_date: str, signal_date: str | None) -> int | None:
|
||||
if not signal_date or not repricing_date:
|
||||
return None
|
||||
return (datetime.strptime(repricing_date, "%Y-%m-%d") - datetime.strptime(signal_date, "%Y-%m-%d")).days
|
||||
|
||||
|
||||
def run_confusion(conn, cfg, spec_path: str) -> dict:
|
||||
spec = yaml.safe_load(open(spec_path))
|
||||
w, rule = spec["window"], spec["rule"]
|
||||
engine = _engine_dates(conn)
|
||||
price_cache: dict[str, list] = {}
|
||||
|
||||
rows = []
|
||||
for node, basket in spec["baskets"].items():
|
||||
prices = {}
|
||||
for sym in basket:
|
||||
if sym not in price_cache:
|
||||
price_cache[sym] = fetch_eod(cfg.fmp_api_key, sym, w["start"], w["end"])
|
||||
prices[sym] = price_cache[sym]
|
||||
missing = [s for s in basket if not prices[s]]
|
||||
idx = basket_index(prices)
|
||||
res = resolve_reprice(idx, threshold_pct=rule["threshold_pct"], hold_pct=rule["hold_pct"],
|
||||
hold_days=rule["hold_days"])
|
||||
ed = engine.get(node, {"cleared": None, "whisper": None})
|
||||
rows.append({
|
||||
"node": node, "basket": basket, "missing": missing,
|
||||
"confirmed": res["confirmed"], "repricing_date": res["repricing_date"], "peak_pct": res["peak_pct"],
|
||||
"cleared_date": ed["cleared"], "whisper_date": ed["whisper"],
|
||||
"lead_cleared": _lead_days(res["repricing_date"], ed["cleared"]) if res["confirmed"] else None,
|
||||
"lead_whisper": _lead_days(res["repricing_date"], ed["whisper"]) if res["confirmed"] else None,
|
||||
# DESIGN_v2.1 Correction A: runway = fraction of the durable move still ahead at signal
|
||||
"runway_cleared": runway_at_signal(idx, ed["cleared"]) if res["confirmed"] else None,
|
||||
"runway_whisper": runway_at_signal(idx, ed["whisper"]) if res["confirmed"] else None,
|
||||
})
|
||||
|
||||
def classify(r, level):
|
||||
fired = bool(r[f"{level}_date"])
|
||||
real = r["confirmed"]
|
||||
return "TP" if (fired and real) else "FP" if (fired and not real) else "FN" if real else "TN"
|
||||
|
||||
def matrix(level):
|
||||
c = {"TP": 0, "FP": 0, "FN": 0, "TN": 0}
|
||||
for r in rows:
|
||||
c[classify(r, level)] += 1
|
||||
p = c["TP"] / (c["TP"] + c["FP"]) if (c["TP"] + c["FP"]) else None
|
||||
rec = c["TP"] / (c["TP"] + c["FN"]) if (c["TP"] + c["FN"]) else None
|
||||
return c, p, rec
|
||||
|
||||
return {"rows": rows, "cleared": matrix("cleared"), "whisper": matrix("whisper"),
|
||||
"classify": classify}
|
||||
Reference in New Issue
Block a user