signal_aggregator + chart_patterns: drop dead parameters and shadowed literals (no behaviour change)

- analyze_pair(context=...): no caller ever supplied it (main, telegram_bot,
  pair_signals, tests), so the value was always None; the parameter and the
  pass-through to analyze_from_dataframes go (analyze_from_dataframes keeps
  its own context parameter).
- The three specific "SELL FILTER: quality|volatility|regime" stale-prefix
  entries were shadowed by the general "SELL FILTER: " entry in the same
  startswith() tuple; every emitter carries the general prefix.
- chart_patterns: the detectors never read the Open series, so the `o`
  parameter is dropped from the three detectors, the PATTERN_DETECTORS
  lambdas and the dispatcher.
- Corrected the pb_funnel/cp_funnel comment: nothing reads those counters
  (the "read by backtest tooling" claim was stale); they stay as cheap
  diagnostics.

Parity: 12-month M5 and H1 books regenerated byte-identical
(md5 a28fed4becd056466c26d6a947e1a0e5 / d20405ef64f069770c258bb9bed1ac90).
Suite: 461 passed, 0 failed.

Claude-Session: https://claude.ai/code/session_0156YhogLytzj9P2iH1skGbL
This commit is contained in:
Harwinder Sekhon
2026-09-06 16:55:26 -04:00
parent 3cf3c60b89
commit 04bdc1737f
2 changed files with 13 additions and 18 deletions
+10 -11
View File
@@ -97,7 +97,7 @@ def _first_close_beyond(closes: np.ndarray, start: int, level: float,
# ── Double bottom / top ────────────────────────────────────────────── # ── Double bottom / top ──────────────────────────────────────────────
def _detect_double(o, h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]: def _detect_double(h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]:
w = cfg.cp_swing_window w = cfg.cp_swing_window
ext = l if bullish else h ext = l if bullish else h
idx = _swing_indices(ext, w, find_high=not bullish) idx = _swing_indices(ext, w, find_high=not bullish)
@@ -147,7 +147,7 @@ def _detect_double(o, h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]:
# ── Head & shoulders (top and inverse) ─────────────────────────────── # ── Head & shoulders (top and inverse) ───────────────────────────────
def _detect_hs(o, h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]: def _detect_hs(h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]:
"""bullish=True → inverse H&S (three swing LOWS, head deepest).""" """bullish=True → inverse H&S (three swing LOWS, head deepest)."""
w = cfg.cp_swing_window w = cfg.cp_swing_window
ext = l if bullish else h ext = l if bullish else h
@@ -210,7 +210,7 @@ def _detect_hs(o, h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]:
# ── Flags (bull and bear) ──────────────────────────────────────────── # ── Flags (bull and bear) ────────────────────────────────────────────
def _detect_flag(o, h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]: def _detect_flag(h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]:
n = len(c) n = len(c)
lo_r, hi_r = cfg.cp_flag_retrace lo_r, hi_r = cfg.cp_flag_retrace
if bullish: if bullish:
@@ -257,12 +257,12 @@ def _detect_flag(o, h, l, c, atr, cfg, bullish: bool) -> Optional[Dict]:
# Priority order: most structured / rarest first — if two patterns # Priority order: most structured / rarest first — if two patterns
# complete on the same bar, the more specific one names the trade. # complete on the same bar, the more specific one names the trade.
PATTERN_DETECTORS = { PATTERN_DETECTORS = {
"inv_hs": lambda o, h, l, c, atr, cfg: _detect_hs(o, h, l, c, atr, cfg, True), "inv_hs": lambda h, l, c, atr, cfg: _detect_hs(h, l, c, atr, cfg, True),
"hs_top": lambda o, h, l, c, atr, cfg: _detect_hs(o, h, l, c, atr, cfg, False), "hs_top": lambda h, l, c, atr, cfg: _detect_hs(h, l, c, atr, cfg, False),
"double_bottom": lambda o, h, l, c, atr, cfg: _detect_double(o, h, l, c, atr, cfg, True), "double_bottom": lambda h, l, c, atr, cfg: _detect_double(h, l, c, atr, cfg, True),
"double_top": lambda o, h, l, c, atr, cfg: _detect_double(o, h, l, c, atr, cfg, False), "double_top": lambda h, l, c, atr, cfg: _detect_double(h, l, c, atr, cfg, False),
"bull_flag": lambda o, h, l, c, atr, cfg: _detect_flag(o, h, l, c, atr, cfg, True), "bull_flag": lambda h, l, c, atr, cfg: _detect_flag(h, l, c, atr, cfg, True),
"bear_flag": lambda o, h, l, c, atr, cfg: _detect_flag(o, h, l, c, atr, cfg, False), "bear_flag": lambda h, l, c, atr, cfg: _detect_flag(h, l, c, atr, cfg, False),
} }
@@ -276,7 +276,6 @@ def detect_chart_pattern(df: pd.DataFrame, atr: float, cfg) -> Optional[Dict]:
if atr <= 0 or len(df) < cfg.cp_lookback + 1: if atr <= 0 or len(df) < cfg.cp_lookback + 1:
return None return None
tail = df.iloc[-(cfg.cp_lookback + 1):] tail = df.iloc[-(cfg.cp_lookback + 1):]
o = tail["Open"].to_numpy(dtype=np.float64)
h = tail["High"].to_numpy(dtype=np.float64) h = tail["High"].to_numpy(dtype=np.float64)
l = tail["Low"].to_numpy(dtype=np.float64) l = tail["Low"].to_numpy(dtype=np.float64)
c = tail["Close"].to_numpy(dtype=np.float64) c = tail["Close"].to_numpy(dtype=np.float64)
@@ -284,7 +283,7 @@ def detect_chart_pattern(df: pd.DataFrame, atr: float, cfg) -> Optional[Dict]:
for name, det in PATTERN_DETECTORS.items(): for name, det in PATTERN_DETECTORS.items():
if name not in enabled: if name not in enabled:
continue continue
hit = det(o, h, l, c, atr, cfg) hit = det(h, l, c, atr, cfg)
if hit is not None: if hit is not None:
return hit return hit
return None return None
+3 -7
View File
@@ -150,8 +150,9 @@ class SignalReport:
class SignalAggregator: class SignalAggregator:
"""Master aggregator for live signal analysis.""" """Master aggregator for live signal analysis."""
# Diagnostic funnel for the M5 pullback route (shared across # Diagnostic rejection counters for the M5 pullback route (class-level,
# instances; read by backtest tooling, harmless in live use). # shared across instances). Nothing in the repo reads them; they are
# cheap and kept for ad-hoc inspection from a REPL.
pb_funnel: Dict[str, int] = {} pb_funnel: Dict[str, int] = {}
# Same, for the chart-pattern route (XAU_CP). # Same, for the chart-pattern route (XAU_CP).
cp_funnel: Dict[str, int] = {} cp_funnel: Dict[str, int] = {}
@@ -172,7 +173,6 @@ class SignalAggregator:
def analyze_pair( def analyze_pair(
self, self,
symbol: Optional[str] = None, symbol: Optional[str] = None,
context: Optional[FundamentalContext] = None,
log_source: Optional[str] = None, log_source: Optional[str] = None,
) -> SignalReport: ) -> SignalReport:
"""Live-mode entry: fetch all needed timeframes then delegate. """Live-mode entry: fetch all needed timeframes then delegate.
@@ -244,7 +244,6 @@ class SignalAggregator:
report = self.analyze_from_dataframes( report = self.analyze_from_dataframes(
symbol=symbol, symbol=symbol,
dfs=dfs, dfs=dfs,
context=context,
use_news=True, use_news=True,
) )
@@ -919,9 +918,6 @@ class SignalAggregator:
"EXHAUSTION BLOCK", "EXHAUSTION BLOCK",
"EXHAUSTED TREND FILTER:", "EXHAUSTED TREND FILTER:",
"MTF OPPOSITION:", "MTF OPPOSITION:",
"SELL FILTER: quality",
"SELL FILTER: volatility",
"SELL FILTER: regime",
"SELL FILTER: ", "SELL FILTER: ",
"M5 FILTER: |score|", "M5 FILTER: |score|",
"M5 FILTER: no MTF agreement", "M5 FILTER: no MTF agreement",