signal_log: weight-research data points, scan_outcomes forward labels, watch provenance

New additive columns on every logged scan: bar_ts (primary closed-bar label, indexed with
timeframe), engine_fingerprint (hash of the scoring config incl. the regime weight table in
force - SignalAggregator.engine_config_fingerprint, twin of the gate fingerprint), code_version
(short git SHA), penalty_tags_json / penalty_total / momentum_fading / exhaustion_count, and
post-hoc watch_level / watch_kinds / watch_line written by violent_watch.attach_watch via
signal_log.update_watch. Each indicators_json entry now also carries eff_weight (base weight x
regime multiplier, the weight the composite actually used) and wscore.

engine: IndicatorSignal.effective_weight / weighted_score are stamped inside _compute_composite
(record-keeping only); regime_weight_table(config) is the single source of truth for the
multiplier table (override honoured). Composite output byte-identical (120-report hash proof).

New table scan_outcomes: model-free forward moves per scan (fwd_1h/4h/24h in $ and ATRs,
mfe/mae over 24h, burst_1h, bars_used, complete) measured on the stored candle stream with the
advisory model's continuity rule; labelled opportunistically at the end of log() (time-boxed,
throttled, idempotent) - no daemon; scripts/label_scan_outcomes.py drives the backlog
(--dry-run is read-only). analyze_signal_log --scan-outcomes: forward move + hit rate by
TF x signal/grade with a NEUTRAL baseline and burst rate by watch level. Readers dedup by the
first row per (timeframe, bar_ts).

Module 31 (10 tests). Suite: 463 passed. Backtester untouched (test-locked).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWV2yLt3YXHHGVYHSQU2EE
This commit is contained in:
Harwinder Sekhon
2026-09-05 22:22:59 -04:00
parent 97f6a0e206
commit e08133e2b5
8 changed files with 1117 additions and 39 deletions
+16
View File
@@ -350,6 +350,21 @@ def assess(report: Any,
readings=readings, likely_direction=direction)
def _log_watch(report: Any, watch: "ViolentWatch") -> None:
"""Persist the assessment onto the scan's signals.db row (fail-safe).
The row is inserted inside analyze_pair — BEFORE a surface attaches
the watch — so these columns arrive by post-hoc update, exactly like
the panel verdict. Level and kinds are written even when the display
line is off: evaluating the watch needs the assessment, not the text.
"""
try:
from signal_log import log_watch_update
log_watch_update(report, watch)
except Exception:
logger.debug("violent watch: logging failed", exc_info=True)
def attach_watch(report: Any,
primary_tf: Optional[Timeframe],
settings: Dict[str, Any],
@@ -377,6 +392,7 @@ def attach_watch(report: Any,
watch = assess(report, moment, events, primary_tf)
if settings.get("alert_line", True):
report.watch_line = watch.line()
_log_watch(report, watch)
return watch
except Exception:
logger.exception("violent watch: assessment failed")