From 35f1eb17b86515789a121184425c6745d349f870 Mon Sep 17 00:00:00 2001 From: Harwinder Sekhon Date: Sun, 6 Sep 2026 17:47:45 -0400 Subject: [PATCH] chore: drop unreachable branches outside the signal core (no behaviour change) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - providers: _to_oanda_instrument / _to_tiingo_ticker / _to_td_symbol no longer guess a wire symbol for pairs that are not registered — the registries are the only source, so an unknown symbol raises the provider's DataIntegrityError instead of silently fetching something else. Every scanned symbol is validated against PAIR_INFO at import, so nothing reachable changes. - main / telegram_bot _fmt_price: the sub-$10 five-decimal branch could never fire for gold. - news_engine._parse_pair: BASE/QUOTE only; the Yahoo "=X" fallback was unreachable (its test goes with it). - dashboard_publisher.event_to_dict: drop minutes_until (frozen at publish time, misleading) and is_high_impact — dashboard.js never read either. - static/dashboard.html: unused id="cal-table". Tried and reverted: trimming the LogRow fields nothing in analyze_signal_log reads — the tests read them to verify the loader round-trips those DB columns, so they stay. Suite: 460 passed, 0 failed. Claude-Session: https://claude.ai/code/session_0156YhogLytzj9P2iH1skGbL --- CLAUDE.md | 4 ++-- dashboard_publisher.py | 6 ++---- main.py | 4 ++-- news_engine.py | 9 ++++----- providers/oanda.py | 6 ++++-- providers/tiingo.py | 6 ++++-- providers/twelve_data.py | 8 ++++---- static/dashboard.html | 2 +- telegram_bot.py | 2 +- test_all.py | 16 ++++++---------- 10 files changed, 30 insertions(+), 33 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b7b227f..2c67cc8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ Every numeric threshold in `config.py` and `pair_signals/xauusd.py` was backtest ```bash python test_all.py # QA suite, no network (mocked); prints its own count - # (461 tests / 31 modules as of 2026-09-06) + # (460 tests / 31 modules as of 2026-09-06) python main.py --pair XAUUSD --timeframe H1 --detail # CLI signal report (--json for full dump, # --panel for the advisory LLM agent panel, --outlook for the # hourly AI market brief; the violent-move watch line appears @@ -103,4 +103,4 @@ Signal currency everywhere is `SignalStrength` (−2..+2). DataFrames are UTC tz - 2026-09-03 signal-log campaign shipped two new per-TF gates (`backtest/research/signal_log_campaign_2026-09.md`): M5 `sell_blocked_regimes=["transitioning"]` and H1 `sell_min_quality=[]` (all H1 shorts blocked). Current-HEAD M5 canonical book is now `backtest/oanda_m5_12mo_065_nopb_20260903.csv` (557 / +112.21R / PF 1.56, halves +55.9/+56.4); H1 reference `backtest/oanda_h1_12mo_065_20260903.csv` (91 / +22.2R / PF 1.79; 5yr +49.1R PF 1.46; OOS +16.7R PF 1.74). - 2026-09-06 stake-sizing campaign (`backtest/research/sizing_campaign_2026-09.md`): a walk-forward "second opinion" stake multiplier (meta-labelling on 65 decision-time features, 3-year Oanda books at $0.65) FAILED the pre-registered rule on M5 (clean null) and M15 (lift inside the label-shuffle null, explained by BUY-vs-SELL). Nothing that changes numbers shipped. Behaviour-neutral tooling remains: `position_sizing.build_features` + `--export-features` (default export stays 63 columns), `SignalReport.penalty_tags/penalty_total/momentum_fading`, `TradeSetup.stake_multiplier` (None; reserved) (the campaign's research scripts were removed 2026-09-06; git history has them). Stage-B facts: M5 stretch measures are POSITIVE in both halves (do not re-propose exhaustion sizing on M5); M15 exhaustion tag is weakly negative (≈ +3R/yr, noise); bursts cluster at 12–15 UTC and 01 UTC bar-close and do NOT hurt entries. - Auto-execution (oanda_daemon.py / oanda_executor.py) was removed 2026-09-03 — the owner is not auto-trading; last commit containing it is 31002cb. Book-parity rules that lived in its docstring (no broker TP, 5-bar cooldown, once-per-bar trail) remain the backtester's model. -- 2026-09-06 stale-code cleanup (behaviour-neutral; suite green, M5/H1 12-month books byte-identical after every signal-side commit): `main.py --timeframe` now accepts M5/M15/H1/H4 and takes its confirmation TFs from the pair factory (the CLI's private table had paired M5 with M1+M15); `PlatformConfig.pip_value` is gone (`PAIR_INFO` is the source); removed as dead or superseded: `_get_affected_pairs`/`event_impact_on_pairs`, `SignalLogger.advance_with_bars`/`path`/`news_for`, `reset_llm_client`, the Telegram scan-summary/keyboard helpers and duplicate confirmation map, the 2026-07 Oanda-vs-TD research pager+study, `burst_profile.py`, `hour_analysis.py`, `scripts/compare_oanda_tiingo.py`, the never-implemented LLM-provider plan and the merged config.json spec. Second pass the same day (owner directive: keep only what the system needs): every research script and data file under `backtest/research/` (book_report, book_diff, the burst tools, sizing_model, commit_ladder, llm_news_eval + golden CSV, ff_calendar_scraper, calendar_impact, watch_rule_eval, the Dukascopy spread study + CSVs, the panel dry-test JSONL), the two completed study scripts in `scripts/`, the architecture diagram + generator and the data-source test plan were deleted; the campaign `.md` reports stay as the decision ledger. Git history before `953c5ad` has all of it. +- 2026-09-06 stale-code cleanup (behaviour-neutral; suite green, M5/H1 12-month books byte-identical after every signal-side commit): `main.py --timeframe` now accepts M5/M15/H1/H4 and takes its confirmation TFs from the pair factory (the CLI's private table had paired M5 with M1+M15); `PlatformConfig.pip_value` is gone (`PAIR_INFO` is the source); removed as dead or superseded: `_get_affected_pairs`/`event_impact_on_pairs`, `SignalLogger.advance_with_bars`/`path`/`news_for`, `reset_llm_client`, the Telegram scan-summary/keyboard helpers and duplicate confirmation map, the 2026-07 Oanda-vs-TD research pager+study, `burst_profile.py`, `hour_analysis.py`, `scripts/compare_oanda_tiingo.py`, the never-implemented LLM-provider plan and the merged config.json spec. Second pass the same day (owner directive: keep only what the system needs): every research script and data file under `backtest/research/` (book_report, book_diff, the burst tools, sizing_model, commit_ladder, llm_news_eval + golden CSV, ff_calendar_scraper, calendar_impact, watch_rule_eval, the Dukascopy spread study + CSVs, the panel dry-test JSONL), the two completed study scripts in `scripts/`, the architecture diagram + generator and the data-source test plan were deleted; the campaign `.md` reports stay as the decision ledger. Git history before `953c5ad` has all of it. Third pass (owner: "remove dead functions as well"): the write-only pb/cp rejection counters, the unreachable `_TP_QUALITY_SCALE` B/C entries, the engine's unused DataFrame argument and M1 arm, the providers' generic-symbol guesses (unknown symbols now raise), the sub-$10 price format, the `=X` pair parsing and the unread `minutes_until`/`is_high_impact` news.json keys are gone. diff --git a/dashboard_publisher.py b/dashboard_publisher.py index 2cb9820..e65a3e8 100644 --- a/dashboard_publisher.py +++ b/dashboard_publisher.py @@ -172,8 +172,8 @@ def report_to_webapp_dict(report) -> dict: def event_to_dict(ev) -> dict: - """EconomicEvent → dict. surprise/minutes_until/is_high_impact are - properties, so asdict() would skip them — added explicitly. + """EconomicEvent → dict. surprise is a property, so asdict() would + skip it — added explicitly. actual/forecast/previous stay strings as published ('228K', '0.3%').""" return { "time": ev.time.astimezone(timezone.utc).isoformat(), @@ -184,8 +184,6 @@ def event_to_dict(ev) -> dict: "previous": ev.previous, "actual": ev.actual, "surprise": ev.surprise, - "minutes_until": _safe_num(ev.minutes_until), - "is_high_impact": ev.is_high_impact, } diff --git a/main.py b/main.py index aa6b253..6b3cef5 100755 --- a/main.py +++ b/main.py @@ -103,8 +103,8 @@ def _compact_reason(reason: str) -> str: def _fmt_price(price: float) -> str: - """2dp for gold-scale prices, 5dp for sub-10 FX-scale prices.""" - return f"{price:.2f}" if price >= 10 else f"{price:.5f}" + """Gold-scale price, 2 dp.""" + return f"{price:.2f}" def print_signal_report(report: SignalReport, detail: bool = False): diff --git a/news_engine.py b/news_engine.py index 888114f..286224b 100755 --- a/news_engine.py +++ b/news_engine.py @@ -457,11 +457,10 @@ class NewsEngine: @staticmethod def _parse_pair(pair_symbol: str) -> Tuple[str, str]: - if "/" in pair_symbol: - parts = pair_symbol.split("/") - return parts[0], parts[1] - clean = pair_symbol.replace("=X", "") - return clean[:3], clean[3:] + base, sep, quote = pair_symbol.partition("/") + if not sep: + raise ValueError(f"pair symbol must be BASE/QUOTE, got {pair_symbol!r}") + return base, quote def get_event_risk_level(self, pair_symbol: str, context: FundamentalContext) -> Tuple[str, str]: """ diff --git a/providers/oanda.py b/providers/oanda.py index 5a9626a..e8b9d1d 100644 --- a/providers/oanda.py +++ b/providers/oanda.py @@ -93,9 +93,11 @@ class OandaDataProvider(BaseDataProvider): return self._fetch_oanda_candles(symbol, timeframe, outputsize) def _to_oanda_instrument(self, symbol: str) -> str: - if symbol in self.OANDA_INSTRUMENT: + try: return self.OANDA_INSTRUMENT[symbol] - return symbol.replace("/", "_") + except KeyError: + raise OandaDataError( + f"Unsupported symbol {symbol!r} for Oanda") from None def _fetch_oanda_candles( self, diff --git a/providers/tiingo.py b/providers/tiingo.py index c053137..862351e 100644 --- a/providers/tiingo.py +++ b/providers/tiingo.py @@ -91,9 +91,11 @@ class TiingoDataProvider(BaseDataProvider): }) def _to_tiingo_ticker(self, symbol: str) -> str: - if symbol in self.TIINGO_TICKER: + try: return self.TIINGO_TICKER[symbol] - return symbol.replace("/", "").replace("=X", "").lower() + except KeyError: + raise TiingoDataError( + f"Unsupported symbol {symbol!r} for Tiingo") from None # ── Raw fetch ────────────────────────────────────────────────── diff --git a/providers/twelve_data.py b/providers/twelve_data.py index 8bf94cc..52e5a4c 100644 --- a/providers/twelve_data.py +++ b/providers/twelve_data.py @@ -58,10 +58,10 @@ class TwelveDataProvider(BaseDataProvider): @staticmethod def _to_td_symbol(symbol: str) -> str: - if "/" in symbol: - return symbol - clean = symbol.replace("=X", "") - return f"{clean[:3]}/{clean[3:]}" + if "/" not in symbol: + raise DataIntegrityError( + f"Unsupported symbol {symbol!r} for Twelve Data") + return symbol # ── Raw fetch ────────────────────────────────────────────────── diff --git a/static/dashboard.html b/static/dashboard.html index 6dc0a29..a5285f1 100644 --- a/static/dashboard.html +++ b/static/dashboard.html @@ -71,7 +71,7 @@
- +
diff --git a/telegram_bot.py b/telegram_bot.py index 6d36589..68f4ea2 100755 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -199,7 +199,7 @@ def _escape_md(text: str) -> str: def _fmt_price(price: float) -> str: - return f"{price:.2f}" if price >= 10 else f"{price:.5f}" + return f"{price:.2f}" # ── Shared data provider (single cache across all scans) ───────────── diff --git a/test_all.py b/test_all.py index 5dcff12..401b93e 100644 --- a/test_all.py +++ b/test_all.py @@ -755,13 +755,6 @@ def _(): assert quote == "USD" -@test("NewsEngine._parse_pair with =X suffix") -def _(): - base, quote = NewsEngine._parse_pair("EURUSD=X") - assert base == "EUR" - assert quote == "USD" - - @test("NewsEngine._parse_pair for XAU pair") def _(): base, quote = NewsEngine._parse_pair("XAU/USD") @@ -3597,7 +3590,11 @@ def _(): provider = providers.oanda.OandaDataProvider.__new__( providers.oanda.OandaDataProvider) assert provider._to_oanda_instrument("XAU/USD") == "XAU_USD" - assert provider._to_oanda_instrument("EUR/USD") == "EUR_USD" + try: + provider._to_oanda_instrument("EUR/USD") + assert False, "unregistered symbol must raise, not be guessed" + except DataIntegrityError: + pass @test("providers.oanda: granularity mapping covers all scan timeframes") @@ -5151,8 +5148,7 @@ def _(): assert d["event"] == "Non-Farm Payrolls" assert d["actual"] == "228K" # strings stay strings assert d["surprise"] == "positive" # property, added explicitly - assert d["is_high_impact"] is True - assert isinstance(d["minutes_until"], float) + assert "minutes_until" not in d # frozen-at-publish value, dropped assert d["time"] == "2026-08-07T12:30:00+00:00" json.dumps(d, allow_nan=False)
Time