audit 2026-09-13: eleven delivery/measurement fixes; books byte-identical

Signal path
- A shipped setup always names a direction: grade C needs confidence
  >= 45 (gate 6c grants B at >= 45) while BUY/SELL needs >= 50, so C/B
  reports reached the alert gate as NEUTRAL with a full setup. The
  backtester traded them (the replay never reads final_signal) while
  Telegram rejected NEUTRAL first — 5/91 trades, +3.7R of +22.2R, in
  the H1 12-month book. _attach_core_setup stamps the setup's direction
  and repairs the pre-gate action text; no-setup reports stay NEUTRAL.
- analyze_pair fails the scan when ANY required frame is missing
  ("DATA UNAVAILABLE:", scan_kind='failed'); a lost confirmation frame
  used to run with an unearned -15 MTF penalty.

Outcomes and labels
- advisory_outcome.resolve_locked_floor: a gap the database cannot fill
  resolves at the locked trailing stop (GAP_LOCKED, result.floor) when
  the stop had crossed the entry; the rebuild fires on both gap reasons
  (rows 915/920: +1.14R / +0.84R floors; resolved sum -2.46R -> -0.48R).
- label_scan_outcomes: SCAN_LABEL_MAX_AGE_H = 72 h, stale rows never
  anchor the shared bar stream, labeled_at bumped on re-examination,
  _stream_since warns at its row cap (the Labor-Day pile would have
  blinded scan_outcomes around 2026-09-14).

Providers and scheduling
- The daily settlement pause (Mon-Thu 17:00 New York) is a scheduled
  closure for _has_latest_closed_bar (whole-span label walk-back) and
  _is_data_fresh (closed seconds are not age); bot and dashboard skip
  scans whose just-closed label sits in the pause (61 false stale rows
  in three nights).
- fetch_range retries a failed page in place (three failures on one
  window raise) and logs an ERROR for interior gaps of >3 unscheduled
  bars.
- pd.Timestamp.utcnow() replaced in providers/base.py and engine.py;
  requirements pin pandas<4, numpy<3.
- telegram_bot: boundary loops share one scan per bar with the breach
  handler (_remember_scan keys on the analysed bar; breach defers 20 s
  after an M15 boundary); the rotating file handler attaches in main()
  only (the test suite used to write into logs/telegram.log).
- dashboard_publisher: catch_up_timeframes re-scans boundaries missed
  while a cycle overran or the process was down.

Backtester and engine guards
- _replay_span returns the per-span GPU cache-miss count; run_backtest
  sums, logs it (must be 0) and writes cache_fallbacks to the manifest.
- regime_weight_table raises on a partial regime_weights_override.

Verification: 519 tests; M5/M15 2-yr, M15 OOS, H1/H4 3-yr books
byte-identical before and after; live signals.db untouched (checked on
a copy). CLAUDE.md carries the dated entry and the blocked-hours union
semantics that every book was validated under.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0139xz1J6YccndA2be6yFnEo
This commit is contained in:
Harwinder Sekhon
2026-09-13 21:42:37 -04:00
parent 1c293f7d2e
commit 2836024e9d
12 changed files with 1602 additions and 70 deletions
+33
View File
@@ -348,6 +348,39 @@ def advance_outcome(
return state
def resolve_locked_floor(state: Dict[str, Any]) -> Dict[str, Any]:
"""If an UNRESOLVABLE gap outcome had already trailed its stop past the
entry, finish it at that stop as a FLOOR (the true outcome is at least
this good) instead of discarding it. Returns the state (mutated)."""
if state.get("status") != "UNRESOLVABLE":
return state
reason = str(state.get("reason", ""))
# Both gap reasons ("lost continuity" / "contain an internal gap")
# share this prefix; an invalid seed or evaluator error never floors.
if not reason.startswith("stored candle snapshots"):
return state
progress = state.get("progress")
setup = state.get("setup")
if not isinstance(progress, dict) or not isinstance(setup, dict):
return state
entry = _finite(progress.get("entry_price"))
stop = _finite(progress.get("current_stop"))
risk = _finite(setup.get("stop_distance"))
exit_at = progress.get("last_processed_bar")
if (entry is None or stop is None or risk is None or risk <= 0
or not exit_at):
return state
buy = setup.get("direction") == "BUY"
locked = stop >= entry if buy else stop <= entry
if not locked:
return state
_finish(state, "GAP_LOCKED", str(exit_at), stop)
state["result"]["floor"] = True
state["reason"] = (
f"{reason} — resolved at the locked trailing stop (floor)")
return state
def canonical_r(state: Dict[str, Any]) -> Optional[float]:
result = state.get("result")
value = result.get("net_r_065") if isinstance(result, dict) else None