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:
+22
-12
@@ -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 "
|
||||
|
||||
Reference in New Issue
Block a user