audit: scheduler cleanup, WS-log secret hygiene, simulator test coverage, CLAUDE.md sync (Batch G)

2026-08-04 Fable 5 QA audit, final code batch.

- Boundary math: removed the provably-dead `remaining <= buffer` guard
  from all three scheduler copies (518,400-instant frozen-clock sweep:
  never fired) and documented the exact-boundary-instant semantics.
- websockets/websockets.client loggers pinned to INFO so a DEBUG run
  can never write the TD key (connect URI) or Tiingo auth frame into
  the 30-day rotating log. (.env also chmod 600, untracked.)
- Cooldown pass factored into _apply_cooldown (pure, testable) — the
  only sequential replay rule finally has direct tests.
- New simulator behavior tests (previously ZERO coverage of the trade
  resolver): same-bar SL+TP tie → SL-first, gap-through-SL fills at
  the open, TIMEOUT pricing + round-trip spread charging, trailing
  ratchet arms at bar close and fills next-bar-only, cooldown ordering.
- CLAUDE.md synced to verified reality: 338 tests/21 modules; three
  weekend-implementation copies caveat; throttle claim corrected
  (single-flight, TD-only throttle); daemon trail/cooldown parity
  caveats (once-per-OBSERVED-bar, wall-clock cooldown) + OPEN_ONLY and
  reconcile notes; CP-path gate semantics documented as validated
  behavior; new "known open items" section for the owner-decision
  findings (weekend DST constant, spread-in-trigger rule-6 item, H4
  seeding divergence, FB2_EMA_LO).

Gates: suite 338/338 green; pinned M15 book and M5 HEAD baseline both
reproduced identically after the cooldown refactor.

Claude-Session: https://claude.ai/code/session_01UnZkzeFcTu9agFg8BDh2tn
This commit is contained in:
Harwinder Sekhon
2026-08-04 20:08:37 -04:00
parent 99eb268cf3
commit 5b3f47f3e1
6 changed files with 137 additions and 23 deletions
+81
View File
@@ -6316,6 +6316,87 @@ def _():
_aio21.run(main())
def _m21_sim_trade(direction="BUY", entry=2400.0, sl=2390.0, tp1=2410.0,
ts="2026-01-05 10:00"):
import gpu_backtest as _gb
return _gb.Trade(pair="XAU/USD", entry_time=pd.Timestamp(ts, tz="UTC"),
entry_price=entry, sl=sl, tp1=tp1, tp2=tp1 + 10,
direction=direction, pip_value=0.1, risk_pips=100.0,
reward_pips=100.0, rr_planned=1.0, quality="B",
final_score=30.0, final_confidence=60.0,
market_regime="trending_up")
def _m21_sim_df(bars, ts="2026-01-05 10:00"):
idx = pd.date_range(ts, periods=len(bars), freq="15min", tz="UTC")
return pd.DataFrame(
{"Open": [b[0] for b in bars], "High": [b[1] for b in bars],
"Low": [b[2] for b in bars], "Close": [b[3] for b in bars],
"Volume": 1.0}, index=idx)
@test("backtester: same-bar SL+TP tie resolves to SL (static path)")
def _():
import gpu_backtest as _gb
t = _m21_sim_trade()
sim = _m21_sim_df([(2400, 2411, 2389, 2400)]) # both barriers inside bar 0
_gb.simulate_trades([t], sim, max_bars=5, spread=0.0, trail_arm_r=0.0)
assert t.exit_reason == "SL", f"tie must be SL-first, got {t.exit_reason}"
assert abs(t.exit_price - 2390.0) < 1e-9
assert abs(t.r_multiple - (-1.0)) < 1e-9
@test("backtester: gap through SL fills at the open, not the stop")
def _():
import gpu_backtest as _gb
t = _m21_sim_trade()
sim = _m21_sim_df([(2385, 2388, 2384, 2386)]) # opens below the 2390 stop
_gb.simulate_trades([t], sim, max_bars=5, spread=0.0, trail_arm_r=0.0)
assert t.exit_reason == "SL"
assert abs(t.exit_price - 2385.0) < 1e-9, "gap must fill at the open"
assert t.r_multiple < -1.0, "gap loss must exceed 1R"
@test("backtester: TIMEOUT closes at the last window bar's close, spread charged")
def _():
import gpu_backtest as _gb
t = _m21_sim_trade()
sim = _m21_sim_df([(2400, 2405, 2395, 2402)] * 3) # never hits either side
_gb.simulate_trades([t], sim, max_bars=3, spread=0.30, trail_arm_r=0.0)
assert t.exit_reason == "TIMEOUT" and t.bars_held == 3
assert abs(t.exit_price - 2402.0) < 1e-9
expected_r = (2402.0 - 2400.0 - 0.30) / 10.0
assert abs(t.r_multiple - expected_r) < 1e-9, \
"R must be (pnl - round-trip spread) / risk"
@test("backtester: trailing stop ratchets at bar close, fills next bar only")
def _():
import gpu_backtest as _gb
t = _m21_sim_trade()
# bar0: runs to +1R (2410) — arms trail (0.6R=6), stop → 2410-7.5=2402.5;
# bar0 low 2398 must NOT trigger the new stop intra-bar.
# bar1: dips to 2402 → trailed stop 2402.5 hit → TRAIL exit.
sim = _m21_sim_df([(2400, 2410, 2398, 2409), (2409, 2409.5, 2402, 2403)])
_gb.simulate_trades([t], sim, max_bars=5, spread=0.0,
trail_arm_r=0.6, trail_dist_r=0.75)
assert t.exit_reason == "TRAIL", f"got {t.exit_reason}"
assert t.bars_held == 2, "new stop may only fill from the NEXT bar"
assert abs(t.exit_price - 2402.5) < 1e-9
assert abs(t.r_multiple - 0.25) < 1e-9
@test("backtester: cooldown pass drops candidates inside the window, in order")
def _():
import gpu_backtest as _gb
cands = [(10, "A"), (12, "B"), (15, "C"), (21, "D"), (24, "E")]
out = _gb._apply_cooldown(cands, cooldown_bars=5)
assert [(p, t) for p, t in out] == [(10, "A"), (15, "C"), (21, "D")], \
f"cooldown must measure from the previously ACCEPTED entry, got {out}"
assert _gb._apply_cooldown([], 5) == []
assert [p for p, _ in _gb._apply_cooldown(cands, 1)] == [10, 12, 15, 21, 24]
# ══════════════════════════════════════════════════════════════════════
# SUMMARY
# ══════════════════════════════════════════════════════════════════════