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 sqlite3
import sys
from dataclasses import dataclass, field
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
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"
def persona_scorecard(rows: Sequence[LogRow], events: Sequence[SignalEvent],
db_path: str) -> List[dict]:
def persona_scorecard(events: Sequence[SignalEvent], db_path: str) -> List[dict]:
"""Per persona × stance: n, mean R and win rate of the underlying
event (needs panel_takes_json — read straight from the DB)."""
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],
events: Sequence[SignalEvent],
def news_outcome_buckets(events: Sequence[SignalEvent],
db_path: str) -> List[dict]:
"""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_stored_books(events)
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} "
f"mean {p['mean_r']:+.3f}R win {100 * p['win_rate']:.0f}%")
if args.news:
print_news(news_lead_lag(rows, streams),
news_extremes(rows, streams),
news_outcome_buckets(rows, events, args.db))
news_outcome_buckets(events, args.db))
print_parity(parity_report(rows))
if args.csv:
export_csv(events, args.csv)
-4
View File
@@ -432,10 +432,6 @@ class DashboardPublisher:
if self.news_payload is not None:
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:
for tf in tfs: # sequential — see module docstring (cache dedup)
self.scan_tf(tf)
-7
View File
@@ -198,10 +198,3 @@ def get_llm_client() -> DeepSeekClient:
enabled=settings.get("enabled", True) is not False,
)
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 ──────────────────
_state_lock = threading.Lock()
_last_run_ts: Optional[float] = None
_last_run_hour: Optional[int] = None # UTC hour bucket of the last attempt
_outlook_day: Optional[_dt.date] = None
_outlook_count = 0
@@ -344,10 +343,9 @@ def _skip(reason: str) -> None:
def reset_outlook_state() -> None:
"""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
with _state_lock:
_last_run_ts = None
_last_run_hour = None
_outlook_day = None
_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
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."""
global _last_run_ts, _last_run_hour, _outlook_day, _outlook_count
global _last_run_hour, _outlook_day, _outlook_count
global _warned_budget
now = time.time()
hour_bucket = int(now // 3600)
@@ -383,7 +381,6 @@ def _begin_attempt(cap: int, force: bool) -> bool:
_skip("daily budget exhausted")
return False
_outlook_count += 1
_last_run_ts = now
_last_run_hour = hour_bucket
return True
-18
View File
@@ -223,7 +223,6 @@ class FundamentalContext:
warning_message: str = ""
news_sentiment: Dict[str, float] = field(default_factory=dict) # currency -> sentiment
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
llm_summary: str = "" # DeepSeek market-context blurb (display-only)
@@ -415,15 +414,6 @@ class NewsEngine:
)
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:
logger.warning(f"Economic calendar fetch failed: {e}")
@@ -1149,11 +1139,3 @@ class NewsEngine:
except ValueError:
continue
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
---------------
* **Bounded lookbacks.** No feature may read further back than the live
fetch depth of its timeframe (``LIVE_DEPTH_BARS``). The primary frame
is hard-capped at ``MAX_LOOKBACK_BARS`` rows, confirmation frames at
60 rows, and the burst scan at 200 rows, so a feature computed on a
full backtest frame equals the same feature computed on the shorter
live tail.
fetch depth of its timeframe (5000 bars on M5/M15, 1460 on H1, 380 on
H4). The primary frame is hard-capped at ``MAX_LOOKBACK_BARS`` rows,
confirmation frames at 60 rows, and the burst scan at 200 rows, so a
feature computed on a full backtest frame equals the same feature
computed on the shorter live tail.
* **Closed bars only.** The frames handed in are exactly the frames
``analyze_from_dataframes`` received: UTC tz-aware, oldest-first,
capitalized OHLCV columns, forming candle already dropped. The last
@@ -46,7 +46,7 @@ import json
import logging
import math
from pathlib import Path
from typing import Dict, Iterable, Optional, Sequence, Tuple
from typing import Dict, Optional, Sequence, Tuple
import numpy as np
import pandas as pd
@@ -60,17 +60,6 @@ logger = logging.getLogger(__name__)
#: A trained artifact carrying a different value is ignored.
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.
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,
atr: float, atr_ok: bool, norm, d: float) -> None:
atr: float, norm, d: float) -> None:
out["f_atr"] = max(0.0, _finite(atr))
ranges = (P["High"] - P["Low"]).astype(float)
last_range = _finite(ranges.iloc[-1])
@@ -566,7 +555,7 @@ def build_features(
for step, args in (
(_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_quality, (out, report, setup, IND, 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:
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("--reseed-unresolvable", action="store_true")
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:
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",
help="read-only candidate counts, writes nothing")
ap.add_argument("--budget", type=int, default=100000,
-26
View File
@@ -806,7 +806,6 @@ class SignalLogger:
the daemon's thread pool."""
def __init__(self, path: str):
self._path = path
parent = os.path.dirname(path)
if parent:
os.makedirs(parent, exist_ok=True)
@@ -860,10 +859,6 @@ class SignalLogger:
raise
time.sleep(delay * (attempt + 1))
@property
def path(self) -> str:
return self._path
def _migrate_signal_columns(self) -> None:
"""Add newer signal columns to pre-existing tables (idempotent)."""
existing = {r[1] for r in self._conn.execute("PRAGMA table_info(signals)")}
@@ -1291,27 +1286,6 @@ class SignalLogger:
rowid, exc_info=True)
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]:
"""Assign setup_key / advisory_event_id to legacy setup rows and
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
@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")
def _():
future_time = datetime.now(timezone.utc) + timedelta(minutes=30)
@@ -8174,7 +8162,7 @@ def _():
"stance": "support"}])))
con.commit()
con.close()
buckets = asl.news_outcome_buckets(rows, events, path)
buckets = asl.news_outcome_buckets(events, path)
finally:
os.remove(path)
assert sum(b["n"] for b in buckets) == 3
@@ -9117,7 +9105,7 @@ def _():
ev = _types.SimpleNamespace(
rows=[_types.SimpleNamespace(id=1), _types.SimpleNamespace(id=2)],
r_headline=2.0)
out = asl.persona_scorecard([], [ev], path)
out = asl.persona_scorecard([ev], path)
assert len(out) == 1, out
assert out[0]["n"] == 1, f"two rows of ONE event must count once: {out}"
assert out[0]["mean_r"] == 2.0