audit: NY-anchored weekend calendar (all 3 copies) + FB2_EMA_LO crash-proofing

Owner decisions from the 2026-08-04 audit report:
- spread-in-exit-triggers: DECLINED (books stay comparable; ledger in
  CLAUDE.md records the measured ~4-6% optimism).
- weekend clock: FIXED. The market week is Fri 17:00 -> Sun 18:00 New
  York time, not Fri/Sun 22:00 UTC — during US DST the Oanda feed stops
  at 21:00 UTC (measured: 159/162 summer weekends). All three weekend
  implementations (providers/base, signal_aggregator, gpu_backtest
  calendar filter) now share the NY-anchored rule; a new test sweeps
  them hourly across both DST transitions and asserts agreement. No NTP
  needed — the host clock was fine, the constant was wrong.
  Ends the weekly false-"STALE DATA / feed lagging" hours (summer Fri
  21-22 UTC, winter Sun 22-23 UTC).
- FB2_EMA_LO (delegated): kept as a research knob but crash-proofed —
  an unparseable value degrades to the tuned default 0.5 with a warning
  instead of killing every scan. Documented in CLAUDE.md.

Book gates: M15 12mo pinned book byte-identical (+78.7111R, 397
trades). M5 12mo drops exactly ONE trade (556 -> 555, +97.56 ->
+98.64R): a phantom whose signal fired on the final summer-Friday bar
(decision 21:00 UTC = NY 17:00 market close) and was "filled" at
Sunday's reopen 49 hours later, -1.08R — an entry live trading can
never take. The backtest no longer contains it; live == backtest
parity improved. M15 unaffected because hour 21 is in its blocked set.

Suite: 341/341 green.

Claude-Session: https://claude.ai/code/session_01UnZkzeFcTu9agFg8BDh2tn
This commit is contained in:
Harwinder Sekhon
2026-08-04 20:21:34 -04:00
parent 5b3f47f3e1
commit 7dbcbc6c82
5 changed files with 129 additions and 28 deletions
+13 -6
View File
@@ -40,10 +40,13 @@ from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from zoneinfo import ZoneInfo
import numpy as np
import pandas as pd
_NY_TZ = ZoneInfo("America/New_York")
# Ensure project root on sys.path so this script can be invoked from anywhere.
PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(PROJECT_ROOT) not in sys.path:
@@ -1450,18 +1453,22 @@ class BacktestConfig:
def _is_xau_blocked_by_calendar(ts: pd.Timestamp, cfg: PlatformConfig) -> Optional[str]:
"""Apply XAU weekend + allowed-weekday filter matching live `analyze_pair`.
Forex market closed: Friday 22:00 UTC -> Sunday 22:00 UTC (Sydney open).
Must match `_is_forex_weekend` in signal_aggregator.py exactly.
Market closed Friday 17:00 -> Sunday 18:00 NEW YORK time (= Fri
21:00 UTC in US summer, 22:00 in winter — matches measured Oanda
feed behavior; 2026-08-04 audit I43). Must match `_is_forex_weekend`
in signal_aggregator.py exactly (rule 4; Module 21 agreement test).
Note the weekday gates below stay on UTC weekday — they mirror the
live gates, which evaluate UTC weekdays.
"""
xau = cfg.xau_strategy
if xau.weekend_filter:
wd = ts.weekday()
hour = ts.hour
if wd == 4 and hour >= 22:
ny = ts.astimezone(_NY_TZ)
wd = ny.weekday()
if wd == 4 and ny.hour >= 17:
return "WEEKEND_FILTER"
if wd == 5:
return "WEEKEND_FILTER"
if wd == 6 and hour < 22:
if wd == 6 and ny.hour < 18:
return "WEEKEND_FILTER"
if xau.allowed_weekdays and ts.weekday() not in xau.allowed_weekdays:
return "DAY_FILTER"