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). Claude-Session: https://claude.ai/code/session_01RWV2yLt3YXHHGVYHSQU2EE
This commit is contained in:
+30
-7
@@ -9,7 +9,7 @@ import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, List, Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
@@ -21,7 +21,7 @@ _NY_TZ = ZoneInfo("America/New_York")
|
||||
from chart_patterns import detect_chart_pattern
|
||||
from config import PlatformConfig, SignalStrength, Timeframe
|
||||
from providers import BaseDataProvider, make_data_provider
|
||||
from engine import TechnicalEngine, TechnicalAnalysis
|
||||
from engine import TechnicalEngine, TechnicalAnalysis, regime_weight_table
|
||||
from news_engine import NewsEngine, FundamentalContext
|
||||
import signal_log
|
||||
|
||||
@@ -322,11 +322,13 @@ class SignalAggregator:
|
||||
logger.debug("signal log: news context unavailable",
|
||||
exc_info=True)
|
||||
extra = {"scan_kind": "blocked" if blocked else "scan"}
|
||||
try:
|
||||
extra["config_fingerprint"] = self.gate_config_fingerprint()
|
||||
except Exception:
|
||||
logger.debug("signal log: config fingerprint unavailable",
|
||||
exc_info=True)
|
||||
for key, fingerprint in (
|
||||
("config_fingerprint", self.gate_config_fingerprint),
|
||||
("engine_fingerprint", self.engine_config_fingerprint)):
|
||||
try:
|
||||
extra[key] = fingerprint()
|
||||
except Exception:
|
||||
logger.debug("signal log: %s unavailable", key, exc_info=True)
|
||||
signal_log.log_signal(
|
||||
report,
|
||||
context=context,
|
||||
@@ -365,6 +367,27 @@ class SignalAggregator:
|
||||
json.dumps(payload, sort_keys=True, default=str).encode("utf-8")
|
||||
).hexdigest()[:12]
|
||||
|
||||
def engine_config_fingerprint(self) -> str:
|
||||
"""Short hash of the SCORING configuration this process runs with.
|
||||
|
||||
gate_config_fingerprint covers the post-grade gates; this covers
|
||||
what produced the composite in the first place — indicator
|
||||
parameters, signal thresholds/geometry, the regime weight
|
||||
multipliers actually in force (override included) and the
|
||||
timeframe set. Logged on every row so indicator-weight research
|
||||
can tell which rows share a scoring configuration."""
|
||||
payload = {
|
||||
"technical": asdict(self.config.technical),
|
||||
"signals": asdict(self.config.signals),
|
||||
"regime_weights": regime_weight_table(self.config.technical),
|
||||
"primary": self.config.primary_timeframe.value,
|
||||
"confirm": [tf.value for tf in self.config.confirmation_timeframes],
|
||||
"display_name": self.config.display_name,
|
||||
}
|
||||
return hashlib.sha1(
|
||||
json.dumps(payload, sort_keys=True, default=str).encode("utf-8")
|
||||
).hexdigest()[:12]
|
||||
|
||||
def _apply_regime_filter_live(
|
||||
self, report: SignalReport, dfs: Dict[Timeframe, pd.DataFrame]
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user