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