From 6c36d04f7aaed2d06e4167247a183730fb0e2ec9 Mon Sep 17 00:00:00 2001 From: momentum dev Date: Thu, 2 Jul 2026 12:18:57 -0400 Subject: [PATCH] Component attribution: per-stage scorecard (CLI + /api/attribution + dashboard + board pulse) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The owner's modularity/monitoring ask: rate each pipeline component so what's earning (and what's cutting) is visible instead of inferred. Everything derives from the existing event stream — no pipeline changes. - research/attribution.py: compute_attribution(days) -> per-day per-stage metrics: DISCOVERY (symbols found + precision = fraction that ever go ready), SIGNALS (ready split EARLY-vs-LATE against the confirmation window — the only +EV ones), GATES (risk_rule histogram: what each gate cut), EXECUTION (submissions vs cancels = fill efficiency), OUTCOMES (realized P&L, win rate, per-close detail). CLI: python -m research.attribution. All queries filter the native timestamp column first (payload casts on the 676k-row events table time out). - api/main.py: GET /api/attribution?days=N. - dashboard.html: "Component Attribution" panel, lazy-loads on open, per-day table with gate-histogram hover. - momentum_cli board header: live pulse line — today's early/late signal split + top gate cuts (two tiny date-scoped queries). Verified live: CLI scorecard over 7 days, endpoint returns real ratings (e.g. 2026-07-02: discovery 45%, early 38%, fill 39%, win 50%, P&L +$29), board renders the pulse line. Co-Authored-By: Claude Opus 4.8 --- api/main.py | 6 ++ api/static/dashboard.html | 38 +++++++++ momentum_cli.py | 22 +++++- research/attribution.py | 157 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 research/attribution.py diff --git a/api/main.py b/api/main.py index 56977c2..6028d80 100644 --- a/api/main.py +++ b/api/main.py @@ -391,6 +391,12 @@ class DashboardHandler(BaseHTTPRequestHandler): symbol = (q.get("symbol") or [""])[0] minutes = int((q.get("minutes") or ["60"])[0]) self._send_json(self.state.bars(symbol, minutes)) + elif route == "/api/attribution": + # per-component stage ratings (research/attribution.py); computed on + # demand — a handful of indexed event queries, fine per click + from research.attribution import compute_attribution + days = int((self._query().get("days") or ["7"])[0]) + self._send_json(compute_attribution(days=days)) elif route == "/api/health": self._send_json({"ok": True, "mode": self.state.execution_mode}) else: diff --git a/api/static/dashboard.html b/api/static/dashboard.html index 6401fa2..6f3d4dd 100644 --- a/api/static/dashboard.html +++ b/api/static/dashboard.html @@ -506,8 +506,46 @@

Order Lifecycle 0

+ +
+

Component Attribution per-stage ratings: discovery → signals (early vs late) → gates → execution → P&L · loads on open

+
Open to load the last 7 days.
+