chore: remove dead functions and write-only state (no behaviour change)

Zero callers anywhere (tests included), verified by grep/AST:
- dashboard_publisher.DashboardPublisher.publish_all (run_cycle inlines both publishes)
- signal_log.SignalLogger.advance_with_bars (the backfill script uses
  backfill_seeds / resolve_from_store / reseed_unresolvable) and the never-read
  SignalLogger.path property plus its write-only self._path
- llm_client.reset_llm_client (tests reset the singleton by assignment)
- market_outlook._last_run_ts, residue of the old "now - last < 3600" dedup;
  _last_run_hour (the UTC hour bucket) is the live key
- news_engine FundamentalContext.event_impact_on_pairs: written once, read by
  nothing; its only feeder NewsEngine._get_affected_pairs and that helper's
  unit test go with it
- position_sizing.LIVE_DEPTH_BARS (referenced only by its own docstring, which
  now states the depths inline), the unused Iterable import and the unused
  atr_ok parameter of _fill_geometry
- analyze_signal_log: unused `field` import and the unused `rows` parameter of
  persona_scorecard / news_outcome_buckets (both read the DB directly)

The two backfill/label scripts' --db help text now names the config.json
setting it actually falls back to.

Suite: 463 passed, 0 failed (one test removed with its subject).

Claude-Session: https://claude.ai/code/session_0156YhogLytzj9P2iH1skGbL
This commit is contained in:
Harwinder Sekhon
2026-09-06 16:48:24 -04:00
parent c94f78e0b0
commit 985ec83ef3
10 changed files with 19 additions and 102 deletions
+5 -7
View File
@@ -67,7 +67,7 @@ import math
import os import os
import sqlite3 import sqlite3
import sys import sys
from dataclasses import dataclass, field from dataclasses import dataclass
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional, Sequence, Tuple from typing import Dict, List, Optional, Sequence, Tuple
@@ -754,8 +754,7 @@ def _fmt_r(r: Optional[float]) -> str:
return " n/a " if r is None else f"{r:+.2f}R" return " n/a " if r is None else f"{r:+.2f}R"
def persona_scorecard(rows: Sequence[LogRow], events: Sequence[SignalEvent], def persona_scorecard(events: Sequence[SignalEvent], db_path: str) -> List[dict]:
db_path: str) -> List[dict]:
"""Per persona × stance: n, mean R and win rate of the underlying """Per persona × stance: n, mean R and win rate of the underlying
event (needs panel_takes_json — read straight from the DB).""" event (needs panel_takes_json — read straight from the DB)."""
by_row = _row_index(events) by_row = _row_index(events)
@@ -921,8 +920,7 @@ def news_extremes(rows: Sequence[LogRow], streams: Dict[str, List[dict]],
} }
def news_outcome_buckets(rows: Sequence[LogRow], def news_outcome_buckets(events: Sequence[SignalEvent],
events: Sequence[SignalEvent],
db_path: str) -> List[dict]: db_path: str) -> List[dict]:
"""Resolved events bucketed by (direction, news bucket, macro stance). """Resolved events bucketed by (direction, news bucket, macro stance).
@@ -1306,13 +1304,13 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
print_report(rows, events, panel, hit) print_report(rows, events, panel, hit)
print_stored_books(events) print_stored_books(events)
print("\n── Persona scorecard (stance × event outcome) " + "─" * 22) print("\n── Persona scorecard (stance × event outcome) " + "─" * 22)
for p in persona_scorecard(rows, events, args.db): for p in persona_scorecard(events, args.db):
print(f" {p['persona']:11} {p['stance']:8} n={p['n']:3d} " print(f" {p['persona']:11} {p['stance']:8} n={p['n']:3d} "
f"mean {p['mean_r']:+.3f}R win {100 * p['win_rate']:.0f}%") f"mean {p['mean_r']:+.3f}R win {100 * p['win_rate']:.0f}%")
if args.news: if args.news:
print_news(news_lead_lag(rows, streams), print_news(news_lead_lag(rows, streams),
news_extremes(rows, streams), news_extremes(rows, streams),
news_outcome_buckets(rows, events, args.db)) news_outcome_buckets(events, args.db))
print_parity(parity_report(rows)) print_parity(parity_report(rows))
if args.csv: if args.csv:
export_csv(events, args.csv) export_csv(events, args.csv)
-4
View File
@@ -432,10 +432,6 @@ class DashboardPublisher:
if self.news_payload is not None: if self.news_payload is not None:
publish(self.news_payload, os.path.join(self.out_dir, NEWS_FILE)) publish(self.news_payload, os.path.join(self.out_dir, NEWS_FILE))
def publish_all(self) -> None:
self.publish_snapshot()
self.publish_news()
def run_cycle(self, tfs: List[Timeframe]) -> None: def run_cycle(self, tfs: List[Timeframe]) -> None:
for tf in tfs: # sequential — see module docstring (cache dedup) for tf in tfs: # sequential — see module docstring (cache dedup)
self.scan_tf(tf) self.scan_tf(tf)
-7
View File
@@ -198,10 +198,3 @@ def get_llm_client() -> DeepSeekClient:
enabled=settings.get("enabled", True) is not False, enabled=settings.get("enabled", True) is not False,
) )
return _client return _client
def reset_llm_client() -> None:
"""Drop the singleton (tests / config reloads)."""
global _client
with _client_lock:
_client = None
+2 -5
View File
@@ -329,7 +329,6 @@ def _collect_payload(settings: Dict[str, Any]) -> Dict[str, Any]:
# ── Orchestration, hourly dedup, and request budget ────────────────── # ── Orchestration, hourly dedup, and request budget ──────────────────
_state_lock = threading.Lock() _state_lock = threading.Lock()
_last_run_ts: Optional[float] = None
_last_run_hour: Optional[int] = None # UTC hour bucket of the last attempt _last_run_hour: Optional[int] = None # UTC hour bucket of the last attempt
_outlook_day: Optional[_dt.date] = None _outlook_day: Optional[_dt.date] = None
_outlook_count = 0 _outlook_count = 0
@@ -344,10 +343,9 @@ def _skip(reason: str) -> None:
def reset_outlook_state() -> None: def reset_outlook_state() -> None:
"""Drop hourly and daily state (tests / deployment config reloads).""" """Drop hourly and daily state (tests / deployment config reloads)."""
global _last_run_ts, _last_run_hour, _outlook_day, _outlook_count global _last_run_hour, _outlook_day, _outlook_count
global _warned_budget global _warned_budget
with _state_lock: with _state_lock:
_last_run_ts = None
_last_run_hour = None _last_run_hour = None
_outlook_day = None _outlook_day = None
_outlook_count = 0 _outlook_count = 0
@@ -360,7 +358,7 @@ def _begin_attempt(cap: int, force: bool) -> bool:
Hourly dedup is by UTC hour BUCKET, not ``now - last < 3600``: the Hourly dedup is by UTC hour BUCKET, not ``now - last < 3600``: the
H1 loop calls this at boundary+10 s+scan time, so consecutive hours H1 loop calls this at boundary+10 s+scan time, so consecutive hours
were routinely 3,590 s apart and every other hour was dropped.""" were routinely 3,590 s apart and every other hour was dropped."""
global _last_run_ts, _last_run_hour, _outlook_day, _outlook_count global _last_run_hour, _outlook_day, _outlook_count
global _warned_budget global _warned_budget
now = time.time() now = time.time()
hour_bucket = int(now // 3600) hour_bucket = int(now // 3600)
@@ -383,7 +381,6 @@ def _begin_attempt(cap: int, force: bool) -> bool:
_skip("daily budget exhausted") _skip("daily budget exhausted")
return False return False
_outlook_count += 1 _outlook_count += 1
_last_run_ts = now
_last_run_hour = hour_bucket _last_run_hour = hour_bucket
return True return True
-18
View File
@@ -223,7 +223,6 @@ class FundamentalContext:
warning_message: str = "" warning_message: str = ""
news_sentiment: Dict[str, float] = field(default_factory=dict) # currency -> sentiment news_sentiment: Dict[str, float] = field(default_factory=dict) # currency -> sentiment
news_headlines: List[NewsSentiment] = field(default_factory=list) news_headlines: List[NewsSentiment] = field(default_factory=list)
event_impact_on_pairs: Dict[str, str] = field(default_factory=dict)
calendar_available: bool = False # True if live calendar data was fetched calendar_available: bool = False # True if live calendar data was fetched
llm_summary: str = "" # DeepSeek market-context blurb (display-only) llm_summary: str = "" # DeepSeek market-context blurb (display-only)
@@ -415,15 +414,6 @@ class NewsEngine:
) )
break break
# Map upcoming high-impact events to affected pairs
for event in context.events_upcoming_1h:
if event.is_high_impact:
affected = self._get_affected_pairs(event.currency)
for pair in affected:
context.event_impact_on_pairs[pair] = (
f"{event.currency} {event.event} in {event.minutes_until:.0f}min"
)
except Exception as e: except Exception as e:
logger.warning(f"Economic calendar fetch failed: {e}") logger.warning(f"Economic calendar fetch failed: {e}")
@@ -1149,11 +1139,3 @@ class NewsEngine:
except ValueError: except ValueError:
continue continue
return None return None
@staticmethod
def _get_affected_pairs(currency: str) -> List[str]:
_MAP = {
"USD": ["XAU/USD"],
"XAU": ["XAU/USD"],
}
return _MAP.get(currency, [])
+8 -19
View File
@@ -10,11 +10,11 @@ inputs (rule 4: live == backtest parity).
Design contract Design contract
--------------- ---------------
* **Bounded lookbacks.** No feature may read further back than the live * **Bounded lookbacks.** No feature may read further back than the live
fetch depth of its timeframe (``LIVE_DEPTH_BARS``). The primary frame fetch depth of its timeframe (5000 bars on M5/M15, 1460 on H1, 380 on
is hard-capped at ``MAX_LOOKBACK_BARS`` rows, confirmation frames at H4). The primary frame is hard-capped at ``MAX_LOOKBACK_BARS`` rows,
60 rows, and the burst scan at 200 rows, so a feature computed on a confirmation frames at 60 rows, and the burst scan at 200 rows, so a
full backtest frame equals the same feature computed on the shorter feature computed on a full backtest frame equals the same feature
live tail. computed on the shorter live tail.
* **Closed bars only.** The frames handed in are exactly the frames * **Closed bars only.** The frames handed in are exactly the frames
``analyze_from_dataframes`` received: UTC tz-aware, oldest-first, ``analyze_from_dataframes`` received: UTC tz-aware, oldest-first,
capitalized OHLCV columns, forming candle already dropped. The last capitalized OHLCV columns, forming candle already dropped. The last
@@ -46,7 +46,7 @@ import json
import logging import logging
import math import math
from pathlib import Path from pathlib import Path
from typing import Dict, Iterable, Optional, Sequence, Tuple from typing import Dict, Optional, Sequence, Tuple
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@@ -60,17 +60,6 @@ logger = logging.getLogger(__name__)
#: A trained artifact carrying a different value is ignored. #: A trained artifact carrying a different value is ignored.
FEATURE_VERSION = 1 FEATURE_VERSION = 1
#: Bars each timeframe actually holds in LIVE mode. Any feature whose
#: lookback exceeds its timeframe's depth here would be unreproducible
#: live, so the windows below stay far inside these numbers.
LIVE_DEPTH_BARS: Dict[Timeframe, int] = {
Timeframe.M5: 5000,
Timeframe.M15: 5000,
Timeframe.H1: 1460,
Timeframe.H4: 380,
Timeframe.D1: 300,
}
#: Hard cap on any primary-timeframe window used in this module. #: Hard cap on any primary-timeframe window used in this module.
MAX_LOOKBACK_BARS = 500 MAX_LOOKBACK_BARS = 500
@@ -338,7 +327,7 @@ def _fill_time(out: Dict[str, float], dt: pd.Timestamp) -> None:
def _fill_geometry(out: Dict[str, float], P: pd.DataFrame, setup, def _fill_geometry(out: Dict[str, float], P: pd.DataFrame, setup,
atr: float, atr_ok: bool, norm, d: float) -> None: atr: float, norm, d: float) -> None:
out["f_atr"] = max(0.0, _finite(atr)) out["f_atr"] = max(0.0, _finite(atr))
ranges = (P["High"] - P["Low"]).astype(float) ranges = (P["High"] - P["Low"]).astype(float)
last_range = _finite(ranges.iloc[-1]) last_range = _finite(ranges.iloc[-1])
@@ -566,7 +555,7 @@ def build_features(
for step, args in ( for step, args in (
(_fill_time, (out, decision_time(P, primary_tf))), (_fill_time, (out, decision_time(P, primary_tf))),
(_fill_geometry, (out, P, setup, atr, atr_ok, norm, d)), (_fill_geometry, (out, P, setup, atr, norm, d)),
(_fill_exhaustion, (out, report, KL, px, d, norm)), (_fill_exhaustion, (out, report, KL, px, d, norm)),
(_fill_quality, (out, report, setup, IND, d)), (_fill_quality, (out, report, setup, IND, d)),
(_fill_mtf, (out, report, primary_tf, confirmation_tfs, d)), (_fill_mtf, (out, report, primary_tf, confirmation_tfs, d)),
+1 -1
View File
@@ -41,7 +41,7 @@ def _status_counts(con: sqlite3.Connection) -> dict:
def main(argv=None) -> int: def main(argv=None) -> int:
ap = argparse.ArgumentParser(description=__doc__.splitlines()[1]) ap = argparse.ArgumentParser(description=__doc__.splitlines()[1])
ap.add_argument("--db", default=None, help="defaults to signal_log.path") ap.add_argument("--db", default=None, help="defaults to the signal_log.path setting in config.json")
ap.add_argument("--dry-run", action="store_true") ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--reseed-unresolvable", action="store_true") ap.add_argument("--reseed-unresolvable", action="store_true")
ap.add_argument("--symbol", default="XAU/USD") ap.add_argument("--symbol", default="XAU/USD")
+1 -1
View File
@@ -60,7 +60,7 @@ def _counts(con: sqlite3.Connection, symbol: str,
def main(argv=None) -> int: def main(argv=None) -> int:
ap = argparse.ArgumentParser(description=__doc__.splitlines()[1]) ap = argparse.ArgumentParser(description=__doc__.splitlines()[1])
ap.add_argument("--db", default=None, help="defaults to signal_log.path") ap.add_argument("--db", default=None, help="defaults to the signal_log.path setting in config.json")
ap.add_argument("--dry-run", action="store_true", ap.add_argument("--dry-run", action="store_true",
help="read-only candidate counts, writes nothing") help="read-only candidate counts, writes nothing")
ap.add_argument("--budget", type=int, default=100000, ap.add_argument("--budget", type=int, default=100000,
-26
View File
@@ -806,7 +806,6 @@ class SignalLogger:
the daemon's thread pool.""" the daemon's thread pool."""
def __init__(self, path: str): def __init__(self, path: str):
self._path = path
parent = os.path.dirname(path) parent = os.path.dirname(path)
if parent: if parent:
os.makedirs(parent, exist_ok=True) os.makedirs(parent, exist_ok=True)
@@ -860,10 +859,6 @@ class SignalLogger:
raise raise
time.sleep(delay * (attempt + 1)) time.sleep(delay * (attempt + 1))
@property
def path(self) -> str:
return self._path
def _migrate_signal_columns(self) -> None: def _migrate_signal_columns(self) -> None:
"""Add newer signal columns to pre-existing tables (idempotent).""" """Add newer signal columns to pre-existing tables (idempotent)."""
existing = {r[1] for r in self._conn.execute("PRAGMA table_info(signals)")} existing = {r[1] for r in self._conn.execute("PRAGMA table_info(signals)")}
@@ -1291,27 +1286,6 @@ class SignalLogger:
rowid, exc_info=True) rowid, exc_info=True)
return written return written
def advance_with_bars(self, rowid: int, bars: Iterable[Dict[str, Any]],
evidence: str = "external_bars") -> str:
"""Advance ONE pending outcome from caller-supplied bars (backfill
from provider history). Returns the resulting status."""
with self._lock:
row = self._conn.execute(
"SELECT advisory_outcome_status, advisory_outcome_json"
" FROM signals WHERE id=?", (rowid,)).fetchone()
if row is None or row[0] != "PENDING" or not row[1]:
return str(row[0]) if row else "MISSING"
state = advance_outcome(json.loads(row[1]), list(bars))
state["evidence"] = evidence
status = state.get("status", "UNRESOLVABLE")
self._conn.execute(
"UPDATE signals SET advisory_outcome_status=?,"
" advisory_r_multiple=?, advisory_outcome_json=?"
" WHERE id=? AND advisory_outcome_status='PENDING'",
(status, canonical_r(state), _dumps(state), rowid))
self._conn.commit()
return status
def backfill_seeds(self, limit: Optional[int] = None) -> Dict[str, int]: def backfill_seeds(self, limit: Optional[int] = None) -> Dict[str, int]:
"""Assign setup_key / advisory_event_id to legacy setup rows and """Assign setup_key / advisory_event_id to legacy setup rows and
seed the canonical ones that carry no outcome yet. Existing seed the canonical ones that carry no outcome yet. Existing
+2 -14
View File
@@ -881,18 +881,6 @@ def _():
assert NewsEngine._parse_rss_date("not-a-date") is None assert NewsEngine._parse_rss_date("not-a-date") is None
@test("NewsEngine._get_affected_pairs")
def _():
pairs = NewsEngine._get_affected_pairs("USD")
assert "XAU/USD" in pairs
pairs_xau = NewsEngine._get_affected_pairs("XAU")
assert "XAU/USD" in pairs_xau
pairs_unknown = NewsEngine._get_affected_pairs("ARS")
assert pairs_unknown == []
@test("EconomicEvent properties") @test("EconomicEvent properties")
def _(): def _():
future_time = datetime.now(timezone.utc) + timedelta(minutes=30) future_time = datetime.now(timezone.utc) + timedelta(minutes=30)
@@ -8174,7 +8162,7 @@ def _():
"stance": "support"}]))) "stance": "support"}])))
con.commit() con.commit()
con.close() con.close()
buckets = asl.news_outcome_buckets(rows, events, path) buckets = asl.news_outcome_buckets(events, path)
finally: finally:
os.remove(path) os.remove(path)
assert sum(b["n"] for b in buckets) == 3 assert sum(b["n"] for b in buckets) == 3
@@ -9117,7 +9105,7 @@ def _():
ev = _types.SimpleNamespace( ev = _types.SimpleNamespace(
rows=[_types.SimpleNamespace(id=1), _types.SimpleNamespace(id=2)], rows=[_types.SimpleNamespace(id=1), _types.SimpleNamespace(id=2)],
r_headline=2.0) r_headline=2.0)
out = asl.persona_scorecard([], [ev], path) out = asl.persona_scorecard([ev], path)
assert len(out) == 1, out assert len(out) == 1, out
assert out[0]["n"] == 1, f"two rows of ONE event must count once: {out}" assert out[0]["n"] == 1, f"two rows of ONE event must count once: {out}"
assert out[0]["mean_r"] == 2.0 assert out[0]["mean_r"] == 2.0