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
+22 -12
View File
@@ -1678,6 +1678,24 @@ def _reanchor_entries_to_raw(
)
def _apply_cooldown(
candidates: List[Tuple[int, Trade]], cooldown_bars: int
) -> List[Tuple[int, Trade]]:
"""Ordered cooldown pass over (bar_pos, Trade) candidates: drop any
candidate within `cooldown_bars` bars of the previously ACCEPTED
entry — exactly what a sequential replay loop would have skipped.
Candidates must already be sorted by position (stable merge from the
worker spans)."""
accepted: List[Tuple[int, Trade]] = []
last_entry_pos = -10_000
for pos, trade in candidates:
if pos - last_entry_pos < cooldown_bars:
continue
accepted.append((pos, trade))
last_entry_pos = pos
return accepted
def run_backtest(
bt_cfg: BacktestConfig,
platform_cfg: Optional[PlatformConfig] = None,
@@ -1805,18 +1823,10 @@ def run_backtest(
candidates.extend(cands)
candidates.sort(key=lambda pc: pc[0])
# ── Phase 2: entry cooldown — the only sequential rule. A candidate
# inside the cooldown window of the previously ACCEPTED entry is
# dropped, exactly as the sequential loop skipped those bars.
trades: List[Trade] = []
signal_ts_list: List[pd.Timestamp] = []
last_entry_pos = -10_000
for pos, trade in candidates:
if pos - last_entry_pos < bt_cfg.cooldown_bars:
continue
trades.append(trade)
signal_ts_list.append(primary_df.index[pos])
last_entry_pos = pos
# ── Phase 2: entry cooldown — the only sequential rule.
accepted = _apply_cooldown(candidates, bt_cfg.cooldown_bars)
trades: List[Trade] = [t for _, t in accepted]
signal_ts_list: List[pd.Timestamp] = [primary_df.index[p] for p, _ in accepted]
logger.info(
f"Replay complete: {len(trades)} entries in "