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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user