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). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,9 @@ if str(PROJECT_ROOT) not in sys.path:
|
||||
from config import PAIR_INFO, PlatformConfig, Timeframe # noqa: E402
|
||||
from providers import make_data_provider # noqa: E402
|
||||
from providers.base import BaseDataProvider # noqa: E402
|
||||
from signal_aggregator import SignalAggregator # noqa: E402 (fingerprints + the two static cleanups only)
|
||||
from signal_aggregator import ( # noqa: E402 (fingerprints, the two static cleanups, the daily-trend definition)
|
||||
SignalAggregator, daily_trend_needed, daily_trend_series,
|
||||
)
|
||||
import position_sizing # noqa: E402
|
||||
from backtest import gpu_kernels as _gk # noqa: E402
|
||||
from backtest import cuda_signal_kernels as ck # noqa: E402
|
||||
@@ -318,6 +320,12 @@ def format_warning(code: int, arg: float, ctx: Dict) -> str:
|
||||
if n == "rf_blocked":
|
||||
return (f"REGIME FILTER: {ctx['rf_tf']} ADX={arg:.1f} < "
|
||||
f"{ctx['regime_filter_min_adx']:.0f} — market choppy, skipping signal")
|
||||
if n == "trend_veto":
|
||||
is_buy = arg > 0
|
||||
return (f"DAILY TREND FILTER: {'BUY' if is_buy else 'SELL'} against a "
|
||||
f"{'falling' if is_buy else 'rising'} daily trend — no trade")
|
||||
if n == "trend_unavailable":
|
||||
return "Daily trend filter requested but 1d data unavailable"
|
||||
raise ValueError(f"unknown warning code {code}")
|
||||
|
||||
|
||||
@@ -379,6 +387,34 @@ def host_flags(primary_index: pd.DatetimeIndex, platform_cfg: PlatformConfig,
|
||||
}
|
||||
|
||||
|
||||
def trend_flags(primary_index: pd.DatetimeIndex, d1: Optional[pd.DataFrame],
|
||||
bt_cfg: BacktestConfig) -> np.ndarray:
|
||||
"""Per primary bar: the daily trend the live code reads on the D1 slice
|
||||
_replay_span hands it (+1 / 0 / -1), or ck.DAILY_TREND_UNAVAILABLE when
|
||||
that slice holds no usable bar.
|
||||
|
||||
The definition is signal_aggregator.daily_trend_series, evaluated once on
|
||||
the whole frame (every step is causal, so the value at a bar equals the
|
||||
value on any prefix ending there). The as-of rule is the replay's: a D1
|
||||
bar is visible once label + 1 day <= the primary bar's close, compared in
|
||||
int64 nanoseconds; the Sunday stubs daily_trend_series drops are skipped
|
||||
by searching its own (kept-bar) index."""
|
||||
n = len(primary_index)
|
||||
out = np.full(n, ck.DAILY_TREND_UNAVAILABLE, dtype=np.int8)
|
||||
if d1 is None or len(d1) == 0:
|
||||
return out
|
||||
series = daily_trend_series(d1)
|
||||
if len(series) == 0:
|
||||
return out
|
||||
primary_ns = primary_index.as_unit("ns").asi8
|
||||
cutoff = primary_ns + (BAR_SECONDS[bt_cfg.primary_tf] - BAR_SECONDS[Timeframe.D1]) * 1_000_000_000
|
||||
labels_ns = series.index.as_unit("ns").asi8
|
||||
k = np.searchsorted(labels_ns, cutoff, side="right")
|
||||
seen = k > 0
|
||||
out[seen] = series.to_numpy()[k[seen] - 1]
|
||||
return out
|
||||
|
||||
|
||||
def replay_bounds(primary_df: pd.DataFrame, bt_cfg: BacktestConfig) -> Tuple[int, int]:
|
||||
bt_start_ts = pd.Timestamp(bt_cfg.start)
|
||||
bt_end_ts = pd.Timestamp(bt_cfg.end)
|
||||
@@ -421,6 +457,8 @@ def refuse_unsupported(platform_cfg: PlatformConfig, bt_cfg: BacktestConfig) ->
|
||||
problems.append(f"technical.ema_mode={t.ema_mode!r}")
|
||||
if platform_cfg.signals.grading_mode not in ck.GRADING_MODE_CODES:
|
||||
problems.append(f"signals.grading_mode={platform_cfg.signals.grading_mode!r}")
|
||||
if x.daily_trend_veto not in ck.DAILY_TREND_CODES:
|
||||
problems.append(f"xau_strategy.daily_trend_veto={x.daily_trend_veto!r}")
|
||||
if problems:
|
||||
raise SystemExit("cudaBasedBacktesting.py cannot run this configuration: "
|
||||
+ "; ".join(problems) + ". Use backtest/gpu_backtest.py.")
|
||||
@@ -456,6 +494,11 @@ def analyze_frames(bt_cfg: BacktestConfig, platform_cfg: PlatformConfig,
|
||||
rf_cut = cuts[rtf] if rtf != bt_cfg.primary_tf else cp.arange(1, primary_dev["n"] + 1, dtype=cp.int64)
|
||||
rf = (dev[rtf]["adx"], rf_cut)
|
||||
flags = host_flags(primary_df.index, platform_cfg, bt_cfg)
|
||||
# The daily trend per primary bar (read by K2 only when the veto is on).
|
||||
flags["daily_trend"] = (
|
||||
trend_flags(primary_df.index, dfs_full.get(Timeframe.D1), bt_cfg)
|
||||
if daily_trend_needed(platform_cfg)
|
||||
else np.zeros(len(primary_df), dtype=np.int8))
|
||||
first_eligible_pos, last_pos = replay_bounds(primary_df, bt_cfg)
|
||||
logger.info(f"Replaying {bt_cfg.pair} {bt_cfg.primary_tf.value}: "
|
||||
f"bars {first_eligible_pos}–{last_pos} of {len(primary_df)}")
|
||||
@@ -508,6 +551,9 @@ def run_backtest_cuda(bt_cfg: BacktestConfig, platform_cfg: PlatformConfig) -> T
|
||||
flt = platform_cfg.backtest
|
||||
if flt.regime_filter and flt.regime_filter_timeframe not in timeframes:
|
||||
timeframes.append(flt.regime_filter_timeframe)
|
||||
# the D1 frame of the daily-trend veto, appended exactly like the reference script
|
||||
if daily_trend_needed(platform_cfg) and Timeframe.D1 not in timeframes:
|
||||
timeframes.append(Timeframe.D1)
|
||||
dfs_full: Dict[Timeframe, pd.DataFrame] = {}
|
||||
cache_names: Dict[str, str] = {}
|
||||
tf_starts = {}
|
||||
@@ -615,27 +661,30 @@ def _warning_ctx(ctx, pos: int, r2i: Dict, r2f: Dict) -> Dict:
|
||||
|
||||
|
||||
_RF_CODES = {ck.WARN_CODES["rf_unavailable"], ck.WARN_CODES["rf_no_adx"], ck.WARN_CODES["rf_blocked"]}
|
||||
_TREND_CODES = {ck.WARN_CODES["trend_veto"], ck.WARN_CODES["trend_unavailable"]}
|
||||
|
||||
|
||||
def build_warnings(ctx, pos: int, r2i: Dict, r2f: Dict, codes, args) -> Tuple[List[str], List[str]]:
|
||||
"""(warnings, cleaned_warnings) of one analysed bar, after the XAU_CP
|
||||
cleanup (when a pattern setup was accepted) and the stale spread-gate
|
||||
step (when a setup existed at step 11), with the regime-filter lines
|
||||
appended last like the replay does."""
|
||||
step (when a setup existed at step 11), with the daily-trend line of
|
||||
step 12 and then the regime-filter lines appended last, in the order
|
||||
the live code and the replay write them."""
|
||||
wctx = _warning_ctx(ctx, pos, r2i, r2f)
|
||||
n = r2i["warn_n"]
|
||||
main: List[str] = []
|
||||
trend: List[str] = []
|
||||
rf: List[str] = []
|
||||
for q in range(n):
|
||||
code = int(codes[q])
|
||||
text = format_warning(code, float(args[q]), wctx)
|
||||
(rf if code in _RF_CODES else main).append(text)
|
||||
(rf if code in _RF_CODES else trend if code in _TREND_CODES else main).append(text)
|
||||
ns = types.SimpleNamespace(warnings=main, cleaned_warnings=[],
|
||||
trade_setup=object() if r2i["strategy_pre_rf"] != 0 else None)
|
||||
if r2i["cp_accepted"]:
|
||||
SignalAggregator._clean_accepted_cp_warnings(ns)
|
||||
SignalAggregator._drop_stale_spread_gate_vetoes(ns)
|
||||
return list(ns.warnings) + rf, list(ns.cleaned_warnings)
|
||||
return list(ns.warnings) + trend + rf, list(ns.cleaned_warnings)
|
||||
|
||||
|
||||
def build_reasons(ctx, r2i: Dict, r2f: Dict, inds: Dict[str, Dict]) -> Tuple[List[str], List[str]]:
|
||||
|
||||
@@ -12,9 +12,13 @@ the repo and stays the reference:
|
||||
thread analyses the prefix [0..j].
|
||||
K2 fb2_aggregate signal_aggregator.analyze_from_dataframes end to end
|
||||
(MTF merge, penalties, grade, gates 6b-6i, the three
|
||||
setup generators, XAU_PB, XAU_CP, the stale-veto step)
|
||||
setup generators with the wide-stop cap, XAU_PB,
|
||||
XAU_CP, the stale-veto step, the daily-trend veto)
|
||||
plus the per-bar rules of gpu_backtest._replay_span.
|
||||
One thread per primary bar.
|
||||
One thread per primary bar. The daily trend itself
|
||||
arrives as a host flag (cudaBasedBacktesting.
|
||||
trend_flags): its pandas definition lives in
|
||||
signal_aggregator.daily_trend_series only.
|
||||
K3 fb2_features position_sizing.build_features (65 features), one
|
||||
thread per accepted trade (--export-features).
|
||||
K4 fb2_cooldown gpu_backtest._apply_cooldown (single thread).
|
||||
@@ -155,6 +159,8 @@ WARN_NAMES = (
|
||||
"rf_unavailable", # 33 regime filter: TF missing
|
||||
"rf_no_adx", # 34 regime filter: ADX None
|
||||
"rf_blocked", # 35 arg adx
|
||||
"trend_veto", # 36 daily-trend veto: arg setup direction (+1 / -1)
|
||||
"trend_unavailable", # 37 daily-trend veto on, no usable D1 bar
|
||||
)
|
||||
WARN_CODES = {n: i for i, n in enumerate(WARN_NAMES)}
|
||||
|
||||
@@ -206,6 +212,13 @@ SKIP_NAMES = {1: "WEEKEND_FILTER", 2: "DAY_FILTER", 3: "SESSION_WINDOW"}
|
||||
# 1 = min conviction, 2 = product conviction). Pinned by name, never by the
|
||||
# position in signal_aggregator.GRADING_MODES; pack_params checks the two agree.
|
||||
GRADING_MODE_CODES = {"pair": 0, "min": 1, "product": 2}
|
||||
# xau_strategy.daily_trend_veto (signal_aggregator.DAILY_TREND_MODES), pinned
|
||||
# by name like the grading modes; pack_params checks the two agree. The
|
||||
# per-bar trend itself is computed on the host (cudaBasedBacktesting.
|
||||
# trend_flags, from signal_aggregator.daily_trend_series): +1 / 0 / -1, and
|
||||
# DAILY_TREND_UNAVAILABLE where the D1 slice holds no usable bar.
|
||||
DAILY_TREND_CODES = {"off": 0, "both": 1, "buy": 2, "sell": 3}
|
||||
DAILY_TREND_UNAVAILABLE = 2
|
||||
|
||||
# ── Config packing ────────────────────────────────────────────────────
|
||||
PARAMS_F = (
|
||||
@@ -228,6 +241,7 @@ PARAMS_F = (
|
||||
# all 0.0 under "pair"): aplus, a, b, c, strong, buy, boost_a, boost_b
|
||||
"conv_aplus", "conv_a", "conv_b", "conv_c",
|
||||
"conv_strong", "conv_buy", "conv_boost_a", "conv_boost_b",
|
||||
"wide_stop_cap_atr", # 0.0 = off (GENERIC and XAU_CP stop cap, after the gates)
|
||||
)
|
||||
PARAMS_I = (
|
||||
"ema_trend_period", "ema_mode_2ema", "ema_partial_neutral", "rsi_regime_aware",
|
||||
@@ -245,6 +259,7 @@ PARAMS_I = (
|
||||
"quality_filter_mask", "strategies_mask", "primary_is_m5", "primary_bar_secs",
|
||||
"regime_filter", "n_confirm",
|
||||
"grading_mode", # 0 pair (shipped) / 1 min / 2 product
|
||||
"daily_trend_veto", # 0 off / 1 both / 2 buy / 3 sell (DAILY_TREND_CODES)
|
||||
)
|
||||
|
||||
# Fields of the four config dataclasses the kernels do NOT read, with the
|
||||
@@ -377,6 +392,12 @@ def pack_params(platform_cfg, bt_cfg) -> Tuple[np.ndarray, np.ndarray, np.ndarra
|
||||
for name, val in zip(("conv_aplus", "conv_a", "conv_b", "conv_c",
|
||||
"conv_strong", "conv_buy", "conv_boost_a", "conv_boost_b"), conv_t):
|
||||
f[name] = val
|
||||
f["wide_stop_cap_atr"] = x.wide_stop_cap_atr
|
||||
# daily-trend veto: the live module owns the mode list (same local import)
|
||||
from signal_aggregator import DAILY_TREND_MODES, daily_trend_mode_of
|
||||
assert set(DAILY_TREND_MODES) == set(DAILY_TREND_CODES), (
|
||||
f"signal_aggregator.DAILY_TREND_MODES {DAILY_TREND_MODES} != the kernel's {sorted(DAILY_TREND_CODES)}")
|
||||
daily_trend_veto = DAILY_TREND_CODES[daily_trend_mode_of(x)]
|
||||
from providers.base import BaseDataProvider # CANDLE_SECONDS (the aggregator's clock)
|
||||
bar_secs = BaseDataProvider.CANDLE_SECONDS[platform_cfg.primary_timeframe]
|
||||
strategies = list(bt_cfg.strategies_only or [])
|
||||
@@ -431,6 +452,7 @@ def pack_params(platform_cfg, bt_cfg) -> Tuple[np.ndarray, np.ndarray, np.ndarra
|
||||
"regime_filter": 1 if b.regime_filter else 0,
|
||||
"n_confirm": len(platform_cfg.confirmation_timeframes),
|
||||
"grading_mode": GRADING_MODE_CODES[grading_mode],
|
||||
"daily_trend_veto": daily_trend_veto,
|
||||
}
|
||||
pf = np.array([float(f[name]) for name in PARAMS_F], dtype=np.float64)
|
||||
pi = np.array([int(i[name]) for name in PARAMS_I], dtype=np.int32)
|
||||
@@ -480,6 +502,9 @@ def _defines() -> str:
|
||||
lines.append(f"#define Q_{name.replace('+', 'PLUS')} {code}")
|
||||
for name, code in STRATEGY_CODES.items():
|
||||
lines.append(f"#define STRAT_{name or 'NONE'} {code}")
|
||||
for name, code in DAILY_TREND_CODES.items():
|
||||
lines.append(f"#define TREND_{name} {code}")
|
||||
lines.append(f"#define FB2_TREND_UNAVAILABLE {DAILY_TREND_UNAVAILABLE}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
@@ -1512,6 +1537,7 @@ void fb2_aggregate(const double* __restrict__ open_p, const double* __restrict__
|
||||
const double* __restrict__ rf_adx, const long long* __restrict__ rf_cut,
|
||||
const signed char* __restrict__ skip_reason, const signed char* __restrict__ label_hour,
|
||||
const signed char* __restrict__ label_wd, const signed char* __restrict__ close_hour,
|
||||
const signed char* __restrict__ daily_trend_p,
|
||||
const double* __restrict__ PF, const int* __restrict__ PI,
|
||||
const int first_pos, const int n_bars,
|
||||
double* __restrict__ OF2, int* __restrict__ OI2,
|
||||
@@ -1933,6 +1959,16 @@ void fb2_aggregate(const double* __restrict__ open_p, const double* __restrict__
|
||||
if (!(rr < min_rr - 1e-9)) {
|
||||
if (tp1_distance < min_tp) { WARN(W_spread_gate_generic, tp1_distance); }
|
||||
else {
|
||||
// wide-stop cap AFTER the gates (signal_aggregator._wide_stop_cap, 0 = off)
|
||||
if (PF[PF_wide_stop_cap_atr] > 0.0 && xau_enabled && is_xau) {
|
||||
const double stop_floor = PYMAX(atr * sl_atr_floor, min_sl_price);
|
||||
const double cap_dist = PYMAX(PF[PF_wide_stop_cap_atr] * atr, stop_floor);
|
||||
if (sl_distance > cap_dist) {
|
||||
sl_distance = cap_dist;
|
||||
sl_price = is_buy ? (entry - cap_dist) : (entry + cap_dist);
|
||||
rr = tp1_distance / sl_distance;
|
||||
}
|
||||
}
|
||||
{ // printed-target floor AFTER the gates (signal_aggregator 2026-09-26)
|
||||
const double floor_dist = sl_distance * PF[PF_tp_print_floor_rr];
|
||||
if (tp1_distance < floor_dist) {
|
||||
@@ -2079,6 +2115,15 @@ void fb2_aggregate(const double* __restrict__ open_p, const double* __restrict__
|
||||
}
|
||||
double rr = (sl_dist > 0.0) ? height / sl_dist : 0.0;
|
||||
if (!(rr < min_rr - 1e-9) && !(height < min_tp)) {
|
||||
// wide-stop cap AFTER the gates (signal_aggregator._wide_stop_cap, 0 = off)
|
||||
if (PF[PF_wide_stop_cap_atr] > 0.0 && xau_enabled && is_xau) {
|
||||
const double cap_dist = PYMAX(PF[PF_wide_stop_cap_atr] * atr, min_sl_price);
|
||||
if (sl_dist > cap_dist) {
|
||||
sl_dist = cap_dist;
|
||||
sl_ = is_buy ? (entry - cap_dist) : (entry + cap_dist);
|
||||
rr = height / sl_dist;
|
||||
}
|
||||
}
|
||||
{ // printed-target floor AFTER the gates (signal_aggregator 2026-09-26)
|
||||
double tp1_dist = height;
|
||||
const double floor_dist = sl_dist * PF[PF_tp_print_floor_rr];
|
||||
@@ -2103,7 +2148,22 @@ void fb2_aggregate(const double* __restrict__ open_p, const double* __restrict__
|
||||
}
|
||||
|
||||
// ── replay: the optional higher-TF regime filter (after analyze_from_dataframes) ──
|
||||
// strategy_pre_rf = the setup at step 11 (the stale spread-gate cleanup on
|
||||
// the host), i.e. BEFORE the daily-trend veto of step 12 and the filter.
|
||||
K2I(strategy_pre_rf) = strategy;
|
||||
|
||||
// ── 12. daily-trend veto (signal_aggregator._apply_daily_trend_veto) ──
|
||||
if (PI[PI_daily_trend_veto] != 0 && xau_enabled && is_xau) {
|
||||
const int td = daily_trend_p[pos];
|
||||
if (td == FB2_TREND_UNAVAILABLE) { WARN(W_trend_unavailable, 0.0); }
|
||||
else if (strategy != STRAT_NONE && td != 0) {
|
||||
const int mode = PI[PI_daily_trend_veto];
|
||||
const bool against = (direction > 0 && td < 0 && (mode == TREND_both || mode == TREND_buy))
|
||||
|| (direction < 0 && td > 0 && (mode == TREND_both || mode == TREND_sell));
|
||||
if (against) { WARN(W_trend_veto, (double)direction); quality = Q_D; strategy = STRAT_NONE; }
|
||||
}
|
||||
}
|
||||
|
||||
if (PI[PI_regime_filter]) {
|
||||
const long long cut = rf_cut[pos];
|
||||
if (cut == 0) { K2I(rf_state) = 1; WARN(W_rf_unavailable, 0.0); }
|
||||
@@ -2780,6 +2840,7 @@ def aggregate(primary_dev: Dict, primary_an: TFAnalysis,
|
||||
rf_adx, rf_cut,
|
||||
_dev(flags["skip_reason"], np.int8), _dev(flags["label_hour"], np.int8),
|
||||
_dev(flags["label_wd"], np.int8), _dev(flags["close_hour"], np.int8),
|
||||
_dev(flags["daily_trend"], np.int8),
|
||||
_dev(pf, np.float64), _dev(pi, np.int32),
|
||||
np.int32(first_pos), np.int32(n_bars), OF2, OI2, warn_code, warn_arg))
|
||||
cp.cuda.Device().synchronize()
|
||||
|
||||
@@ -63,7 +63,9 @@ from config import ( # noqa: E402
|
||||
)
|
||||
from providers import BaseDataProvider, make_data_provider # noqa: E402
|
||||
from engine import IndicatorSignal, TechnicalAnalysis, TechnicalEngine # noqa: E402
|
||||
from signal_aggregator import SignalAggregator, SignalReport, TradeSetup # noqa: E402
|
||||
from signal_aggregator import ( # noqa: E402
|
||||
SignalAggregator, SignalReport, TradeSetup, daily_trend_needed,
|
||||
)
|
||||
import position_sizing # noqa: E402
|
||||
from backtest import replay_record # noqa: E402
|
||||
from backtest import gpu_kernels as _gk # noqa: E402
|
||||
@@ -2084,6 +2086,11 @@ def run_backtest(
|
||||
flt = platform_cfg.backtest
|
||||
if flt.regime_filter and flt.regime_filter_timeframe not in timeframes:
|
||||
timeframes.append(flt.regime_filter_timeframe)
|
||||
# Same for the daily-trend veto: analyze_pair fetches D1 as a required
|
||||
# frame when it is on, and the core reads it from the slices (the
|
||||
# as-of slicing in _replay_span hands it closed daily bars only).
|
||||
if daily_trend_needed(platform_cfg) and Timeframe.D1 not in timeframes:
|
||||
timeframes.append(Timeframe.D1)
|
||||
dfs_full: Dict[Timeframe, pd.DataFrame] = {}
|
||||
# Which parquet each TF came from (recorded in --record's manifest so
|
||||
# a study can prove two runs read the same bars).
|
||||
|
||||
@@ -65,3 +65,26 @@ re-implementation.
|
||||
and it refuses configurations beyond its compile-time limits (more than 4 confirmation
|
||||
timeframes, divergence lookback >= 64, swing lookback >= 256, cp_lookback >= 128) with a
|
||||
message that points back to gpu_backtest.py.
|
||||
|
||||
--- 2026-09-30: daily-trend veto (M15) and the D1 frame ---
|
||||
- The M15 factory config carries xau_strategy.daily_trend_veto = "both" (M15 override 7). Both
|
||||
backtesters then load the D1 frame as an EXTRA frame next to the primary and confirmation
|
||||
frames (the "Confirm TFs:" line does not list it), cached under its own pad:
|
||||
backtest/cache/oanda_XAU_USD_1d_<start minus 1400 days>_<end>.parquet. A new M15 window therefore
|
||||
needs that file (one network fetch the first time; "GPU cache fallbacks: 0" is unaffected).
|
||||
- The veto reads CLOSED daily bars only (a bar labelled D counts from D + 1 day <= the primary
|
||||
bar's close), drops the provider's Sunday stub bars, and removes a finished setup that goes
|
||||
against a non-zero state. Definition and constants: signal_aggregator.daily_trend_series.
|
||||
- Research switches, both default-neutral elsewhere: --set xau_strategy.daily_trend_veto=
|
||||
off|both|buy|sell and --set xau_strategy.wide_stop_cap_atr=K (K = 0 is off; the cap FAILED its
|
||||
rule on 2026-09-30 and ships nowhere). A control for an M15 candidate is the factory config,
|
||||
i.e. WITH the veto; to reproduce a pre-2026-09-30 M15 book pass --set
|
||||
xau_strategy.daily_trend_veto=off.
|
||||
- A candidate that is a FINAL veto (it only removes finished setups) may be screened on a research
|
||||
harness (K1 + K2 of the twin, a mask on the final candidates before the cooldown, the shared
|
||||
resolver) once that harness is shown to reproduce a shipped book byte for byte; the formal
|
||||
numbers still come from the real code path with the paired gpu_backtest.py MD5.
|
||||
- The 2026-09-30 report (backtest/research/daily_trend_campaign_2026-09-30.md) is the template for
|
||||
a risk-first rule: rules, windows and neighbours written before the runs; the long window
|
||||
(2019-01-01 -> 2026-08-07) and an unseen holdout as clauses; the rising-market cost reported,
|
||||
not hidden.
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
# Context screens after the daily-trend veto — declared before looking, 2026-10-01
|
||||
|
||||
Follow-up to `daily_trend_campaign_2026-09-30.md` (owner decision 2026-09-30: "more context
|
||||
screens"). The daily chart was information the engine had never seen, and it paid on M15. This
|
||||
file asks the same question of seven more pieces of context. Sections 1 to 3 were written before
|
||||
any number in section 4 existed.
|
||||
|
||||
## 1. Books and slices
|
||||
|
||||
The books as shipped on 2026-09-30, `--spread 0.65`, CUDA twin (byte-identical to
|
||||
`gpu_backtest.py` on the paired runs of the campaign report):
|
||||
|
||||
| Book | Window | Trades / R | MD5 |
|
||||
|---|---|---|---|
|
||||
| M15 LONG (veto on) | 2019-01-01 → 2026-08-07 | 1473 / +223.33 | `aa5253aa…` |
|
||||
| M15 W3 (veto on) | 2023-09-05 → 2026-09-05 | 824 / +205.12 | `ad3b0dbd…` |
|
||||
| M5 LONG | 2019-01-01 → 2026-08-07 | 1052 / +128.10 | `3f0c091c…` |
|
||||
| M5 W3 | 2023-09-05 → 2026-09-05 | 870 / +153.64 | `d20ec8a3…` |
|
||||
|
||||
Four slices per timeframe: LONG era A (entries 2019–2023), LONG era B (2024 → 2026-08-07), W3
|
||||
first half (before 2025-03-05), W3 second half. W3 overlaps era B, so the four slices are not
|
||||
independent; that is known and accepted for a screen.
|
||||
|
||||
## 2. The seven features (closed bars only, as of the trade's decision time)
|
||||
|
||||
Decision time = the trade's entry time (the entry is the open of the bar after the signal bar, so
|
||||
it equals the signal bar's close). A bar counts only when it had closed by then.
|
||||
|
||||
Daily bars: provider D1 candles (UTC days), Sunday stub bars dropped, usable from label + 1 day.
|
||||
|
||||
1. **Weekly trend.** Weekly bars = the kept daily bars grouped into weeks ending Friday; a week is
|
||||
usable once its Friday bar is. State +1 when the weekly close is above its 20-week EMA
|
||||
(`ewm(span=20, adjust=False)`) AND above the close 12 weeks earlier; −1 when below both; else 0.
|
||||
Trade state: `with` / `against` / `unclear`.
|
||||
2. **Four-hour trend.** Provider H4 candles (UTC-aligned), usable from label + 4 h. State +1 when
|
||||
the close is above its 50-bar EMA AND above the close 30 bars earlier; −1 when below both; else
|
||||
0. Trade state: `with` / `against` / `unclear`.
|
||||
3. **Daily-trend strength.** `(Close − Close 60 kept days earlier) / ATR14`, ATR14 = the simple
|
||||
14-day mean of the true range, taken in the trade's direction (multiplied by −1 for a SELL).
|
||||
Trade state by fixed cut-offs: `counter` (< 0), `weak` (0 to 5), `medium` (5 to 12),
|
||||
`strong` (> 12 daily ATRs).
|
||||
4. **Daily volatility state.** ATR14 / Close, ranked within its own trailing 250 kept days
|
||||
(percentile, the current day included). Trade state: `quiet` (< 33%), `normal`, `wild` (> 67%).
|
||||
5. **Place in the 20-day range.** `(Close − lowest Low of 20 kept days) / (highest High − lowest
|
||||
Low)`, mirrored for a SELL (1 − value). Trade state: `near the far edge` (< 1/3: buying near
|
||||
the 20-day low or selling near the high), `middle`, `near the chasing edge` (> 2/3: buying near
|
||||
the 20-day high or selling near the low).
|
||||
6. **Tick-volume surge.** Volume of the signal bar divided by the median volume of the same clock
|
||||
slot (same hour and minute, UTC) over that slot's previous 20 bars. Trade state: `thin`
|
||||
(< 0.8), `usual` (0.8 to 1.5), `surge` (> 1.5).
|
||||
7. **Time to a high-impact US release** (`backtest/cache/calendar.parquet`, currency USD, impact
|
||||
high, time not masked; the file starts 2023-09-01, so W3 only). Minutes from the decision time
|
||||
to the nearest such release. Trade state: `0–30 min before`, `0–30 min after`,
|
||||
`30–120 min before`, `30–120 min after`, `clear` (more than 2 hours either side).
|
||||
|
||||
Rows whose feature cannot be computed (not enough history) are counted as `n/a` and never flagged.
|
||||
|
||||
## 3. Rule
|
||||
|
||||
A screen is an in-book statistic: it cannot see the trades a change would add and it ignores the
|
||||
5-bar cooldown. It can only nominate a hypothesis.
|
||||
|
||||
- A state is **nominated** only if, on one timeframe, it holds at least 8% of the trades of BOTH
|
||||
books (LONG and W3) and its total R is negative in ALL FOUR slices.
|
||||
- For each nominated state the mechanism is written down before its replay.
|
||||
- At most **three** replays in total, on the final-veto research harness of the campaign (K1 + K2
|
||||
of the twin, a mask on the final candidates before the cooldown, the shared resolver), judged by
|
||||
the same risk-first rule T against the shipped book: W3 R ≥ 97% of control, W3 PF ≥ control +
|
||||
0.10, W3 max drawdown ≤ control, LONG R ≥ control, LONG max drawdown ≤ control, 2021 + 2022
|
||||
better, HOLDOUT R ≥ control − 0.5R, the same at $0.30, at least 20 trades differ. (No
|
||||
neighbour-definition clause here: a pass only earns a full Stage-1/Stage-2 build, where the
|
||||
neighbours are tested.)
|
||||
- No nomination, no replay. No second cut-off, no other bins, no "best of seven" story afterwards.
|
||||
With 7 features, about 25 states and 2 timeframes, a state that is negative in four overlapping
|
||||
slices by luck alone is not rare; that is why a nomination is only a ticket to a replay.
|
||||
|
||||
## 4. Results
|
||||
|
||||
Run 2026-10-01 on the four books of section 1 (read from the campaign's exported CSVs; the feature
|
||||
frames are the kept Oanda cache windows: daily `1d_20150303_20260807` / `1d_20191105_20260905`,
|
||||
four-hour `4h_20181031_20260807` / `4h_20230114_20260905`, the books' own primary frames for
|
||||
volume, `calendar.parquet` for releases). Every trade's decision time equals its entry time in
|
||||
all four books (no entry sat behind a market closure). Cells are total R (trades); "mean" is R per
|
||||
trade.
|
||||
|
||||
### 4.1 M15 (daily-trend veto on): LONG 1473 trades / +223.33R, W3 824 trades / +205.12R
|
||||
|
||||
**M15 — 1. weekly trend**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| against | 78 (5%) | -15.0 / -0.193 | 25 (3%) | +3.9 / +0.157 | -18.3 (58) | +3.2 (20) | +3.8 (7) | +0.1 (18) | |
|
||||
| unclear | 176 (12%) | +28.1 / +0.160 | 101 (12%) | +37.8 / +0.375 | +1.9 (102) | +26.2 (74) | +25.4 (61) | +12.5 (40) | |
|
||||
| with | 1219 (83%) | +210.3 / +0.173 | 698 (85%) | +163.4 / +0.234 | +53.1 (568) | +157.2 (651) | +47.6 (316) | +115.8 (382) | |
|
||||
|
||||
**M15 — 2. four-hour trend**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| against | 260 (18%) | +23.3 / +0.090 | 134 (16%) | +14.8 / +0.111 | +12.4 (137) | +10.9 (123) | +3.4 (59) | +11.4 (75) | |
|
||||
| unclear | 167 (11%) | +16.8 / +0.100 | 84 (10%) | +16.7 / +0.199 | +0.3 (90) | +16.5 (77) | +1.8 (41) | +14.9 (43) | |
|
||||
| with | 1046 (71%) | +183.3 / +0.175 | 606 (74%) | +173.6 / +0.286 | +24.1 (501) | +159.2 (545) | +71.5 (284) | +102.1 (322) | |
|
||||
|
||||
**M15 — 3. daily-trend strength**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| counter | 145 (10%) | -4.8 / -0.033 | 72 (9%) | +16.9 / +0.234 | -13.1 (92) | +8.3 (53) | +15.2 (39) | +1.7 (33) | |
|
||||
| medium | 586 (40%) | +106.6 / +0.182 | 393 (48%) | +94.6 / +0.241 | +16.7 (199) | +89.9 (387) | +9.5 (162) | +85.1 (231) | |
|
||||
| strong | 17 (1%) | +4.6 / +0.268 | 10 (1%) | +4.8 / +0.483 | -0.3 (7) | +4.8 (10) | -1.1 (1) | +5.9 (9) | |
|
||||
| weak | 725 (49%) | +117.0 / +0.161 | 349 (42%) | +88.8 / +0.254 | +33.5 (430) | +83.5 (295) | +53.1 (182) | +35.7 (167) | |
|
||||
|
||||
**M15 — 4. daily volatility state**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| normal | 445 (30%) | +54.8 / +0.123 | 299 (36%) | +77.6 / +0.259 | -11.8 (194) | +66.6 (251) | +6.7 (118) | +70.8 (181) | |
|
||||
| quiet | 373 (25%) | +64.1 / +0.172 | 152 (18%) | +51.7 / +0.340 | +24.2 (235) | +39.9 (138) | +38.9 (109) | +12.8 (43) | |
|
||||
| wild | 655 (44%) | +104.4 / +0.159 | 373 (45%) | +75.9 / +0.203 | +24.3 (299) | +80.1 (356) | +31.1 (157) | +44.8 (216) | |
|
||||
|
||||
**M15 — 5. place in the 20-day range**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| middle | 420 (29%) | +54.9 / +0.131 | 230 (28%) | +32.3 / +0.140 | +25.3 (217) | +29.7 (203) | +30.9 (107) | +1.4 (123) | |
|
||||
| near the chasing edge | 835 (57%) | +143.5 / +0.172 | 483 (59%) | +147.1 / +0.305 | +8.8 (400) | +134.7 (435) | +40.6 (227) | +106.5 (256) | |
|
||||
| near the far edge | 218 (15%) | +24.8 / +0.114 | 111 (13%) | +25.7 / +0.231 | +2.6 (111) | +22.2 (107) | +5.3 (50) | +20.4 (61) | |
|
||||
|
||||
**M15 — 6. tick-volume surge**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| surge | 556 (38%) | +83.1 / +0.149 | 226 (27%) | +45.7 / +0.202 | +45.8 (355) | +37.3 (201) | +22.5 (110) | +23.1 (116) | |
|
||||
| thin | 220 (15%) | +15.6 / +0.071 | 125 (15%) | +28.5 / +0.228 | -10.4 (99) | +26.0 (121) | +11.5 (48) | +17.0 (77) | |
|
||||
| usual | 697 (47%) | +124.7 / +0.179 | 473 (57%) | +130.9 / +0.277 | +1.4 (274) | +123.2 (423) | +42.7 (226) | +88.3 (247) | |
|
||||
|
||||
**M15 — 7. time to a high-impact US release (W3 only)**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 0–30 min after | | | 42 (5%) | +8.9 / +0.212 | | | +4.7 (27) | +4.2 (15) | |
|
||||
| 0–30 min before | | | 26 (3%) | +8.7 / +0.336 | | | +7.8 (18) | +0.9 (8) | |
|
||||
| 30–120 min after | | | 75 (9%) | +28.4 / +0.378 | | | +11.7 (51) | +16.6 (24) | |
|
||||
| 30–120 min before | | | 22 (3%) | +16.1 / +0.732 | | | +12.9 (18) | +3.2 (4) | |
|
||||
| clear | | | 659 (80%) | +143.0 / +0.217 | | | +39.6 (270) | +103.5 (389) | |
|
||||
|
||||
### 4.2 M5: LONG 1052 trades / +128.10R, W3 870 trades / +153.64R
|
||||
|
||||
**M5 — 1. weekly trend**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| against | 258 (25%) | +11.0 / +0.043 | 202 (23%) | +6.6 / +0.033 | +9.4 (69) | +1.6 (189) | +3.8 (10) | +2.8 (192) | |
|
||||
| unclear | 84 (8%) | +6.3 / +0.075 | 88 (10%) | +24.6 / +0.279 | -7.5 (30) | +13.8 (54) | +8.2 (12) | +16.4 (76) | |
|
||||
| with | 710 (67%) | +110.8 / +0.156 | 580 (67%) | +122.5 / +0.211 | -12.3 (159) | +123.1 (551) | +4.7 (114) | +117.8 (466) | |
|
||||
|
||||
**M5 — 2. four-hour trend**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| against | 332 (32%) | +3.0 / +0.009 | 255 (29%) | +11.2 / +0.044 | -5.5 (98) | +8.4 (234) | -0.9 (35) | +12.2 (220) | |
|
||||
| unclear | 126 (12%) | +13.7 / +0.109 | 105 (12%) | +17.8 / +0.170 | +0.6 (28) | +13.1 (98) | +0.8 (13) | +17.0 (92) | |
|
||||
| with | 594 (56%) | +111.4 / +0.188 | 510 (59%) | +124.6 / +0.244 | -5.5 (132) | +116.9 (462) | +16.9 (88) | +107.7 (422) | |
|
||||
|
||||
**M5 — 3. daily-trend strength**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| counter | 296 (28%) | +0.7 / +0.003 | 241 (28%) | -2.6 / -0.011 | +8.8 (87) | -8.0 (209) | +3.7 (18) | -6.3 (223) | |
|
||||
| medium | 367 (35%) | +77.3 / +0.211 | 318 (37%) | +78.4 / +0.247 | -1.2 (49) | +78.4 (318) | +6.9 (53) | +71.6 (265) | |
|
||||
| strong | 14 (1%) | +16.6 / +1.186 | 13 (1%) | +15.6 / +1.199 | +1.0 (1) | +15.6 (13) | +0.0 (0) | +15.6 (13) | |
|
||||
| weak | 375 (36%) | +33.5 / +0.089 | 298 (34%) | +62.2 / +0.209 | -19.0 (121) | +52.5 (254) | +6.2 (65) | +56.1 (233) | |
|
||||
|
||||
**M5 — 4. daily volatility state**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| normal | 302 (29%) | +33.2 / +0.110 | 305 (35%) | +45.6 / +0.150 | -0.8 (63) | +34.0 (239) | -3.0 (42) | +48.6 (263) | |
|
||||
| quiet | 114 (11%) | +18.8 / +0.165 | 72 (8%) | +17.9 / +0.249 | +3.0 (45) | +15.8 (69) | +10.9 (37) | +7.0 (35) | |
|
||||
| wild | 636 (60%) | +76.1 / +0.120 | 493 (57%) | +90.1 / +0.183 | -12.6 (150) | +88.8 (486) | +8.8 (57) | +81.3 (436) | |
|
||||
|
||||
**M5 — 5. place in the 20-day range**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| middle | 323 (31%) | +11.8 / +0.037 | 271 (31%) | +18.8 / +0.069 | -6.8 (76) | +18.6 (247) | -1.8 (34) | +20.6 (237) | |
|
||||
| near the chasing edge | 439 (42%) | +101.5 / +0.231 | 380 (44%) | +115.0 / +0.303 | -2.6 (103) | +104.1 (336) | +17.2 (77) | +97.8 (303) | |
|
||||
| near the far edge | 290 (28%) | +14.8 / +0.051 | 219 (25%) | +19.9 / +0.091 | -1.0 (79) | +15.9 (211) | +1.4 (25) | +18.5 (194) | |
|
||||
|
||||
**M5 — 6. tick-volume surge**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| surge | 479 (46%) | +46.2 / +0.097 | 334 (38%) | +65.3 / +0.195 | -5.9 (176) | +52.1 (303) | +13.2 (64) | +52.1 (270) | |
|
||||
| thin | 152 (14%) | +9.3 / +0.061 | 134 (15%) | +18.1 / +0.135 | -8.0 (22) | +17.3 (130) | -0.7 (7) | +18.8 (127) | |
|
||||
| usual | 421 (40%) | +72.6 / +0.172 | 402 (46%) | +70.3 / +0.175 | +3.4 (60) | +69.2 (361) | +4.2 (65) | +66.1 (337) | |
|
||||
|
||||
**M5 — 7. time to a high-impact US release (W3 only)**
|
||||
|
||||
| State | LONG trades (share) | LONG R / mean | W3 trades (share) | W3 R / mean | LONG era A (2019–2023) | LONG era B (2024–2026) | W3 first half | W3 second half | Nominated |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 0–30 min after | | | 46 (5%) | -3.8 / -0.082 | | | -0.6 (15) | -3.2 (31) | |
|
||||
| 0–30 min before | | | 18 (2%) | +1.8 / +0.098 | | | +0.7 (5) | +1.1 (13) | |
|
||||
| 30–120 min after | | | 87 (10%) | +32.8 / +0.377 | | | +8.3 (31) | +24.4 (56) | |
|
||||
| 30–120 min before | | | 18 (2%) | +3.4 / +0.187 | | | +2.7 (7) | +0.7 (11) | |
|
||||
| clear | | | 701 (81%) | +119.5 / +0.171 | | | +5.6 (78) | +114.0 (623) | |
|
||||
|
||||
### 4.3 Verdict
|
||||
|
||||
**Nominated states: none.** No state on either timeframe holds 8% of both books and loses in all
|
||||
four slices. By the rule of section 3 there is no replay and nothing ships.
|
||||
|
||||
What the tables do say (in-book description, not a decision):
|
||||
|
||||
- **M15, after the daily-trend veto, has no losing pocket left in these seven views.** The only
|
||||
clearly negative cell is trades against the weekly trend in 2019–2023 (−18.3R over 58 trades),
|
||||
5% of the book and positive since 2024. Trades with the four-hour trend earn +0.18R to +0.29R
|
||||
each against +0.09R to +0.11R against it: weaker, still positive.
|
||||
- **M5 earns its R where everything already agrees.** With the weekly trend +0.16R to +0.21R per
|
||||
trade, against it +0.03R to +0.04R; with the four-hour trend +0.19R to +0.24R, against it
|
||||
+0.01R to +0.04R; when the 60-day move runs against the trade, 0.00R (about 28% of the book).
|
||||
Those against-the-tide M5 trades are flat, not losing, which is the same picture the formal
|
||||
daily-trend test gave on M5: removing them removes no loss, and it made the worst losing run
|
||||
deeper. They stay.
|
||||
- **M5's profit sits near the 20-day extreme in the trade's direction** (+0.23R to +0.30R per
|
||||
trade against +0.04R to +0.09R elsewhere). This repeats the 2026-08-04 finding that the best
|
||||
trades are the stretched runs: a "do not chase" filter would cut the part of the book that pays.
|
||||
- **Volume and news timing separate nothing reliably.** A volume surge is the best state in
|
||||
2019–2023 on M15 and the middle state since; trades 30 to 120 minutes after a major US release
|
||||
look good on both timeframes (+0.38R each) on 75 and 87 trades, and trades in the half hour
|
||||
after a release are slightly negative on M5 (−3.8R over 46 trades, 5% of the book, below the
|
||||
8% bar). Neither is a rule.
|
||||
|
||||
So the daily chart was the one piece of missing context that paid, on M15. Weekly and four-hour
|
||||
context add nothing on top of it there, and nothing on M5 that survives the rule.
|
||||
@@ -0,0 +1,639 @@
|
||||
# Daily-trend veto and wide-stop cap — pre-registered campaign, 2026-09-30
|
||||
|
||||
Owner request (2026-09-30): improve the direction call and the trade setup size; anything may
|
||||
change if the evidence supports it. This file holds the rules first, then the results. Sections 1
|
||||
to 4 were written before any formal run. Section 2 records what was already looked at while
|
||||
planning, so the reader knows which numbers are a confirmation and which are a first look.
|
||||
|
||||
## 1. What is tested
|
||||
|
||||
**Candidate T — daily-trend veto.** The engine sees at most the H1 chart (M5 confirms on M15 and
|
||||
H1, M15 on M5 and H1). None of the 65 decision-time features of the earlier campaigns reads the
|
||||
daily chart. The candidate drops a finished trade setup that goes against a clear daily trend.
|
||||
|
||||
Definition (fixed; `signal_aggregator.daily_trend_series`):
|
||||
- daily candles from the provider (UTC-aligned, label = 00:00 UTC of the day), closed bars only:
|
||||
a bar labelled D is used once D + 1 day ≤ the close of the primary bar;
|
||||
- bars labelled Sunday are dropped first (Oanda's 1 to 2 hour reopening stub);
|
||||
- `ema = Close.ewm(span=50, adjust=False).mean()`, `change = Close / Close.shift(60) − 1`;
|
||||
- state +1 when `Close > ema` and `change > 0`; −1 when `Close < ema` and `change < 0`; else 0;
|
||||
- after every setup archetype has run (gold ATR, swing, pullback, chart pattern) a setup against a
|
||||
non-zero state is removed: grade D, no setup, warning `DAILY TREND FILTER: …`.
|
||||
Switch `xau_strategy.daily_trend_veto`: `off` (default, books byte-identical), `both`, `buy`
|
||||
(only buys against a falling daily trend are removed), `sell`.
|
||||
|
||||
**Candidate C — wide-stop cap.** `xau_strategy.wide_stop_cap_atr = K` caps the stop distance of
|
||||
the swing (GENERIC) and chart-pattern setups at `max(K × ATR, the existing floor)`, applied after
|
||||
the min-RR and spread gates so the set of trades cannot change. K = 6, with 5, 7 and 8 declared
|
||||
as neighbours. M15 only (the exit-only screen was negative on M5).
|
||||
|
||||
## 2. What was already seen (planning phase, 2026-09-30, read-only, in memory)
|
||||
|
||||
Prototype: K1 and K2 of the CUDA twin, then a host-side mask on the final candidates before the
|
||||
5-bar cooldown, then the shared resolver. The control rows of the prototype reproduce the pinned
|
||||
books (M15 W3 1105 / +195.79R, M5 W3 870 / +153.64R, H1 W3 253 / +70.02R).
|
||||
|
||||
| Book ($0.65) | Control | Veto, both directions |
|
||||
|---|---|---|
|
||||
| M15 W3 2023-09-05 → 2026-09-05 | 1105 / +195.8R / PF 1.49 / DD 20.9 | 824 / +205.1R / PF 1.73 / DD 12.4 |
|
||||
| M15 LONG 2019-01-01 → 2026-08-07 | 2091 / +192.5R / PF 1.24 / DD 64.8 | 1473 / +223.3R / PF 1.42 / DD 41.5 |
|
||||
| M15 rising window 2024-06-01 → 2025-03-30 | 313 / +68.5R / PF 1.67 | 258 / +63.8R / PF 1.80 |
|
||||
| M5 W3 | 870 / +153.6R / PF 1.50 / DD 10.1 | 668 / +159.1R / PF 1.70 / DD 15.2 |
|
||||
| M5 W3, buy side only | | 741 / +156.5R / PF 1.60 / DD 9.6 |
|
||||
| M5 LONG | 1052 / +128.1R / PF 1.34 / DD 18.4 | 790 / +127.5R / PF 1.45 / DD 25.2 |
|
||||
| H1 W3 | 253 / +70.0R / PF 1.98 / DD 4.4 | 231 / +60.3R / PF 1.90 / DD 4.4 |
|
||||
|
||||
Also seen: neighbouring definitions on M15 (60-day change alone +188.3R W3 / +228.1R LONG;
|
||||
90-day +196.1 / +228.8; 120-day +199.4 / +233.2; 100-day average +201.2 / +227.3); the share of
|
||||
M15 W3 trades reaching +1R before −1R (58.6% → 61.4%); the live September 2026 setups with the
|
||||
veto applied after the fact (cooldown view −11.6R → −6.0R to −8.8R); an exit-only re-simulation
|
||||
of 25 stop and exit variants (cap 6 × ATR on M15 W3 +14.0R, LONG +11.8R; cap 5 negative on LONG);
|
||||
two variants dropped at this stage and NOT tested formally: lifting the SELL gates when the daily
|
||||
trend is down (worse on LONG on both timeframes), and removing the existing M15 short blocks
|
||||
under the veto (costs 4 to 22R).
|
||||
|
||||
So the windows W3, W2, the rising window and LONG are a CONFIRMATION through the real code path.
|
||||
First looks are: the HOLDOUT window through the backtester, the $0.30 numbers on LONG, and every
|
||||
number produced by the shipped code rather than the prototype.
|
||||
|
||||
## 3. Harness
|
||||
|
||||
`venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf <tf> --start <s>
|
||||
--end <e> --spread <0.65|0.30> [--set xau_strategy.daily_trend_veto=<mode>]
|
||||
[--set xau_strategy.wide_stop_cap_atr=<K>] --export-csv <out>`; factory configuration otherwise.
|
||||
Twin numbers count only with the paired `backtest/gpu_backtest.py` MD5 (same flags plus
|
||||
`--workers 12`). Neighbouring trend definitions are not shipped code: they run on the research
|
||||
harness (the prototype), after the harness book for the shipped definition is shown identical to
|
||||
the twin's.
|
||||
|
||||
Windows: **W3** 2023-09-05 → 2026-09-05 (halves at 2025-03-05); **W2** 2024-09-05 → 2026-09-05
|
||||
(halves at 2025-09-05); **RISING** 2024-06-01 → 2025-03-30 (the old out-of-sample window; a
|
||||
rising market throughout); **LONG** 2019-01-01 → 2026-08-07; **HOLDOUT** 2026-09-05 → 2026-10-01
|
||||
(fetched for this campaign, never seen by any tuning or by the prototype).
|
||||
|
||||
## 4. Acceptance rules (written before the runs)
|
||||
|
||||
**Rule T (risk-first; owner decision 2026-09-30).** Candidate T on a timeframe PASSES only if,
|
||||
against the same-harness control:
|
||||
- (a) W3 total R ≥ 97% of the control's at $0.65;
|
||||
- (b) W3 profit factor ≥ control + 0.10;
|
||||
- (c) W3 max drawdown ≤ the control's;
|
||||
- (d) LONG: total R ≥ control, max drawdown ≤ control, and 2021 + 2022 combined R better;
|
||||
- (e) HOLDOUT total R ≥ control − 0.5R;
|
||||
- (f) clauses (a) to (c) also hold at $0.30;
|
||||
- (g) at least 3 of the 4 neighbouring definitions (60-day change alone, 90-day, 120-day,
|
||||
100-day average alone) satisfy (a) and (c): a plateau, not a spike;
|
||||
- (h) at least 20 trades differ on W3.
|
||||
Reported, NOT clauses (the owner accepted this cost on 2026-09-30): the W2 halves and the RISING
|
||||
window, where the veto is expected to give up some R. The 2026-09-26 template (every half and the
|
||||
old out-of-sample window must not lose) would reject this candidate by construction; that is the
|
||||
difference between the two rules and it is deliberate.
|
||||
Decision per timeframe: `both` if it passes; else `buy` if that passes; else no veto (the daily
|
||||
trend is then shown on the alert as information only). H1 is run for the record.
|
||||
|
||||
**Rule C (standard).** K = 6 on M15 against the post-T control PASSES only if: W3 ΔR ≥ +5R; both
|
||||
W3 halves ≥ control − 1R; PF ≥ control − 0.02; W3 max drawdown ≤ control + 10%; LONG total R ≥
|
||||
control; the same sign of ΔR at $0.30; at least 20 trades differ; and the neighbours K = 5 and
|
||||
K = 7 are not negative on W3 or on LONG.
|
||||
|
||||
No rescue for either rule: no second definition, no other K, no window shopping after the result.
|
||||
|
||||
## 5. Results
|
||||
|
||||
All runs 2026-09-30 on the dev box, branch `sql-integration`, working tree = HEAD `0c56b4e` plus
|
||||
this campaign's code. Every twin log reads
|
||||
`Backend: CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)` and
|
||||
`GPU cache fallbacks: 0 (no per-bar recompute path in cudaBasedBacktesting)`; every reference log
|
||||
reads the same `Backend:` line and `GPU cache fallbacks: 0`. 86 formal twin books (5 windows × 2
|
||||
costs × the variants below, plus the fixed-1R-target books). R is per trade in units of the
|
||||
initial risk; "DD" is the largest peak-to-trough fall of the running total, in R.
|
||||
|
||||
### 5.1 Proofs before any formal number
|
||||
|
||||
- **Baseline.** Before any code change the five reference MD5s of CLAUDE.md §9 were reproduced on
|
||||
both scripts (ten books).
|
||||
- **Default-off neutrality.** With the new code and both knobs at their defaults the same ten
|
||||
books are byte-identical to the pins (`e599c8a8…` M5 W2, `5b65ecf6…` M15 W2, `09b8a8e7…` M15
|
||||
OOS, `a44fd19b…` H1 W3, `f3587bae…` H4 W3).
|
||||
- **Twin = reference on every new path** (`--spread 0.65`, W3 2023-09-05 → 2026-09-05, equal MD5
|
||||
from `backtest/gpu_backtest.py --workers 12` and `backtest/cudaBasedBacktesting.py`):
|
||||
|
||||
| Flags | Trades / R / DD | MD5 (both scripts) |
|
||||
|---|---|---|
|
||||
| M15 `--set xau_strategy.daily_trend_veto=both` | 824 / +205.12 / 12.39 | `ad3b0dbda0bf77b659caea1c53be6a50` |
|
||||
| M15 `…=buy` | 999 / +188.21 / 20.87 | `6e0f3b742d9690f78c97319d1b1c8a43` |
|
||||
| M15 `…=sell` | 930 / +212.70 / 13.72 | `31aa5762964d846f15d111cd4a718adc` |
|
||||
| M15 `--set xau_strategy.wide_stop_cap_atr=6` | 1105 / +209.78 / 19.32 | `4ec0e4450d5dab143a4ed37e80545626` |
|
||||
| M5 `--set xau_strategy.daily_trend_veto=both` | 668 / +159.07 / 15.20 | `39cc0f198d38b532720dce4f98d99452` |
|
||||
| M5 `--set xau_strategy.wide_stop_cap_atr=6` | 870 / +141.57 / 13.95 | `4da0997256b272ffcd11a90bf9658eef` |
|
||||
|
||||
The M15 `both` book equals the planning prototype trade for trade (824 / +205.1R / PF 1.73 /
|
||||
DD 12.4), so the shipped definition is the one that was screened.
|
||||
- **Suite.** 595 passed / 0 failed after Stage 1 (Module 39 new, Module 37 extended).
|
||||
|
||||
### 5.2 Candidate T — every window, both costs
|
||||
|
||||
Command per row: `venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf
|
||||
<15min|5min> --start <s> --end <e> --spread <0.65|0.30> [--set xau_strategy.daily_trend_veto=<mode>]
|
||||
--export-csv <out>`. "control" = no `--set`.
|
||||
|
||||
**M15**
|
||||
|
||||
| Window | Cost | Variant | Trades | Total R | Win rate | PF | Max DD | First half | Second half | MD5 (8) |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| W3 | $0.65 | control | 1105 | +195.79 | 61.1% | 1.49 | 20.9 | +76.87 | +118.92 | 70195fb0 |
|
||||
| W3 | $0.65 | both | 824 | +205.12 | 63.1% | 1.73 | 12.4 | +76.72 | +128.40 | ad3b0dbd |
|
||||
| W3 | $0.65 | buy | 999 | +188.21 | 61.3% | 1.52 | 20.9 | +60.40 | +127.81 | 6e0f3b74 |
|
||||
| W3 | $0.65 | sell | 930 | +212.70 | 62.7% | 1.66 | 13.7 | +93.19 | +119.52 | 31aa5762 |
|
||||
| W3 | $0.30 | control | 1105 | +216.53 | 61.4% | 1.55 | 19.6 | +88.86 | +127.67 | ce074a1b |
|
||||
| W3 | $0.30 | both | 824 | +221.18 | 63.6% | 1.81 | 11.4 | +86.41 | +134.77 | 96a87454 |
|
||||
| W3 | $0.30 | buy | 999 | +207.23 | 61.7% | 1.59 | 19.6 | +71.83 | +135.41 | 2ada14e4 |
|
||||
| W3 | $0.30 | sell | 930 | +230.48 | 63.1% | 1.73 | 12.8 | +103.45 | +127.03 | 49665c41 |
|
||||
| W2 | $0.65 | control | 813 | +154.15 | 62.2% | 1.53 | 15.6 | +70.10 | +84.04 | 5b65ecf6 |
|
||||
| W2 | $0.65 | both | 597 | +155.89 | 64.3% | 1.78 | 11.4 | +61.83 | +94.06 | dfc647ee |
|
||||
| W2 | $0.65 | buy | 718 | +154.67 | 63.0% | 1.62 | 14.4 | +65.03 | +89.65 | f23bfda4 |
|
||||
| W2 | $0.65 | sell | 692 | +155.36 | 63.3% | 1.65 | 13.7 | +66.91 | +88.46 | 1a071ac7 |
|
||||
| W2 | $0.30 | control | 813 | +167.53 | 62.6% | 1.59 | 14.5 | +78.61 | +88.91 | 15486607 |
|
||||
| W2 | $0.30 | both | 597 | +166.22 | 64.8% | 1.85 | 11.0 | +68.82 | +97.39 | d2f678bf |
|
||||
| W2 | $0.30 | buy | 718 | +166.64 | 63.4% | 1.68 | 14.0 | +73.12 | +93.52 | f3abc1e8 |
|
||||
| W2 | $0.30 | sell | 692 | +167.10 | 63.7% | 1.71 | 12.8 | +74.31 | +92.79 | d7d038fd |
|
||||
| RISING | $0.65 | control | 313 | +68.50 | 63.6% | 1.67 | 12.4 | +18.87 | +49.63 | 09b8a8e7 |
|
||||
| RISING | $0.65 | both | 258 | +63.78 | 64.3% | 1.80 | 9.1 | +23.97 | +39.81 | e87b6de1 |
|
||||
| RISING | $0.65 | buy | 303 | +60.15 | 63.0% | 1.60 | 12.4 | +18.87 | +41.28 | f001a0a6 |
|
||||
| RISING | $0.65 | sell | 268 | +72.13 | 64.9% | 1.89 | 9.1 | +23.97 | +48.16 | cbe4cb24 |
|
||||
| RISING | $0.30 | control | 313 | +76.21 | 64.5% | 1.77 | 11.1 | +22.92 | +53.29 | 5fc34cef |
|
||||
| RISING | $0.30 | both | 258 | +70.24 | 65.5% | 1.91 | 7.9 | +27.31 | +42.92 | 5de32f51 |
|
||||
| RISING | $0.30 | buy | 303 | +67.59 | 64.0% | 1.70 | 11.1 | +22.92 | +44.67 | 69b41026 |
|
||||
| RISING | $0.30 | sell | 268 | +78.86 | 66.0% | 2.00 | 7.9 | +27.31 | +51.54 | 3631466e |
|
||||
| LONG | $0.65 | control | 2091 | +192.53 | 57.8% | 1.24 | 64.8 | | | c2228398 |
|
||||
| LONG | $0.65 | both | 1473 | +223.33 | 59.7% | 1.42 | 41.5 | | | aa5253aa |
|
||||
| LONG | $0.65 | buy | 1762 | +214.62 | 58.9% | 1.33 | 34.5 | | | 952d64d8 |
|
||||
| LONG | $0.65 | sell | 1802 | +201.23 | 58.3% | 1.30 | 76.4 | | | d3965ab8 |
|
||||
| LONG | $0.30 | control | 2091 | +240.00 | 58.8% | 1.31 | 49.6 | | | 0c58b300 |
|
||||
| LONG | $0.30 | both | 1473 | +257.25 | 60.8% | 1.50 | 37.2 | | | 1835b341 |
|
||||
| LONG | $0.30 | buy | 1762 | +254.33 | 59.9% | 1.40 | 29.5 | | | a2b519bd |
|
||||
| LONG | $0.30 | sell | 1802 | +242.92 | 59.4% | 1.37 | 62.3 | | | f6edb469 |
|
||||
| HOLDOUT | $0.65 | control | 24 | −6.63 | 50.0% | 0.42 | 7.0 | | | c42470a0 |
|
||||
| HOLDOUT | $0.65 | both | 18 | −4.62 | 50.0% | 0.50 | 4.8 | | | fa50bcd0 |
|
||||
| HOLDOUT | $0.65 | buy | 22 | −6.81 | 45.5% | 0.41 | 7.0 | | | 548a4bf4 |
|
||||
| HOLDOUT | $0.65 | sell | 20 | −4.44 | 55.0% | 0.52 | 4.8 | | | 1459ef5f |
|
||||
| HOLDOUT | $0.30 | control | 24 | −6.29 | 50.0% | 0.44 | 6.7 | | | d7fbee9d |
|
||||
| HOLDOUT | $0.30 | both | 18 | −4.35 | 50.0% | 0.52 | 4.5 | | | 2179c08c |
|
||||
| HOLDOUT | $0.30 | buy | 22 | −6.49 | 45.5% | 0.43 | 6.7 | | | 0b90faeb |
|
||||
| HOLDOUT | $0.30 | sell | 20 | −4.15 | 55.0% | 0.54 | 4.5 | | | 70932b7e |
|
||||
|
||||
**M5**
|
||||
|
||||
| Window | Cost | Variant | Trades | Total R | Win rate | PF | Max DD | First half | Second half | MD5 (8) |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| W3 | $0.65 | control | 870 | +153.64 | 53.3% | 1.50 | 10.1 | +16.73 | +136.91 | d20ec8a3 |
|
||||
| W3 | $0.65 | both | 668 | +159.07 | 53.9% | 1.70 | 15.2 | +14.01 | +145.06 | 39cc0f19 |
|
||||
| W3 | $0.65 | buy | 741 | +156.53 | 53.4% | 1.60 | 9.6 | +15.68 | +140.85 | 631f25ac |
|
||||
| W3 | $0.30 | control | 870 | +172.40 | 54.3% | 1.57 | 8.9 | +21.08 | +151.32 | 5acafcba |
|
||||
| W3 | $0.30 | both | 668 | +174.43 | 54.8% | 1.79 | 14.3 | +18.09 | +156.34 | 34f2e282 |
|
||||
| W3 | $0.30 | buy | 741 | +172.72 | 54.3% | 1.69 | 8.9 | +19.87 | +152.86 | c8bdc9e2 |
|
||||
| W2 | $0.65 | control | 781 | +146.07 | 52.9% | 1.52 | 10.1 | +26.42 | +119.65 | e599c8a8 |
|
||||
| W2 | $0.65 | both | 585 | +153.82 | 53.7% | 1.78 | 15.2 | +33.16 | +120.66 | b9fa356c |
|
||||
| W2 | $0.65 | buy | 656 | +151.09 | 53.0% | 1.66 | 9.6 | +27.42 | +123.66 | 30e243b0 |
|
||||
| W2 | $0.30 | control | 781 | +162.09 | 53.9% | 1.60 | 8.9 | +31.71 | +130.37 | 7ab48db9 |
|
||||
| W2 | $0.30 | both | 585 | +166.59 | 54.7% | 1.87 | 14.3 | +38.05 | +128.54 | 3427b933 |
|
||||
| W2 | $0.30 | buy | 656 | +164.66 | 54.0% | 1.74 | 8.9 | +32.58 | +132.08 | 7dd9e2a2 |
|
||||
| RISING | $0.65 | control | 86 | +9.47 | 53.5% | 1.33 | 8.2 | +0.01 | +9.46 | 65c46e62 |
|
||||
| RISING | $0.65 | both | 81 | +9.07 | 54.3% | 1.34 | 7.5 | +0.67 | +8.40 | e20fdef2 |
|
||||
| RISING | $0.65 | buy | 85 | +10.55 | 54.1% | 1.39 | 7.2 | +0.01 | +10.54 | de24b2e8 |
|
||||
| RISING | $0.30 | control | 86 | +12.42 | 54.7% | 1.46 | 6.7 | +1.65 | +10.77 | 5c154360 |
|
||||
| RISING | $0.30 | both | 81 | +11.91 | 55.6% | 1.47 | 6.1 | +2.28 | +9.63 | 3b1a8f9e |
|
||||
| RISING | $0.30 | buy | 85 | +13.46 | 55.3% | 1.52 | 5.7 | +1.65 | +11.81 | 21ffab88 |
|
||||
| LONG | $0.65 | control | 1052 | +128.10 | 50.5% | 1.34 | 18.4 | | | 3f0c091c |
|
||||
| LONG | $0.65 | both | 790 | +127.47 | 50.4% | 1.45 | 25.2 | | | 9872c068 |
|
||||
| LONG | $0.65 | buy | 871 | +123.37 | 49.9% | 1.39 | 27.1 | | | 613dcf0e |
|
||||
| LONG | $0.30 | control | 1052 | +153.02 | 51.7% | 1.41 | 13.0 | | | 6708d8cc |
|
||||
| LONG | $0.30 | both | 790 | +147.13 | 51.5% | 1.54 | 19.6 | | | 201d968f |
|
||||
| LONG | $0.30 | buy | 871 | +144.02 | 51.1% | 1.47 | 22.3 | | | d9cba459 |
|
||||
| HOLDOUT | $0.65 | control | 33 | −2.45 | 51.5% | 0.82 | 6.1 | | | 351f7149 |
|
||||
| HOLDOUT | $0.65 | both | 26 | −2.04 | 50.0% | 0.81 | 5.4 | | | b9c8cb82 |
|
||||
| HOLDOUT | $0.65 | buy | 33 | −2.45 | 51.5% | 0.82 | 6.1 | | | 351f7149 |
|
||||
| HOLDOUT | $0.30 | control | 33 | −1.85 | 51.5% | 0.86 | 5.6 | | | 5a6aea9c |
|
||||
| HOLDOUT | $0.30 | both | 26 | −1.56 | 50.0% | 0.85 | 5.0 | | | ddd29068 |
|
||||
| HOLDOUT | $0.30 | buy | 33 | −1.85 | 51.5% | 0.86 | 5.6 | | | 5a6aea9c |
|
||||
|
||||
**H1, for the record** ($0.65): W3 control 253 / +70.02R / PF 1.98 / DD 4.4 (`a44fd19b`), `both`
|
||||
231 / +60.28R / PF 1.90 / DD 4.4 (`cdac563b`); HOLDOUT 3 / −3.04R in both (no trade removed).
|
||||
Fails (a) (60.28 < 67.92) and (b). H4 was not run: it confirms on the daily chart already.
|
||||
|
||||
### 5.3 Per calendar year ($0.65), total R / trades
|
||||
|
||||
M15, LONG 2019-01-01 → 2026-08-07:
|
||||
|
||||
| Variant | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | 2026 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| control | +9.0 / 117 | +48.8 / 283 | −25.6 / 210 | −24.6 / 267 | +11.7 / 211 | +53.3 / 343 | +81.2 / 425 | +38.7 / 235 |
|
||||
| both | +14.0 / 88 | +45.1 / 207 | −27.3 / 121 | +3.2 / 149 | +1.8 / 163 | +53.5 / 275 | +92.5 / 341 | +40.6 / 129 |
|
||||
| buy | +7.6 / 111 | +44.2 / 253 | −21.4 / 135 | +7.4 / 166 | +1.6 / 189 | +46.4 / 335 | +83.1 / 416 | +45.9 / 157 |
|
||||
| sell | +15.4 / 94 | +49.7 / 237 | −31.5 / 196 | −28.8 / 250 | +11.9 / 185 | +60.4 / 283 | +90.6 / 350 | +33.4 / 207 |
|
||||
|
||||
M5, LONG:
|
||||
|
||||
| Variant | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | 2026 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| control | −1.0 / 20 | −2.8 / 95 | −1.7 / 32 | −2.2 / 50 | −2.8 / 61 | +1.0 / 102 | +40.5 / 332 | +97.0 / 360 |
|
||||
| both | +0.2 / 17 | −6.9 / 76 | −3.4 / 14 | −4.7 / 28 | −3.5 / 54 | +1.5 / 96 | +43.2 / 297 | +101.0 / 208 |
|
||||
| buy | +0.2 / 17 | −6.7 / 83 | −3.2 / 16 | −6.0 / 30 | −4.6 / 56 | +2.1 / 101 | +40.5 / 329 | +101.0 / 239 |
|
||||
|
||||
M15, W3 per calendar year (total R / trades / max DD inside the year):
|
||||
|
||||
| Variant | 2023 (from 09-05) | 2024 | 2025 | 2026 (to 09-05) |
|
||||
|---|---|---|---|---|
|
||||
| control | +10.8 / 66 / 9.0 | +53.3 / 343 / 16.3 | +81.2 / 425 / 15.6 | +50.5 / 271 / 10.3 |
|
||||
| both | +9.3 / 47 / 6.9 | +53.5 / 275 / 11.0 | +92.5 / 341 / 11.4 | +49.9 / 161 / 8.3 |
|
||||
|
||||
M15, R per month from 2025-09 (W3):
|
||||
|
||||
| Variant | 2025-09 | 10 | 11 | 12 | 2026-01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 (to 09-05) |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| control | +14.7 | −1.4 | +3.7 | +19.4 | +29.1 | −2.2 | +9.5 | +4.4 | +0.0 | +1.7 | −4.6 | +12.4 | +0.2 |
|
||||
| both | +19.4 | +0.6 | +8.4 | +18.7 | +32.4 | −2.4 | +1.5 | +7.2 | −2.4 | +5.1 | +2.3 | +5.9 | +0.2 |
|
||||
|
||||
Losing runs on M15: longest run of losing trades 9 → 8 (W3 and LONG); worst calendar month
|
||||
−10.1R → −6.1R (W3, 2024-01) and −11.7R → −10.0R (LONG); losing months 11 → 8 of 37 on W3,
|
||||
39 of 92 → 37 of 91 on LONG.
|
||||
|
||||
### 5.4 Rule T, clause by clause
|
||||
|
||||
**M15 `both` — PASS**
|
||||
|
||||
| Clause | Result | Numbers (candidate vs control) |
|
||||
|---|---|---|
|
||||
| (a) W3 R ≥ 97% of control | PASS | +205.12 vs +195.79 (97% = +189.92) |
|
||||
| (b) W3 PF ≥ control + 0.10 | PASS | 1.734 vs 1.488 |
|
||||
| (c) W3 max DD ≤ control | PASS | 12.39 vs 20.87 |
|
||||
| (d) LONG R ≥ control | PASS | +223.33 vs +192.53 |
|
||||
| (d) LONG max DD ≤ control | PASS | 41.54 vs 64.77 |
|
||||
| (d) 2021 + 2022 better | PASS | −24.12 vs −50.21 |
|
||||
| (e) HOLDOUT R ≥ control − 0.5 | PASS | −4.62 (18 trades) vs −6.63 (24) |
|
||||
| (f) (a) at $0.30 | PASS | +221.18 vs +216.53 (97% = +210.04) |
|
||||
| (f) (b) at $0.30 | PASS | 1.809 vs 1.551 |
|
||||
| (f) (c) at $0.30 | PASS | 11.44 vs 19.56 |
|
||||
| (g) ≥ 3 of 4 neighbours pass (a) and (c) | PASS | 3 of 4 (table 5.5) |
|
||||
| (h) ≥ 20 trades differ on W3 | PASS | 281 removed, 0 added |
|
||||
|
||||
**M15 `buy` — FAIL**: (a) +188.21 < +189.92; (b) 1.523 < 1.588; the same two at $0.30.
|
||||
**M15 `sell` — FAIL**: (d) LONG max DD 76.39 > 64.77 and 2021 + 2022 −60.29 < −50.21. It has the
|
||||
best W3 total (+212.70R) and that is exactly the trap the long window was there to catch: in the
|
||||
falling years the buys against the daily trend are the losers, and `sell` keeps them.
|
||||
**M5 `both` — FAIL**: (c) W3 DD 15.20 > 10.11 (also at $0.30: 14.30 > 8.88); (d) LONG R
|
||||
+127.47 < +128.10, LONG DD 25.25 > 18.39, 2021 + 2022 −8.02 < −3.91; (g) 0 of 4.
|
||||
**M5 `buy` — FAIL**: (d) LONG R +123.37 < +128.10, LONG DD 27.14 > 18.39, 2021 + 2022 −9.13 <
|
||||
−3.91; (g) 1 of 4.
|
||||
**H1 `both` — FAIL** (a), (b).
|
||||
|
||||
### 5.5 Clause (g): neighbouring definitions (research harness)
|
||||
|
||||
The harness (K1 + K2 of the twin with the veto OFF, a host-side mask on the final candidates
|
||||
before the cooldown, the shared resolver) reproduces the twin's `--set` books byte for byte for
|
||||
the shipped definition, so it is the same thing as the shipped path for a final veto:
|
||||
|
||||
| Book ($0.65) | Harness CSV MD5 | Twin CSV MD5 |
|
||||
|---|---|---|
|
||||
| M15 W3 control | `70195fb0e6865b3a46ee8a9aa8844e51` | same |
|
||||
| M15 W3 `both` | `ad3b0dbda0bf77b659caea1c53be6a50` | same |
|
||||
| M15 LONG control | `c222839878f791e980b2dbc99fb6dfbd` | same |
|
||||
| M15 LONG `both` | `aa5253aab206fd21fa6a8d5ba25b75b5` | same |
|
||||
| M5 W3 control / `both` / `buy` | `d20ec8a3…` / `39cc0f19…` / `631f25ac…` | same |
|
||||
| M5 LONG control / `both` / `buy` | `3f0c091c…` / `9872c068…` / `613dcf0e…` | same |
|
||||
|
||||
M15, veto in both directions, each neighbour replacing the definition of "clear daily trend":
|
||||
|
||||
| Definition | W3 trades | W3 R | W3 PF | W3 DD | (a) | (c) | LONG trades | LONG R | LONG PF | LONG DD |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| control | 1105 | +195.79 | 1.49 | 20.87 | (97% = +189.92) | | 2091 | +192.53 | 1.24 | 64.77 |
|
||||
| **shipped: 50-day average AND 60-day change** | 824 | +205.12 | 1.73 | 12.39 | PASS | PASS | 1473 | +223.33 | 1.42 | 41.54 |
|
||||
| 60-day change alone | 752 | +188.25 | 1.73 | 11.89 | FAIL | PASS | 1328 | +228.11 | 1.49 | 27.30 |
|
||||
| 90-day change alone | 782 | +196.14 | 1.72 | 12.46 | PASS | PASS | 1412 | +228.75 | 1.46 | 37.36 |
|
||||
| 120-day change alone | 781 | +199.36 | 1.75 | 12.26 | PASS | PASS | 1397 | +233.19 | 1.47 | 34.79 |
|
||||
| 100-day average alone | 796 | +201.23 | 1.74 | 12.26 | PASS | PASS | 1375 | +227.30 | 1.47 | 32.56 |
|
||||
|
||||
3 of 4 pass. Every neighbour beats the control on PF and on drawdown in both windows; the one
|
||||
miss is 1.7R under the 97% line. It is a plateau. (Each neighbour has a lower LONG drawdown than
|
||||
the shipped definition; switching to one now would be the rescue the rule forbids.)
|
||||
|
||||
M5: `both` 0 of 4 (every neighbour keeps the 15.2R drawdown), `buy` 1 of 4 (90-day, 120-day and
|
||||
100-day average all fall under the 97% line). No plateau on M5.
|
||||
|
||||
### 5.6 What the veto removes (M15, $0.65)
|
||||
|
||||
Zero trades are added in any window (W3 and LONG, every variant, both timeframes): the setups
|
||||
inside a vetoed setup's 5-bar cooldown point the same way under the same daily state and are
|
||||
vetoed with it, so the cooldown displaces nothing.
|
||||
|
||||
| Slice | W3 trades | W3 R | W3 mean R | LONG trades | LONG R | LONG mean R |
|
||||
|---|---|---|---|---|---|---|
|
||||
| all removed | 281 | −9.32 | −0.033 | 618 | −30.80 | −0.050 |
|
||||
| BUY against a falling daily trend | 106 | +7.59 | +0.072 | 329 | −22.10 | −0.067 |
|
||||
| SELL against a rising daily trend | 175 | −16.91 | −0.097 | 289 | −8.70 | −0.030 |
|
||||
| removed, grade B | 210 | −11.53 | −0.055 | 408 | −25.69 | −0.063 |
|
||||
| removed, grade A | 46 | +1.28 | +0.028 | 164 | −1.12 | −0.007 |
|
||||
| removed, grade A+ | 14 | +4.26 | +0.304 | 26 | +1.81 | +0.069 |
|
||||
| removed, grade C | 11 | −3.34 | −0.304 | 20 | −5.80 | −0.290 |
|
||||
| kept (the new book) | 824 | +205.12 | +0.249 | 1473 | +223.33 | +0.152 |
|
||||
| kept BUY | 763 | +194.20 | +0.255 | 1302 | +222.50 | +0.171 |
|
||||
| kept SELL | 61 | +10.91 | +0.179 | 171 | +0.83 | +0.005 |
|
||||
|
||||
Reading: the removed trades are close to break-even after costs (−0.03 to −0.05R each) while the
|
||||
kept trades earn +0.15 to +0.25R each. The veto does not find big losers; it drops about a quarter
|
||||
of the trades, the ones with no edge, and with them the long flat-to-down stretches. The two
|
||||
halves of the veto are carried by different windows: the SELL half by the rising 3-year window,
|
||||
the BUY half by the falling years of the long window. Only the symmetric rule holds on both.
|
||||
|
||||
Share of trades that reach +1R before −1R (same entries, fixed 1R target, `--fixed-rr-tp 1.0`,
|
||||
$0.65): M15 W3 58.6% → 61.4% (1105 → 824 trades), M15 LONG 55.9% → 58.2%; M5 W3 56.8% → 58.2%,
|
||||
M5 LONG 55.3% → 56.1%.
|
||||
|
||||
Checklist breakdowns, M15 W3 → with the veto (trades / R), $0.65:
|
||||
|
||||
| Slice | Control | Veto | Change in R |
|
||||
|---|---|---|---|
|
||||
| regime ranging | 140 / +46.75 | 106 / +49.23 | +2.48 |
|
||||
| regime transitioning | 264 / +47.05 | 199 / +49.47 | +2.42 |
|
||||
| regime trending_down | 165 / −4.39 | 43 / +9.71 | +14.10 |
|
||||
| regime trending_up | 536 / +106.38 | 476 / +96.70 | −9.68 |
|
||||
| volatility extreme | 508 / +103.91 | 361 / +95.86 | −8.05 |
|
||||
| volatility high | 228 / +4.00 | 135 / +18.32 | +14.32 |
|
||||
| volatility normal | 202 / +58.96 | 177 / +57.01 | −1.96 |
|
||||
| volatility low | 167 / +28.92 | 151 / +33.94 | +5.01 |
|
||||
| BUY | 869 / +201.79 | 763 / +194.20 | −7.59 |
|
||||
| SELL | 236 / −6.00 | 61 / +10.91 | +16.91 |
|
||||
| setup GENERIC | 1004 / +184.73 | 757 / +192.15 | +7.42 |
|
||||
| setup XAU_ATR | 101 / +11.07 | 67 / +12.97 | +1.90 |
|
||||
| Monday | 188 / +36.54 | 145 / +52.46 | +15.92 |
|
||||
| Tuesday | 240 / +44.89 | 186 / +41.82 | −3.07 |
|
||||
| Wednesday | 234 / +12.08 | 165 / +13.36 | +1.28 |
|
||||
| Thursday | 216 / +63.84 | 157 / +70.36 | +6.51 |
|
||||
| Friday | 214 / +33.51 | 162 / +21.04 | −12.47 |
|
||||
| Sunday evening | 13 / +4.94 | 9 / +6.08 | +1.14 |
|
||||
|
||||
By entry hour (UTC), change in R on W3 / on LONG: 03 +0.3 / +1.5; 04 −2.1 / −0.1; 05 −0.2 / +2.6;
|
||||
06 +1.9 / −1.5; 07 +2.9 / +0.9; 08 −5.3 / −0.3; 09 −6.6 / −4.0; 12 −2.9 / +2.7; 14 +11.8 / +12.9;
|
||||
15 −4.0 / +1.8; 17 −0.5 / −2.4; 18 +11.4 / +11.3; 19 +0.9 / +2.7; 20 +0.8 / +2.9; 22 +0.8 / −0.1.
|
||||
The weekday and hour changes are not stable in sign between the two windows except hours 14 and 18
|
||||
(positive in both) and hour 9 (negative in both); none of it is a reason to act.
|
||||
|
||||
### 5.7 Verdict T
|
||||
|
||||
- **M15: `both` PASSES every clause and SHIPS** (`pair_signals/xauusd.py`, M15 override 7).
|
||||
- **M5: no veto.** `both` deepens the worst losing run (10.1R → 15.2R) and loses the long window;
|
||||
`buy` passes the 3-year window and fails the long one, with no plateau. The M5 book earns
|
||||
almost all of its R in 2025–2026 and the daily trend adds nothing reliable to it.
|
||||
- **H1 / H4: no veto.** H1 loses 10R of 70R; H4 already confirms on the daily chart.
|
||||
- On the timeframes without the veto the daily trend is shown on the alert as information only.
|
||||
|
||||
What the owner gives up, as agreed: in the purely rising stretch the veto costs R. W2 first half
|
||||
(2024-09 → 2025-09) +61.83R vs +70.10R; the RISING window +63.78R vs +68.50R; W2 at $0.30
|
||||
+166.22R vs +167.53R. What it buys: the worst losing run on the 3-year window falls from 20.9R to
|
||||
12.4R, on the 7.6-year window from 64.8R to 41.5R, 2022 turns from −24.6R to +3.2R, and the three
|
||||
weeks after the test window (HOLDOUT, never seen by any tuning) lose 4.6R instead of 6.6R. 2021
|
||||
does not improve (−27.3R vs −25.6R) and 2023 is worse (+1.8R vs +11.7R): in a year that chops
|
||||
sideways the daily trend flips late and the veto cannot help.
|
||||
|
||||
### 5.8 Candidate C — wide-stop cap on M15 (post-T control)
|
||||
|
||||
Control = the factory configuration after the M15 flip (daily-trend veto on). Command per row:
|
||||
`venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf 15min --start <s>
|
||||
--end <e> --spread <0.65|0.30> [--set xau_strategy.wide_stop_cap_atr=<K>] --export-csv <out>`.
|
||||
Paired proof on this control: W3, K = 6, `gpu_backtest.py --workers 12` and the twin both give
|
||||
`f94f1429fb4c9172427ea34c2f4d92a1` (824 / +216.87R / DD 11.37).
|
||||
|
||||
| Window | Cost | K | Trades | Total R | Win rate | PF | Max DD | First half | Second half | MD5 (8) |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| W3 | $0.65 | off | 824 | +205.12 | 63.1% | 1.73 | 12.4 | +76.72 | +128.40 | ad3b0dbd |
|
||||
| W3 | $0.65 | 5 | 824 | +211.26 | 61.4% | 1.74 | 15.0 | +81.77 | +129.48 | 055d72a4 |
|
||||
| W3 | $0.65 | **6** | 824 | +216.87 | 62.9% | 1.78 | 11.4 | +80.67 | +136.20 | f94f1429 |
|
||||
| W3 | $0.65 | 7 | 824 | +213.15 | 63.1% | 1.76 | 11.8 | +79.08 | +134.07 | 5ea4ab4b |
|
||||
| W3 | $0.65 | 8 | 824 | +208.01 | 62.9% | 1.74 | 12.3 | +77.18 | +130.84 | 372ac298 |
|
||||
| W3 | $0.30 | off | 824 | +221.18 | 63.6% | 1.81 | 11.4 | +86.41 | +134.77 | 96a87454 |
|
||||
| W3 | $0.30 | 5 | 824 | +228.46 | 62.1% | 1.82 | 14.0 | +92.27 | +136.18 | 7453b5f7 |
|
||||
| W3 | $0.30 | **6** | 824 | +233.37 | 63.6% | 1.86 | 11.0 | +90.67 | +142.70 | bf7e4157 |
|
||||
| W3 | $0.30 | 7 | 824 | +229.37 | 63.7% | 1.84 | 11.0 | +88.88 | +140.48 | e44cb5b5 |
|
||||
| W3 | $0.30 | 8 | 824 | +224.12 | 63.5% | 1.82 | 11.3 | +86.91 | +137.22 | c214455b |
|
||||
| W2 | $0.65 | off | 597 | +155.89 | 64.3% | 1.78 | 11.4 | +61.83 | +94.06 | dfc647ee |
|
||||
| W2 | $0.65 | 5 | 597 | +156.59 | 63.3% | 1.79 | 11.4 | +62.63 | +93.96 | 5e5c0207 |
|
||||
| W2 | $0.65 | **6** | 597 | +160.79 | 64.0% | 1.81 | 11.4 | +61.31 | +99.48 | 3614c98a |
|
||||
| W2 | $0.65 | 7 | 597 | +158.70 | 64.3% | 1.79 | 11.4 | +60.19 | +98.51 | afa7eb01 |
|
||||
| W2 | $0.65 | 8 | 597 | +156.58 | 64.2% | 1.78 | 11.4 | +60.54 | +96.04 | ba29cc41 |
|
||||
| RISING | $0.65 | off | 258 | +63.78 | 64.3% | 1.80 | 9.1 | +23.97 | +39.81 | e87b6de1 |
|
||||
| RISING | $0.65 | 5 | 258 | +57.68 | 61.6% | 1.71 | 8.0 | +22.85 | +34.83 | 295731a3 |
|
||||
| RISING | $0.65 | **6** | 258 | +58.03 | 64.0% | 1.72 | 8.6 | +23.84 | +34.19 | 8dba71d7 |
|
||||
| RISING | $0.65 | 7 | 258 | +62.18 | 64.3% | 1.78 | 8.8 | +26.44 | +35.74 | c461380f |
|
||||
| RISING | $0.65 | 8 | 258 | +62.12 | 63.6% | 1.77 | 9.1 | +24.06 | +38.07 | fce21577 |
|
||||
| LONG | $0.65 | off | 1473 | +223.33 | 59.7% | 1.42 | 41.5 | | | aa5253aa |
|
||||
| LONG | $0.65 | 5 | 1473 | +204.76 | 58.4% | 1.38 | 53.1 | | | b365f28f |
|
||||
| LONG | $0.65 | **6** | 1473 | +233.56 | 59.7% | 1.44 | 47.2 | | | 4aa12918 |
|
||||
| LONG | $0.65 | 7 | 1473 | +228.83 | 59.8% | 1.43 | 46.5 | | | 6e91e0dd |
|
||||
| LONG | $0.65 | 8 | 1473 | +223.79 | 59.7% | 1.42 | 43.2 | | | 36267a43 |
|
||||
| LONG | $0.30 | off | 1473 | +257.25 | 60.8% | 1.50 | 37.2 | | | 1835b341 |
|
||||
| LONG | $0.30 | 5 | 1473 | +241.64 | 59.8% | 1.46 | 44.9 | | | 6a346020 |
|
||||
| LONG | $0.30 | **6** | 1473 | +268.63 | 60.8% | 1.52 | 41.6 | | | 526ffa4a |
|
||||
| LONG | $0.30 | 7 | 1473 | +263.16 | 60.9% | 1.51 | 42.1 | | | 3f42a60e |
|
||||
| LONG | $0.30 | 8 | 1473 | +257.84 | 60.8% | 1.50 | 38.9 | | | ffbba3e4 |
|
||||
| HOLDOUT | $0.65 | off, 6, 7, 8 | 18 | −4.62 | 50.0% | 0.50 | 4.8 | | | fa50bcd0 |
|
||||
| HOLDOUT | $0.65 | 5 | 18 | −4.62 | 50.0% | 0.50 | 4.8 | | | 3ff32a0d |
|
||||
|
||||
(W2 and RISING at $0.30 and the HOLDOUT at $0.30 are in the same direction as the $0.65 rows:
|
||||
W2 +171.35 vs +166.22 for K = 6; RISING +64.66 vs +70.24; HOLDOUT unchanged.)
|
||||
|
||||
LONG per calendar year ($0.65), total R (trades are identical: 88 / 207 / 121 / 149 / 163 / 275 /
|
||||
341 / 129):
|
||||
|
||||
| K | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | 2026 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| off | +14.0 | +45.1 | −27.3 | +3.2 | +1.8 | +53.5 | +92.5 | +40.6 |
|
||||
| 5 | +19.0 | +27.9 | −32.4 | −1.0 | +2.5 | +63.1 | +89.2 | +36.6 |
|
||||
| 6 | +19.5 | +46.7 | −31.8 | +1.8 | +4.7 | +59.8 | +88.1 | +44.8 |
|
||||
| 7 | +16.5 | +45.4 | −32.2 | +4.3 | +1.8 | +59.2 | +91.1 | +42.7 |
|
||||
| 8 | +14.5 | +43.9 | −29.0 | +3.4 | +2.9 | +54.4 | +92.3 | +41.4 |
|
||||
|
||||
Trades the cap touches (the trade set is identical by construction; only stops, and through them
|
||||
results, move). All of them are swing (GENERIC) setups; the gold ATR setup is never capped:
|
||||
|
||||
| Window | K | Stops changed | R of those trades: control → capped |
|
||||
|---|---|---|---|
|
||||
| W3 | 5 / 6 / 7 / 8 | 281 / 168 / 89 / 44 | +66.4 → +72.5 / +49.2 → +60.9 / +25.6 → +33.6 / +12.2 → +15.1 |
|
||||
| LONG | 5 / 6 / 7 / 8 | 597 / 350 / 170 / 83 | +97.5 → +78.9 / +56.7 → +66.9 / +37.0 → +42.5 / +12.5 → +13.0 |
|
||||
| RISING | 5 / 6 / 7 / 8 | 97 / 58 / 25 / 12 | +28.1 → +22.0 / +21.0 → +15.3 / +13.1 → +11.5 / +5.7 → +4.0 |
|
||||
| HOLDOUT | 5 / 6 / 7 / 8 | 3 / 0 / 0 / 0 | −3.03 → −3.04 |
|
||||
|
||||
**Rule C, K = 6**
|
||||
|
||||
| Clause | Result | Numbers |
|
||||
|---|---|---|
|
||||
| W3 change in R ≥ +5R | PASS | +11.75 (+216.87 vs +205.12) |
|
||||
| W3 first half ≥ control − 1R | PASS | +80.67 vs +76.72 |
|
||||
| W3 second half ≥ control − 1R | PASS | +136.20 vs +128.40 |
|
||||
| W3 PF ≥ control − 0.02 | PASS | 1.777 vs 1.734 |
|
||||
| W3 max DD ≤ control + 10% | PASS | 11.37 vs 12.39 |
|
||||
| LONG total R ≥ control | PASS | +233.56 vs +223.33 (but max DD 47.19 vs 41.54) |
|
||||
| same sign at $0.30 | PASS | +12.19 (+233.37 vs +221.18) |
|
||||
| ≥ 20 trades differ | PASS | 168 stops and results changed |
|
||||
| neighbour K = 5 not negative on W3 | PASS | +6.14 |
|
||||
| **neighbour K = 5 not negative on LONG** | **FAIL** | **−18.57** |
|
||||
| neighbour K = 7 not negative on W3 | PASS | +8.04 |
|
||||
| neighbour K = 7 not negative on LONG | PASS | +5.50 |
|
||||
|
||||
**Verdict C: FAIL. Nothing ships; `wide_stop_cap_atr` stays 0.0 (research knob only).** K = 6 is a
|
||||
narrow peak, not a plateau: one step tighter loses 18.6R over the long window (2020 alone −17R),
|
||||
one step wider keeps half the gain, two steps wider keeps none. The cap also costs R in the rising
|
||||
window at every K (−1.6R to −6.1R), deepens the long-window drawdown at every K (41.5R → 43.2R to
|
||||
53.1R) and changes nothing in the unseen three weeks. This agrees with the planning-phase screen
|
||||
of 25 stop and exit variants: the stop geometry is at a plateau, and tightening it is the one
|
||||
direction that reliably loses. No second K is tried (the rule's no-rescue clause).
|
||||
|
||||
### 5.9 What shipped, and the re-pinned books
|
||||
|
||||
Shipped: `pair_signals/xauusd.py` M15 override 7, `config.xau_strategy.daily_trend_veto = "both"`
|
||||
on M15 only. Not shipped: any veto on M5 / H1 / H4; the wide-stop cap (knob kept at 0.0).
|
||||
|
||||
Reference books after the flip, factory configuration, `--spread 0.65`, regenerated with BOTH
|
||||
scripts (`gpu_backtest.py --workers 12` and `cudaBasedBacktesting.py`), `Backend: CuPy GPU
|
||||
(NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)`, `GPU cache fallbacks: 0`:
|
||||
|
||||
| Book | Window | Trades / R / PF / DD | MD5 (reference = twin) | |
|
||||
|---|---|---|---|---|
|
||||
| M5 W2 | 2024-09-05 → 2026-09-05 | 781 / +146.07 / 1.52 / 10.1 | `e599c8a8652f95b7fcdf5a7ef2b564f5` | unchanged |
|
||||
| M15 W2 | 2024-09-05 → 2026-09-05 | 597 / +155.89 / 1.78 / 11.4 | `dfc647ee02afd179dd9e031f344ed380` | re-pinned (was `5b65ecf6…`, 813 / +154.15 / 1.53) |
|
||||
| M15 OOS | 2024-06-01 → 2025-03-30 | 258 / +63.78 / 1.80 / 9.1 | `e87b6de12f90f3b5a5d41a953ee9ff7a` | re-pinned (was `09b8a8e7…`, 313 / +68.50 / 1.67) |
|
||||
| H1 W3 | 2023-09-05 → 2026-09-05 | 253 / +70.02 / 1.98 / 4.4 | `a44fd19b4067f46e5bd98d50cc3eff92` | unchanged |
|
||||
| H4 W3 | 2023-09-05 → 2026-09-05 | 114 / +21.7 / 1.59 | `f3587bae3c5dc3eda468ea3588b17dcb` | unchanged |
|
||||
| M15 W3 | 2023-09-05 → 2026-09-05 | 824 / +205.12 / 1.73 / 12.4 | `ad3b0dbda0bf77b659caea1c53be6a50` | was `70195fb0…`, 1105 / +195.79 / 1.49 |
|
||||
|
||||
The factory books are byte-identical to the `--set xau_strategy.daily_trend_veto=both` books of
|
||||
section 5.2, as they must be.
|
||||
|
||||
### 5.10 A fetch bug found on the way (fixed, not a signal change)
|
||||
|
||||
The first HOLDOUT and RISING runs with the daily frame failed with `OandaDataError: XAU/USD 1d:
|
||||
3 consecutive failures fetching window 2020-08-01–2020-08-01 … (Empty candle response)`. Since
|
||||
2026-09-13 `fetch_range` retries a failing page and aborts after three tries, while the Oanda
|
||||
provider raised on an EMPTY date window. A padded window that starts on a closed day (the daily
|
||||
pad is 1,400 days = exactly 200 weeks, so a window starting on a Saturday pads to a Saturday)
|
||||
begins with a page nothing traded in, and the whole fetch aborted. Fix in `providers/oanda.py`:
|
||||
a DATE-WINDOW request with no candles returns an empty frame (a `count` request, the live path,
|
||||
still raises); two Module 15 tests. Books are unaffected (every pinned window was already cached;
|
||||
the ten MD5s reproduce). New cache files fetched for this campaign: the daily frames
|
||||
`oanda_XAU_USD_1d_20201105_20260905`, `_20150303_20260807`, `_20200801_20250330`,
|
||||
`_20221105_20261001`, and the HOLDOUT window `5min` / `15min` / `1h` `_20260705_20261001` and
|
||||
`4h_20260114_20261001`. The two interior gaps the HOLDOUT fetch logged are market closures
|
||||
(Labor Day 2026-09-07, Good Friday 2026-04-02), not dropped pages.
|
||||
|
||||
### 5.11 Setup size: what the evidence supports, and the display lines that shipped
|
||||
|
||||
"Trade setup size" was tested two ways.
|
||||
|
||||
**The stop itself.** Candidate C (section 5.8) fails its rule, and the planning-phase screen of
|
||||
25 stop and exit variants found nothing better than the shipped geometry (a flat 2 × ATR stop on
|
||||
M15 turns +196R into +3R). The stops stay as they are.
|
||||
|
||||
**How much is at risk at once.** If every alert is taken, the plans pile up. Shipped books, W3,
|
||||
$0.65 (`M15` = veto on):
|
||||
|
||||
| Books taken together | Trades | Same-direction plans already open at an entry: mean | median | max | Entries with ≥ 1 open | with ≥ 3 open | Open at once while anything is open: mean | max | Share of the time something is open |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| M15 alone | 824 | 1.17 | 1 | 6 | 62% | 15% | 1.70 | 7 | 41% |
|
||||
| M5 alone | 870 | 0.69 | 0 | 6 | 40% | 7% | 1.36 | 7 | 23% |
|
||||
| M5 + M15 | 1694 | 1.70 | 1 | 12 | 66% | 26% | 1.97 | 13 | 51% |
|
||||
| M5 + M15 + H1 | 1947 | 2.75 | 2 | 15 | 78% | 43% | 2.60 | 16 | 65% |
|
||||
|
||||
The stacked entries are not worse trades (M5 + M15, by the number of same-direction plans already
|
||||
open at the entry: 0 → +0.154R per trade over 577; 1 → +0.218R over 410; 2 → +0.241R over 271;
|
||||
3–4 → +0.282R over 267; 5 or more → +0.236R over 169). So there is no evidence for skipping them,
|
||||
and the 2026-09-06 sizing campaign already showed that varying the stake by signal features does
|
||||
not pay. What the numbers do say is that "1% per alert" is in practice several percent on one move
|
||||
of gold. That is a sizing fact for the reader, so it ships as information, not as a gate:
|
||||
|
||||
- **Size line** (`exposure.py`, off until `config.json` `trade_size.risk_per_trade` is set): the
|
||||
number of lots that keeps the loss at the printed stop inside the owner's risk limit, rounded
|
||||
DOWN to the lot step. Private by default (owner chats and the CLI); the channel and the public
|
||||
dashboard get it only with `trade_size.telegram_channel` / `trade_size.dashboard`.
|
||||
- **Already-open line**: how many ALERTED plans in the same direction are still open in the
|
||||
advisory outcome model (`signal_log.open_alerted_setups`, a read-only query; the bot stamps
|
||||
`alerted_at` on a row once its alert reached a chat).
|
||||
- **Daily-trend line** on every surface (`main.daily_trend_words`): on M15 the veto has already
|
||||
removed a plan against a clear daily trend; on M5 / H1 / H4, where the veto failed its rule, the
|
||||
line is context ("Daily trend: falling — this buy goes against it"). The live wrapper stamps the
|
||||
trend on every logged scan row (`signals.daily_trend`), so the live record can answer the M5
|
||||
question again after a few months.
|
||||
|
||||
None of the three is read by a gate, the backtester or the twin.
|
||||
|
||||
### 5.12 Final verification (2026-10-01)
|
||||
|
||||
- `venv/bin/python test_all.py`: **607 passed, 0 failed** (40 modules: Module 39 daily-trend veto
|
||||
and wide-stop cap, Module 40 the display lines, Module 37 extended, two Module 15 tests for the
|
||||
empty-window fetch).
|
||||
- The five reference books regenerated with both scripts after the last code change: ten MD5s
|
||||
equal the table in section 5.9.
|
||||
- Recorder and feature export with the veto on, M15 2024-06-01 → 2025-03-30, `--export-features
|
||||
--record DIR --record-bars` on both scripts: feature CSV `732f396bb3ee6036b62750a8c755194e` on
|
||||
both; `trades` 258 × 137, `indicators` 5900 × 15, `events` 5098 × 14 and `bars` 12315 × 172
|
||||
identical; both manifests carry book MD5 `e87b6de12f90f3b5a5d41a953ee9ff7a` and differ only in
|
||||
`cli`, `created_at`, `replay_seconds`, `workers`; `replay_scorecard.py DIR --check`: all
|
||||
invariants held.
|
||||
- `agent_panel_base_rates.json` rebuilt from the re-pinned books (M15 timeframe row 597 trades,
|
||||
64.3% winners, +0.261R; was 813 / 62.2% / +0.190R).
|
||||
|
||||
### 5.13 Independent verification (2026-10-01)
|
||||
|
||||
An agent that had not done the work re-derived the change list from `git diff HEAD` and
|
||||
re-ran the books itself (24 on the twin, 9 on the reference script, all offline, 0 cache
|
||||
fallbacks). Every figure of sections 5.2 to 5.9 it was given matched to the cent, on both
|
||||
scripts where it ran both: the M15 books with the veto on and off (W3, W2, the 2024-06-01 →
|
||||
2025-03-30 window, LONG with its per-year R, the holdout), the unchanged M5 / H1 / H4 pins, the
|
||||
failed M5, H1, `buy` and `sell` variants, and the removed-trade tables (281 removed on W3, 0
|
||||
added, all 63 columns of the 824 kept trades identical). Its own implementation of the rule
|
||||
from closed daily bars predicts the removed trades exactly (281 / 281, 618 / 618, 55 / 55); the
|
||||
same rule fed the still-forming day mis-predicts 52, 129 and 14 of them, so there is no
|
||||
look-ahead. The scoring configuration is identical to HEAD on all four timeframes; the only
|
||||
decision-side difference is the M15 veto.
|
||||
|
||||
It also raised four points this report did not make clearly enough. They change no verdict;
|
||||
they are recorded so the size of the claim is not overstated.
|
||||
|
||||
1. **Clause (g) is thinner than "3 of 4" reads.** The "100-day average alone" neighbour of
|
||||
table 5.5 is an EXPONENTIAL average, the same kind as the shipped 50-day one (harness key
|
||||
`e100`). With a SIMPLE 100-day average the M15 W3 book is 781 / +188.68R / PF 1.70 /
|
||||
drawdown 12.27R: it beats the control on profit factor and drawdown like the others, but
|
||||
sits 1.2R under the 97% line (+189.92R), so with that reading the count would be 2 of 4.
|
||||
The four definitions were fixed before the runs in the harness's own form and the clause
|
||||
passes as written; the honest summary is "five alternative definitions, all better than the
|
||||
control on profit factor and drawdown, two of them 1.2R and 1.7R short on total R".
|
||||
2. **"The veto never adds a trade" is an observation, not a guarantee.** The rule itself only
|
||||
removes a plan. In a book, removing an entry also moves the 5-bar cooldown, which could
|
||||
free a later entry; that happened 0 times in every control / candidate pair compared (13).
|
||||
3. **It is a risk filter, not a better direction call.** Nothing in how direction is scored
|
||||
changed, and a vetoed bar still shows its buy or sell reading. The removed plans were close
|
||||
to break-even (−0.03R each on W3, −0.05R on LONG; 55% winners against 63% for the kept
|
||||
ones), so total R barely moves while profit factor and the losing runs improve.
|
||||
4. **The help is concentrated.** Effect of the rule by year on LONG (shipped minus off):
|
||||
2019 +4.9R, 2020 −3.7R, 2021 −1.8R, 2022 **+27.8R**, 2023 **−10.0R**, 2024 +0.1R, 2025
|
||||
+11.4R, 2026 +1.9R; total +30.8R, of which 2022 (buys against a falling daily trend) is
|
||||
nine tenths. On W3 the whole gain in R comes from sells removed in 2025, and the drawdown
|
||||
cut from 20.9R to 12.4R is one episode: 13 sells between 2023-12-01 and 2024-02-23
|
||||
(−8.5R). In a year where the daily trend flips often (2023) the rule costs. The multi-year
|
||||
windows had been looked at with the prototype before rule T was written (section 2), and
|
||||
the only period nobody had seen is four weeks long and differs by six trades.
|
||||
|
||||
Cross-checked here from the formal books (a filter on the control book reproduces the shipped
|
||||
book and the exponential neighbour exactly, and gives the simple-average figure above).
|
||||
|
||||
## 6. Summary of decisions
|
||||
|
||||
| Item | Verdict |
|
||||
|---|---|
|
||||
| Daily-trend veto, M15, both directions | **PASSED rule T on every clause, SHIPPED** (factory M15 override 7) |
|
||||
| Daily-trend veto, M15, buy-only / sell-only | FAILED (W3 R and PF / LONG drawdown and 2021–22) |
|
||||
| Daily-trend veto, M5 (both, buy-only) | FAILED (W3 drawdown; LONG R, drawdown, 2021–22; no plateau) |
|
||||
| Daily-trend veto, H1 | FAILED (W3 −9.7R); H4 not run |
|
||||
| Wide-stop cap K = 6 on M15 | FAILED rule C (neighbour K = 5 loses 18.6R on LONG); knob kept at 0.0, research-only |
|
||||
| Lot-size, already-open and daily-trend lines | SHIPPED, display only (books byte-identical) |
|
||||
| Oanda empty date window | FIXED (fetch_range no longer aborts on a closed-day page) |
|
||||
| Seven further context screens | no state nominated, nothing shipped (`context_screens_2026-10.md`) |
|
||||
| Eight outside data sources (dollar, silver, bonds, real yield, fund flows, futures and Oanda client positioning) | none adds a forecast to gold's own daily trend; broad dollar index on M5 = WATCH; nothing shipped, no feed added (`new_inputs_2026-10.md`) |
|
||||
@@ -0,0 +1,304 @@
|
||||
# New data sources for the direction call — a report-only study, 2026-10-01
|
||||
|
||||
Owner decision 2026-09-30: after the daily-trend work, study new data sources and report; nothing
|
||||
ships from this file. The engine reads gold's own price and nothing else. The question here is
|
||||
whether anything OUTSIDE gold's own chart tells the direction better than gold's chart already
|
||||
does. Sections 1 to 3 were written before any data was fetched.
|
||||
|
||||
## 1. Sources
|
||||
|
||||
| # | Source | What it is | Where from | When a value is usable |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Dollar | EUR/USD daily candles, read upside down (a falling EUR/USD is a rising dollar) | Oanda v20 candles, the live feed's own token | daily close + 0 (same rule as gold's daily bars) |
|
||||
| 2 | Silver | XAG/USD daily candles | Oanda v20 | daily close |
|
||||
| 3 | US 10-year note | USB10Y_USD daily candles (price up = yields down), if Oanda offers it | Oanda v20 | daily close |
|
||||
| 4 | Real yield | US 10-year inflation-indexed yield, `DFII10` | FRED (public CSV) | the day's value from two days later (it is published the next US afternoon) |
|
||||
| 5 | Broad dollar index | `DTWEXBGS` | FRED | same two-day rule |
|
||||
| 6 | Gold fund holdings | SPDR Gold Shares, shares outstanding (each share is a fixed slice of the vault, so creations and redemptions are the fund's gold flow) | the fund manager's public history file (`navhist-us-en-gld.xlsx`); the older tonnes archive now serves a PDF | the day's value from two days later |
|
||||
| 7 | Futures positioning | CFTC disaggregated report, gold (code 088691), managed-money net long | CFTC public reporting site | a Tuesday report from the following Monday |
|
||||
| 8 | Oanda client positioning | position book for XAU_USD: the share of Oanda clients' open gold positions that are long (by count) | Oanda v20 `/v3/instruments/XAU_USD/positionBook`, one snapshot per day at 00:00 UTC | at once (the snapshot is taken at the daily close) |
|
||||
|
||||
Everything fetched stays in the session scratchpad. Nothing is added to the repository's cache.
|
||||
|
||||
Amendment before any analysis (2026-10-01, after the access probes and nothing else): the plan
|
||||
expected source 8 to be gone. It is not: on the practice API the position book answers for every
|
||||
date probed back to 2017, so it joins the study with the definition below. The probe printed ten
|
||||
long-share readings (between 60% and 75% long, higher at higher gold prices) and no forward
|
||||
returns; that is everything that was seen before the definition was fixed. Source 6 uses shares
|
||||
outstanding instead of tonnes because that is the file that can still be downloaded.
|
||||
|
||||
## 2. One state per source
|
||||
|
||||
The same shape as the shipped daily trend, so the comparison is like for like. For a daily series
|
||||
`x` (Sunday stub bars dropped for the Oanda candles): `up` when `x` is above its 50-day EMA AND
|
||||
above its value 60 days earlier, `down` when below both, else `unclear`. Read for gold:
|
||||
|
||||
| Source | Gold-friendly state | Gold-hostile state |
|
||||
|---|---|---|
|
||||
| Dollar (EUR/USD) | EUR/USD up (dollar falling) | EUR/USD down |
|
||||
| Silver | up | down |
|
||||
| 10-year note price | up (yields falling) | down |
|
||||
| Real yield | down | up |
|
||||
| Broad dollar index | down | up |
|
||||
| Fund holdings | shares outstanding above the level 20 trading days earlier | below |
|
||||
| Futures positioning | managed-money net long above its level 4 reports earlier | below |
|
||||
| Oanda client positioning | long share `down` (the crowd is cutting longs or adding shorts) | long share `up` (the crowd is adding longs) |
|
||||
|
||||
The positioning state is read the contrarian way on purpose and before looking: small traders
|
||||
tend to buy dips and sell rallies, so a growing long share is expected to go with a falling price.
|
||||
Test A is two-sided, so if the truth is the other way round it will show as a real spread with the
|
||||
opposite sign and is reported as such; Test B is judged on the declared reading only.
|
||||
|
||||
## 3. Two tests and the verdict rule
|
||||
|
||||
**Test A — forward move of gold.** Every closed gold daily bar from 2019-01-01 to 2026-08-07.
|
||||
Forward move = gold's close 5 days later minus today's close, in units of gold's own 14-day ATR
|
||||
(and the same for 20 days). For each source: mean forward move on gold-friendly days minus mean
|
||||
on gold-hostile days, (i) over all days and (ii) inside each of gold's own three daily-trend
|
||||
states (rising / falling / unclear), in two eras (2019–2023, 2024–2026). The spread is called real
|
||||
only if it has the same sign in both eras and its t-value, computed on every fifth day so the
|
||||
5-day windows do not overlap, is at least 2 in each era.
|
||||
|
||||
**Test B — the shipped books.** M15 LONG (daily-trend veto on, 1473 trades) and M5 LONG (1052
|
||||
trades), $0.65. Each trade is labelled `friendly` / `hostile` / `unclear` from the source state at
|
||||
its decision time, mirrored for a SELL. A source is interesting on a timeframe only if its
|
||||
`hostile` trades are at least 8% of the book and lose (total R < 0) in BOTH eras.
|
||||
|
||||
**Verdict per source.**
|
||||
- `GO` (worth a pre-registered campaign and the feed work it needs) only if Test A calls the
|
||||
spread real INSIDE gold's own trend states (that is the "beyond gold's own chart" part) AND
|
||||
Test B finds the hostile trades losing in both eras on at least one timeframe.
|
||||
- `WATCH` if exactly one of the two holds.
|
||||
- `NO-GO` otherwise, or when the source cannot be fetched reliably.
|
||||
A `GO` is a ticket to a campaign, never a shipping decision: a live feed for a second instrument
|
||||
needs its own provider path, cache, staleness rule and live == backtest proof.
|
||||
|
||||
Two points of method were fixed while the analysis script was written, before it produced any
|
||||
output. (1) The broad dollar index is published weekly (the H.10 release, on Monday, for the week
|
||||
before), not daily, so the "two-day rule" of the table would have looked ahead: each week's final
|
||||
state is used from the Tuesday after that week. (2) "Inside gold's own trend states" is computed
|
||||
as one stratified number per era: the forward move minus the mean forward move of that day's gold
|
||||
trend state (rising / unclear / falling), friendly days against hostile days, t-value on every
|
||||
fifth day. That is the column the verdict reads; the unstratified spread is printed beside it.
|
||||
|
||||
## 4. Results
|
||||
|
||||
Run 2026-10-01. Fetched into the session scratchpad: Oanda daily candles for EUR_USD, XAG_USD,
|
||||
USB10Y_USD and XAU_USD (2015-01-01 → 2026-09-30, about 3,650 bars each, about 600 Sunday stubs
|
||||
dropped); the Oanda XAU_USD position book, one snapshot per day at 00:00 UTC from 2018-06-01
|
||||
(2,609 days asked, 97.7% answered at exactly 00:00, the rest from the last snapshot up to six
|
||||
hours earlier, 45 days with none, all before mid-2022); FRED `DFII10` and `DTWEXBGS`; the gold
|
||||
fund's history file (5,540 days since 2004); 1,059 weekly CFTC reports since 2006.
|
||||
|
||||
### 4.1 What was fetched
|
||||
|
||||
| Source | Days with a state, 2019-01-01 → 2026-08-07 | friendly / unclear / hostile | Same side as gold's own daily trend (days where both are clear) |
|
||||
|---|---|---|---|
|
||||
| 1 dollar (EUR/USD) | 1973 | 37% / 18% / 44% | 67% of 1363 |
|
||||
| 2 silver | 1973 | 49% / 21% / 31% | 88% of 1387 |
|
||||
| 3 10-year note price | 1973 | 39% / 16% / 46% | 73% of 1398 |
|
||||
| 4 real yield (DFII10) | 1973 | 42% / 20% / 39% | 73% of 1353 |
|
||||
| 5 broad dollar index | 1973 | 39% / 19% / 42% | 62% of 1362 |
|
||||
| 6 gold fund shares | 1973 | 51% / 0% / 49% | 77% of 1639 |
|
||||
| 7 futures positioning | 1973 | 51% / 0% / 49% | 59% of 1644 |
|
||||
| 8 Oanda client positioning | 1973 | 37% / 15% / 48% | 36% of 1414 |
|
||||
|
||||
Gold itself, same days: rising 58%, unclear 17%, falling 25%; mean 5-day forward move +0.27 ATR (rising +0.32, unclear +0.30, falling +0.14).
|
||||
|
||||
### 4.2 Test A — gold's forward move, friendly days minus hostile days (in daily ATRs)
|
||||
|
||||
| Source | Era | Days friendly / hostile | 5-day spread, all days (t) | 5-day spread inside gold's trend states (t) | 20-day spread inside gold's trend states (t) | Real? |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 dollar (EUR/USD) | 2019–2023 | 443 / 623 | -0.04 (-0.3) | -0.05 (-0.4) | +0.35 (+0.7) | |
|
||||
| 1 dollar (EUR/USD) | 2024–2026 | 296 / 249 | +0.21 (+0.7) | +0.20 (+0.7) | +0.80 (+0.5) | |
|
||||
| 1 dollar (EUR/USD) | verdict | | | same sign in both eras: no; |t| ≥ 2 in both: no | | not real |
|
||||
| 2 silver | 2019–2023 | 548 / 449 | -0.21 (-0.9) | -0.25 (-1.0) | -1.00 (-1.2) | |
|
||||
| 2 silver | 2024–2026 | 411 / 160 | -0.10 (-0.7) | -0.20 (-0.7) | -0.64 (-0.2) | |
|
||||
| 2 silver | verdict | | | same sign in both eras: yes; |t| ≥ 2 in both: no | | not real |
|
||||
| 3 10-year note price | 2019–2023 | 515 / 603 | +0.08 (+0.3) | +0.04 (+0.1) | +0.46 (+0.5) | |
|
||||
| 3 10-year note price | 2024–2026 | 253 / 295 | +0.23 (+0.4) | +0.17 (+0.4) | +1.02 (-0.2) | |
|
||||
| 3 10-year note price | verdict | | | same sign in both eras: yes; |t| ≥ 2 in both: no | | not real |
|
||||
| 4 real yield (DFII10) | 2019–2023 | 583 / 468 | +0.04 (+0.4) | +0.01 (+0.2) | -0.20 (-0.5) | |
|
||||
| 4 real yield (DFII10) | 2024–2026 | 242 / 294 | +0.20 (+0.7) | +0.14 (+0.6) | +1.22 (+0.5) | |
|
||||
| 4 real yield (DFII10) | verdict | | | same sign in both eras: yes; |t| ≥ 2 in both: no | | not real |
|
||||
| 5 broad dollar index | 2019–2023 | 497 / 536 | +0.03 (-0.1) | +0.02 (-0.1) | -0.12 (-0.5) | |
|
||||
| 5 broad dollar index | 2024–2026 | 269 / 293 | +0.02 (-0.2) | +0.04 (-0.0) | +0.71 (+0.1) | |
|
||||
| 5 broad dollar index | verdict | | | same sign in both eras: yes; |t| ≥ 2 in both: no | | not real |
|
||||
| 6 gold fund shares | 2019–2023 | 609 / 681 | +0.20 (+1.4) | +0.17 (+1.2) | +0.37 (+1.0) | |
|
||||
| 6 gold fund shares | 2024–2026 | 393 / 281 | +0.40 (+1.7) | +0.36 (+1.8) | +1.09 (-0.1) | |
|
||||
| 6 gold fund shares | verdict | | | same sign in both eras: yes; |t| ≥ 2 in both: no | | not real |
|
||||
| 7 futures positioning | 2019–2023 | 640 / 656 | +0.06 (+0.0) | +0.04 (-0.1) | +1.03 (+0.6) | |
|
||||
| 7 futures positioning | 2024–2026 | 369 / 308 | -0.01 (+0.3) | +0.11 (+0.8) | +1.23 (+1.7) | |
|
||||
| 7 futures positioning | verdict | | | same sign in both eras: yes; |t| ≥ 2 in both: no | | not real |
|
||||
| 8 Oanda client positioning | 2019–2023 | 636 / 428 | +0.20 (+1.2) | +0.19 (+1.1) | +0.53 (+0.1) | |
|
||||
| 8 Oanda client positioning | 2024–2026 | 97 / 516 | -0.01 (-0.6) | +0.33 (+0.3) | n/a (too few friendly days) | |
|
||||
| 8 Oanda client positioning | verdict | | | same sign in both eras: yes; |t| ≥ 2 in both: no | | not real |
|
||||
|
||||
### 4.3 Test B — the shipped books, by the source's state at the trade's decision time
|
||||
|
||||
|
||||
**M15 LONG (daily-trend veto on): 1473 trades, +223.3R** (era A = 2019–2023: 728 trades, +36.8R; era B = 2024–2026: 745 trades, +186.6R)
|
||||
|
||||
| Source | Trade state | Trades (share) | Total R / mean | Era A R (trades) | Era B R (trades) | Hostile ≥ 8% and losing in both eras |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 dollar (EUR/USD) | friendly | 725 (49%) | +127.3 / +0.176 | +32.0 (345) | +95.4 (380) | |
|
||||
| 1 dollar (EUR/USD) | unclear | 252 (17%) | +13.0 / +0.052 | -15.3 (127) | +28.3 (125) | |
|
||||
| 1 dollar (EUR/USD) | hostile | 496 (34%) | +83.0 / +0.167 | +20.1 (256) | +62.9 (240) | no |
|
||||
| 2 silver | friendly | 937 (64%) | +154.9 / +0.165 | +26.8 (406) | +128.0 (531) | |
|
||||
| 2 silver | unclear | 304 (21%) | +1.3 / +0.004 | -18.2 (196) | +19.5 (108) | |
|
||||
| 2 silver | hostile | 232 (16%) | +67.1 / +0.289 | +28.1 (126) | +39.0 (106) | no |
|
||||
| 3 10-year note price | friendly | 753 (51%) | +107.0 / +0.142 | +34.6 (397) | +72.4 (356) | |
|
||||
| 3 10-year note price | unclear | 254 (17%) | +39.1 / +0.154 | +11.3 (121) | +27.8 (133) | |
|
||||
| 3 10-year note price | hostile | 466 (32%) | +77.2 / +0.166 | -9.2 (210) | +86.4 (256) | no |
|
||||
| 4 real yield (DFII10) | friendly | 723 (49%) | +97.7 / +0.135 | +45.5 (405) | +52.2 (318) | |
|
||||
| 4 real yield (DFII10) | unclear | 313 (21%) | +30.8 / +0.098 | -12.5 (142) | +43.2 (171) | |
|
||||
| 4 real yield (DFII10) | hostile | 437 (30%) | +94.9 / +0.217 | +3.8 (181) | +91.1 (256) | no |
|
||||
| 5 broad dollar index | friendly | 647 (44%) | +122.5 / +0.189 | +37.5 (315) | +85.0 (332) | |
|
||||
| 5 broad dollar index | unclear | 277 (19%) | +17.2 / +0.062 | -4.7 (168) | +21.8 (109) | |
|
||||
| 5 broad dollar index | hostile | 549 (37%) | +83.7 / +0.152 | +3.9 (245) | +79.8 (304) | no |
|
||||
| 6 gold fund shares | friendly | 1031 (70%) | +183.6 / +0.178 | +52.7 (519) | +130.9 (512) | |
|
||||
| 6 gold fund shares | unclear | 7 (0%) | -4.7 / -0.668 | -2.9 (4) | -1.8 (3) | |
|
||||
| 6 gold fund shares | hostile | 435 (30%) | +44.4 / +0.102 | -13.1 (205) | +57.5 (230) | no |
|
||||
| 7 futures positioning | friendly | 789 (54%) | +115.5 / +0.146 | +22.3 (422) | +93.3 (367) | |
|
||||
| 7 futures positioning | unclear | 0 (0%) | +0.0 / | +0.0 (0) | +0.0 (0) | |
|
||||
| 7 futures positioning | hostile | 684 (46%) | +107.8 / +0.158 | +14.5 (306) | +93.3 (378) | no |
|
||||
| 8 Oanda client positioning | friendly | 394 (27%) | +41.7 / +0.106 | +15.9 (326) | +25.9 (68) | |
|
||||
| 8 Oanda client positioning | unclear | 198 (13%) | +26.9 / +0.136 | +13.7 (122) | +13.2 (76) | |
|
||||
| 8 Oanda client positioning | hostile | 881 (60%) | +154.7 / +0.176 | +7.2 (280) | +147.5 (601) | no |
|
||||
|
||||
**M5 LONG: 1052 trades, +128.1R** (era A = 2019–2023: 258 trades, -10.4R; era B = 2024–2026: 794 trades, +138.5R)
|
||||
|
||||
| Source | Trade state | Trades (share) | Total R / mean | Era A R (trades) | Era B R (trades) | Hostile ≥ 8% and losing in both eras |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 dollar (EUR/USD) | friendly | 439 (42%) | +85.3 / +0.194 | -11.5 (103) | +96.8 (336) | |
|
||||
| 1 dollar (EUR/USD) | unclear | 211 (20%) | +28.6 / +0.136 | -0.9 (34) | +29.5 (177) | |
|
||||
| 1 dollar (EUR/USD) | hostile | 402 (38%) | +14.2 / +0.035 | +2.0 (121) | +12.2 (281) | no |
|
||||
| 2 silver | friendly | 556 (53%) | +89.3 / +0.161 | -7.6 (113) | +96.8 (443) | |
|
||||
| 2 silver | unclear | 143 (14%) | +7.3 / +0.051 | -9.9 (45) | +17.2 (98) | |
|
||||
| 2 silver | hostile | 353 (34%) | +31.5 / +0.089 | +7.1 (100) | +24.4 (253) | no |
|
||||
| 3 10-year note price | friendly | 432 (41%) | +51.6 / +0.120 | -0.6 (119) | +52.2 (313) | |
|
||||
| 3 10-year note price | unclear | 169 (16%) | -9.5 / -0.056 | -13.7 (49) | +4.2 (120) | |
|
||||
| 3 10-year note price | hostile | 451 (43%) | +86.0 / +0.191 | +3.9 (90) | +82.1 (361) | no |
|
||||
| 4 real yield (DFII10) | friendly | 377 (36%) | +4.2 / +0.011 | -15.0 (115) | +19.2 (262) | |
|
||||
| 4 real yield (DFII10) | unclear | 186 (18%) | +32.3 / +0.174 | -7.0 (45) | +39.3 (141) | |
|
||||
| 4 real yield (DFII10) | hostile | 489 (46%) | +91.6 / +0.187 | +11.6 (98) | +80.0 (391) | no |
|
||||
| 5 broad dollar index | friendly | 447 (42%) | +131.2 / +0.294 | -0.7 (91) | +131.9 (356) | |
|
||||
| 5 broad dollar index | unclear | 170 (16%) | +8.1 / +0.047 | -6.0 (40) | +14.1 (130) | |
|
||||
| 5 broad dollar index | hostile | 435 (41%) | -11.2 / -0.026 | -3.7 (127) | -7.5 (308) | YES |
|
||||
| 6 gold fund shares | friendly | 600 (57%) | +86.9 / +0.145 | -15.8 (142) | +102.7 (458) | |
|
||||
| 6 gold fund shares | unclear | 3 (0%) | -1.8 / -0.608 | +0.0 (0) | -1.8 (3) | |
|
||||
| 6 gold fund shares | hostile | 449 (43%) | +43.0 / +0.096 | +5.4 (116) | +37.6 (333) | no |
|
||||
| 7 futures positioning | friendly | 493 (47%) | +41.1 / +0.083 | -17.0 (120) | +58.1 (373) | |
|
||||
| 7 futures positioning | unclear | 0 (0%) | +0.0 / | +0.0 (0) | +0.0 (0) | |
|
||||
| 7 futures positioning | hostile | 559 (53%) | +87.0 / +0.156 | +6.6 (138) | +80.4 (421) | no |
|
||||
| 8 Oanda client positioning | friendly | 313 (30%) | +17.9 / +0.057 | -1.9 (99) | +19.8 (214) | |
|
||||
| 8 Oanda client positioning | unclear | 92 (9%) | +2.3 / +0.025 | +0.5 (40) | +1.9 (52) | |
|
||||
| 8 Oanda client positioning | hostile | 647 (62%) | +107.9 / +0.167 | -8.9 (119) | +116.9 (528) | no |
|
||||
|
||||
### 4.4 Verdict per source
|
||||
|
||||
| Source | Test A: real spread beyond gold's own trend | Test B: hostile trades ≥ 8% and losing in both eras (M15 / M5) | Verdict |
|
||||
|---|---|---|---|
|
||||
| 1 dollar (EUR/USD) | no | no / no | NO-GO |
|
||||
| 2 silver | no | no / no | NO-GO |
|
||||
| 3 10-year note price | no | no / no | NO-GO |
|
||||
| 4 real yield (DFII10) | no | no / no | NO-GO |
|
||||
| 5 broad dollar index | no | no / yes | WATCH |
|
||||
| 6 gold fund shares | no | no / no | NO-GO |
|
||||
| 7 futures positioning | no | no / no | NO-GO |
|
||||
| 8 Oanda client positioning | no | no / no | NO-GO |
|
||||
|
||||
### 4.5 The one flag, looked at more closely (after the fact: description, not a test)
|
||||
|
||||
**M5 LONG: gold's own daily trend (rows) x 5 broad dollar index (columns)** — total R (trades)
|
||||
|
||||
| Gold's own daily trend vs the trade | dollar friendly | dollar unclear | dollar hostile |
|
||||
|---|---|---|---|
|
||||
| with | +118.8 (352) | +1.9 (105) | -0.1 (215) |
|
||||
| unclear | +6.8 (33) | -2.1 (12) | +2.3 (73) |
|
||||
| against | +5.6 (62) | +8.3 (53) | -13.3 (147) |
|
||||
|
||||
**M5 LONG: gold's own daily trend (rows) x 1 dollar (EUR/USD) (columns)** — total R (trades)
|
||||
|
||||
| Gold's own daily trend vs the trade | dollar friendly | dollar unclear | dollar hostile |
|
||||
|---|---|---|---|
|
||||
| with | +76.8 (372) | +23.0 (109) | +20.7 (191) |
|
||||
| unclear | +4.5 (37) | -5.5 (19) | +7.9 (62) |
|
||||
| against | +4.0 (30) | +11.1 (83) | -14.4 (149) |
|
||||
|
||||
**M5 LONG: broad-dollar hostile trades by year** — 2019: +1.4 (14); 2020: -3.4 (45); 2021: -0.6 (21); 2022: -1.1 (36); 2023: -0.1 (11); 2024: +3.5 (68); 2025: -8.5 (107); 2026: -2.4 (133)
|
||||
**M5 LONG: broad-dollar friendly trades by year** — 2019: -1.5 (3); 2020: +0.0 (45); 2021: -0.0 (9); 2022: +0.0 (7); 2023: +0.7 (27); 2024: -0.1 (19); 2025: +40.3 (160); 2026: +91.7 (177)
|
||||
W3 friendly: 397 trades (46%), +145.1R, halves +8.2 / +136.9
|
||||
W3 unclear: 150 trades (17%), +15.6R, halves -3.0 / +18.7
|
||||
W3 hostile: 323 trades (37%), -7.1R, halves +11.6 / -18.7
|
||||
|
||||
**M15 LONG (veto on): gold's own daily trend (rows) x 5 broad dollar index (columns)** — total R (trades)
|
||||
|
||||
| Gold's own daily trend vs the trade | dollar friendly | dollar unclear | dollar hostile |
|
||||
|---|---|---|---|
|
||||
| with | +93.2 (561) | +48.0 (196) | +68.5 (416) |
|
||||
| unclear | +29.3 (86) | -30.8 (81) | +15.2 (133) |
|
||||
| against | +0.0 (0) | +0.0 (0) | +0.0 (0) |
|
||||
|
||||
**M15 LONG (veto on): gold's own daily trend (rows) x 1 dollar (EUR/USD) (columns)** — total R (trades)
|
||||
|
||||
| Gold's own daily trend vs the trade | dollar friendly | dollar unclear | dollar hostile |
|
||||
|---|---|---|---|
|
||||
| with | +99.4 (635) | +41.8 (176) | +68.5 (362) |
|
||||
| unclear | +27.9 (90) | -28.8 (76) | +14.5 (134) |
|
||||
| against | +0.0 (0) | +0.0 (0) | +0.0 (0) |
|
||||
|
||||
**M15 LONG (veto on): broad-dollar hostile trades by year** — 2019: +12.5 (57); 2020: +11.5 (74); 2021: -6.7 (44); 2022: -9.2 (43); 2023: -4.1 (27); 2024: +65.1 (179); 2025: +9.9 (95); 2026: +4.8 (30)
|
||||
**M15 LONG (veto on): broad-dollar friendly trades by year** — 2019: +4.7 (16); 2020: +25.0 (114); 2021: -8.9 (46); 2022: +8.2 (51); 2023: +8.5 (88); 2024: -4.4 (56); 2025: +52.1 (192); 2026: +37.3 (84)
|
||||
W3 friendly: 373 trades (45%), +103.5R, halves +4.0 / +99.4
|
||||
W3 unclear: 130 trades (16%), +28.2R, halves +6.2 / +22.1
|
||||
W3 hostile: 321 trades (39%), +73.4R, halves +66.5 / +6.9
|
||||
|
||||
### 4.6 Reading
|
||||
|
||||
- **Nothing outside gold's own chart passes Test A.** For all eight sources the 5-day forward move
|
||||
of gold on "friendly" days is not reliably different from "hostile" days once gold's own daily
|
||||
trend is accounted for (largest t-values 1.2 and 1.8, the gold fund's share count; everything
|
||||
else is under 1.2 in at least one era). The familiar stories (a falling dollar, falling real
|
||||
yields, rising silver, fund inflows) describe gold's past move well: they sit on the same side
|
||||
as gold's own daily trend on 59% to 88% of clear days. They do not add a forecast to it.
|
||||
- **Oanda client positioning is available but not useful in this form.** The plan expected the
|
||||
endpoint to be gone; it answers, with history back to 2017. Read the contrarian way (a growing
|
||||
long share is hostile), the spread has the expected sign and no weight (t 1.1 and 0.3), and in
|
||||
2024–2026 the crowd was adding longs on 516 of 613 clear days while gold rose. The trades taken
|
||||
on those "hostile" days are the best part of both books. Only the daily state was tested; the
|
||||
intraday snapshots (one per hour) were not.
|
||||
- **The one flag is the broad dollar index on M5** (`WATCH`): the 435 M5 trades taken against the
|
||||
dollar's trend lost 11.2R in total and lost in both eras, while the 447 taken with it made
|
||||
+131.2R. The closer look shows what that is. All of the M5 profit sits in one cell: trades with
|
||||
gold's own daily trend AND with the dollar's trend (+118.8R over 352 trades); the same trades
|
||||
against the dollar's trend made nothing (−0.1R over 215). But every R of the friendly side was
|
||||
earned in 2025 and 2026 (+40.3R and +91.7R), that is inside the one long rally from 2,600 to the
|
||||
March 2026 peak (in 2025 the broad dollar fell 7.5% while gold rose 65%); before 2025 the
|
||||
dollar's state separates nothing on M5 because the M5 book earned nothing at all. In the 3-year
|
||||
window the hostile trades are positive in the first half (+11.6R) and negative only in the
|
||||
second (−18.7R). On M15 the same state separates nothing: the hostile
|
||||
trades made +65R in 2024 alone, a year when gold and the dollar rose together. The timelier
|
||||
dollar measure (EUR/USD) shows the same lean on M5 without losing in both eras. So this is one
|
||||
market phase seen from another angle, not a second source of information; it stays a
|
||||
hypothesis, and it is the same picture as the context screens: M5 pays only when everything
|
||||
agrees and has paid only since 2025.
|
||||
|
||||
### 4.7 Verdict
|
||||
|
||||
| Source | Verdict | Why |
|
||||
|---|---|---|
|
||||
| Dollar via EUR/USD | NO-GO | no forward spread; hostile trades are not losers in both eras |
|
||||
| Silver | NO-GO | 88% the same information as gold's own trend; the spread has the wrong sign |
|
||||
| US 10-year note | NO-GO | no forward spread |
|
||||
| Real yield | NO-GO | no forward spread; on M5 the "hostile" trades are the better ones |
|
||||
| Broad dollar index | WATCH | M5 trades against it lose in both eras, but the forward test finds nothing and the effect is the 2025–2026 phase; weekly data, four to ten days old when used |
|
||||
| Gold fund share count | NO-GO | the nearest miss in the forward test (t 1.2 and 1.8), nothing on the books |
|
||||
| Futures positioning | NO-GO | no forward spread, no difference on the books |
|
||||
| Oanda client positioning | NO-GO in this daily form | available, but the contrarian reading failed in the rising years |
|
||||
|
||||
**Nothing ships and no feed is added.** The direction call was improved by the one input that
|
||||
passed a formal test, gold's own daily chart on M15. If the M5 question is taken up again, the
|
||||
honest test is forward: `signals.daily_trend` is now logged on every scan, so a few months of live
|
||||
M5 rows can be split by the daily trend, and by the dollar's trend after the fact, without touching
|
||||
the signal code.
|
||||
Reference in New Issue
Block a user