Daily-trend veto on M15 + lot-size / already-open lines (campaign 2026-09-30), and the 2026-10-01 whole-codebase bug review

Two bodies of work that touch the same files, so one commit.

Direction and setup-size campaign (2026-09-30 → 10-01, owner's RISK-FIRST rule,
written before the runs):
- SIGNAL: M15 daily-trend veto (factory override 7, xau_strategy.daily_trend_veto
  "both"). A finished setup against a clear daily trend (closed D1 bars, Sunday
  stubs dropped; close vs its 50-bar EMA AND vs the close 60 kept days earlier)
  is removed as the last step of the core. One definition
  (signal_aggregator.daily_trend_series) for live, the bar replay and the twin's
  host side; D1 is a required frame of the M15 scan; mirrored in
  cuda_signal_kernels.py K2. W3 824 / +205.12R / PF 1.73 / max DD 12.4R vs
  1105 / +195.79R / 1.49 / 20.9R; 2019 → 2026 1473 / +223.33R / 1.42 / 41.5R vs
  2091 / +192.53R / 1.24 / 64.8R; unseen holdout −4.62R vs −6.63R.
- FAILED, not shipped: the veto on M5 (W3 drawdown 10.1R → 15.2R) and on H1
  (−9.7R), buy-only and sell-only on M15, the wide-stop cap
  (xau_strategy.wide_stop_cap_atr stays 0.0, a research knob).
- New M15 pins: W2 dfc647ee…, OOS e87b6de1…, W3 ad3b0dbd…. M5 / H1 / H4 pins
  unchanged. --set xau_strategy.daily_trend_veto=off reproduces the previous M15
  pins byte for byte. Twin and reference script MD5-equal on every window;
  recorder and feature-export parity re-proved; base-rate table rebuilt.
- DISPLAY (books byte-identical): exposure.py — the lot size for
  trade_size.risk_per_trade (new config.json block, off by default, private to
  the owner) and the count of alerted same-direction plans still open
  (signals.alerted_at, read-only open_alerted_setups); the daily trend in words
  on Telegram, the CLI and the dashboard; signals.daily_trend logged.
- providers/oanda: a date window with no bars is an empty page, not an error
  (a D1 pad starting on a Saturday could not be fetched).
- Reports only, nothing shipped: seven context screens (no state nominated) and
  eight outside data sources (none adds a forecast to gold's own daily trend).
- Independent verification (report section 5.13): every figure reproduced on
  both scripts, scoring identical to the previous commit on all four
  timeframes, no look-ahead. Caveats recorded there: this is a risk filter, not
  a more accurate direction call; nine tenths of the 2019 → 2026 gain is 2022;
  the neighbour clause is thin (a simple 100-day average misses the R line by
  1.2R); "never adds a trade" is an observation, not a guarantee.

Bug review of the whole codebase (2026-10-01). Signal path clean. 17 defects
elsewhere, plus 4 found by an independent review of the fixes; all fixed, each
with a test; no trade book changed:
- telegram_bot: one _deliver_alert for the candle scan and the level break. The
  row is stamped alerted_at BEFORE the send, on a one-worker alert-db pool, and
  un-stamped when no chat received it or the task is cancelled mid-stamp, so
  two alerts at one candle close count each other and no SQLite write runs on
  the event loop. The 5-bar cooldown counts bars of the scan's own frame
  (== gpu_backtest._apply_cooldown; clock time released the 22:00 UTC bar after
  the settlement pause) and is restored from signals.db at start-up. The
  lot-size line goes to the owner's own chat only (a group the owner ran
  /autoscan in used to receive it). /status gains a news-model line and escapes
  failure details.
- signal_log: mark_alerted returns the stamp it wrote; unmark_alerted;
  last_alerted_bars (read-only).
- advisory_outcome: candle continuity is judged bar by bar inside the walk; a
  hole after a trade's exit no longer voids it. The stored-candle replay
  resolves 108 of 111 logged setups instead of 99, all equal to the live
  resolver's R.
- providers/oanda: an empty date window is accepted only when it is 5 days or
  shorter and no malformed candle was skipped; a longer one is retried, then
  refused, instead of being cached as history with a hole.
- news_engine: blank RSS titles skipped; one unreadable headline no longer
  costs the local model its whole batch (checked on the real bundle); model
  health recorded; no DeepSeek audit call in a process that does not archive;
  event-direction cache keyed per release; peek_week_events never fetches.
- llm_client: chat_json never raises and never logs the API key (requests
  quotes the Authorization value in InvalidHeader; error bodies are redacted).
- signal_aggregator: the scan's news block is guarded; the replay path
  (use_news=False) assigns exactly the values it did before.
- agent_panel: PANEL_PROMPT_VERSION v2.2 — same prompt text, corrected input
  (event timing over the cached calendar week, headlines newest first).
  scripts/panel_replay.py replays the logged payload per persona;
  scripts/label_scan_outcomes.py counts with an ISO cutoff.
- test_all.py: no ALTER TABLE … DROP COLUMN (the live box has SQLite 3.34);
  tests that write Parquet print a notice and return without pyarrow.

Verification after the last code change: suite 632 passed, 0 failed (also with
both Parquet engines blocked and FB2_BACKEND=cpu, the live box's view); the
five reference MD5s reproduce on both backtesters, 0 cache fallbacks; Module 37
green; static Python 3.9 / SQLite 3.34 check clean on every touched file.

Evidence: backtest/research/daily_trend_campaign_2026-09-30.md,
context_screens_2026-10.md, new_inputs_2026-10.md; CLAUDE.md sections 2, 4, 6,
8, 9, 10 and 11; pair_signals/xauusd.py docstring (M15 override 7).
This commit is contained in:
Harwinder Sekhon
2026-10-01 10:28:28 -04:00
parent 42025da0b0
commit 2cc5915b00
31 changed files with 6021 additions and 286 deletions
+45
View File
@@ -130,6 +130,45 @@ on M15 (trade-identical, -2.0R W2, -2.8R W3: the swing-anchored
GENERIC stop is not better than the 2 x ATR stop on the same grade-A
signals); the combination with this block (-0.7R vs the block alone).
M15 override 7 — daily-trend veto (validated 2026-09-30,
backtest/research/daily_trend_campaign_2026-09-30.md, pre-registered
RISK-FIRST rule approved by the owner that day, CUDA twin runs paired
with gpu_backtest.py MD5s, $0.65): daily_trend_veto="both". The engine
sees at most the H1 chart. The daily trend is +1 when the last CLOSED
daily bar closes above its 50-day EMA AND above the close 60 daily
bars earlier, -1 when below both, else 0 (Oanda's Sunday stub bars are
dropped; signal_aggregator.daily_trend_series). After every setup
archetype has run, a setup AGAINST a non-zero state is dropped (grade
D, no setup, warning "DAILY TREND FILTER: ..."). Candidate vs control:
3-year W3 2023-09-05 -> 2026-09-05 824/+205.12R PF 1.73 DD 12.4R vs
1105/+195.79R PF 1.49 DD 20.9R (halves +76.7/+128.4 vs +76.9/+118.9;
at $0.30 +221.18R PF 1.81 DD 11.4R vs +216.53R PF 1.55 DD 19.6R);
7.6-year LONG 2019-01-01 -> 2026-08-07 1473/+223.33R PF 1.42 DD 41.5R
vs 2091/+192.53R PF 1.24 DD 64.8R (2022 +3.2R vs -24.6R; 2021 NOT
improved, -27.3R vs -25.6R; 2023 worse, +1.8R vs +11.7R); the unseen
HOLDOUT 2026-09-05 -> 2026-10-01 18/-4.62R vs 24/-6.63R; 3 of 4
neighbouring definitions pass (90-day change, 120-day change, 100-day
average; the 60-day change alone misses the 97% line by 1.7R). Trade
diff W3: 281 removed (-9.3R: BUY 106/+7.6R, SELL 175/-16.9R), 0 added;
the removed trades are break-even after costs (-0.03R each) against
+0.25R for the kept ones, so the veto drops the no-edge quarter of the
book rather than a pocket of big losers. ACCEPTED COST (owner decision
2026-09-30): R given up while gold only rises — 2-year W2 597/+155.89R
PF 1.78 DD 11.4R vs 813/+154.15R PF 1.53 DD 15.6R with the first half
+61.8R vs +70.1R, and the old OOS window 2024-06-01 -> 2025-03-30
258/+63.78R PF 1.80 DD 9.1R vs 313/+68.50R PF 1.67 DD 12.4R. The
2026-09-26 rule template (no half and no OOS window may lose) would
have rejected it; the risk-first rule (3-year R >= 97%, PF +0.10, DD
not deeper, long window better on R, DD and 2021+2022, holdout, both
costs, plateau) is the owner's deliberate choice. M15 ONLY: the same
veto FAILED on M5 ("both": W3 DD 10.1 -> 15.2R and the long window
worse on R and DD; "buy": passes W3, loses the long window, no
plateau) and on H1 (W3 +60.3R vs +70.0R). "buy"-only and "sell"-only
FAILED on M15 as well ("sell" has the best W3 total, +212.7R, and a
LONG DD of 76.4R vs 64.8R: in falling years the buys against the daily
trend are the losers). The live wrapper and both backtesters load D1
as an extra REQUIRED frame while the veto is on.
M5 override (validated 2026-07-11, full-history replay 2020-03-17 ->
2026-07-11 + 12mo book, $0.30 spread): block hour 9 UTC (post-London-
open whipsaw). Hour-9 entries lost -28.9R/143 over 6 years, negative
@@ -332,6 +371,12 @@ def make_config(
# actionable-grade short whose grade is not listed) — see module
# docstring (M15 override 6, validated 2026-09-26).
config.xau_strategy.sell_min_quality = ["A+", "B"]
# Daily-trend veto on M15 only: a finished setup against a clear
# daily trend (price vs its 50-day average AND the 60-day change)
# is dropped. The live wrapper and both backtesters load the D1
# frame for it — see module docstring (M15 override 7, validated
# 2026-09-30). M5, H1 and H4 FAILED the same rule and stay "off".
config.xau_strategy.daily_trend_veto = "both"
if primary_tf == Timeframe.H1:
# Block every H1 SELL — the SELL quality gate (6e in
# signal_aggregator.py) downgrades any actionable-grade short