- Reject a non-positive last_price ("bad_price") instead of `last_price or
trigger` coercing 0.0 -> trigger, which silently skipped the extension guard
and sized on a phantom price. (latent: fires() guarantees price>=trigger live,
but submit_breakout_now is public.)
- Extract the halt-gap decision to runtime/halt_guard.is_halt_gap() — pure and
unit-tested (the riskiest heuristic had no coverage). Encodes the fail-safe:
None ages (query glitch / session-date mismatch) -> inert, not mass-block;
global-health gate -> a feed-wide lag can't flag the whole book. 7 cases.
- Honest rationale on the extension ceiling: it measures distance above the ORB
trigger (catches the intraday chase / gap-through / halt re-open); overnight
+100% gappers are already excluded upstream by the trigger book's gap_max. It
is not a day-extension metric (a fuller day-move guard is a possible follow-up).
- Fix stale test docstring (15% not 6%); add bad_price test.
tests/unit: 154 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
35 lines
1.5 KiB
Python
35 lines
1.5 KiB
Python
"""Halt detection for the entry guard — pure decision, separately testable.
|
|
|
|
A real LULD trading halt isn't directly readable on the paper/IEX feed, so we
|
|
infer it from a gap in a symbol's minute bars: during RTH a liquid gapper that
|
|
goes silent for minutes while the rest of the book keeps printing is almost
|
|
certainly halted (you can't exit during a halt, and it gaps through the stop on
|
|
resume). The bar-age math lives in the loop (it queries the research DB); this
|
|
module is just the decision so its edge cases are unit-tested.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
def is_halt_gap(
|
|
sym_age_s: float | None,
|
|
global_age_s: float | None,
|
|
gap_seconds: float,
|
|
) -> bool:
|
|
"""True iff this symbol looks halted: its freshest bar is older than
|
|
``gap_seconds`` WHILE ingestion is globally healthy (the freshest bar across
|
|
ALL names is within ``gap_seconds``). The global-health gate is what stops a
|
|
feed-wide lag from flagging the entire book at once.
|
|
|
|
``None`` ages (no data) deliberately return False — NOT halted. This fails
|
|
SAFE: a query error or a session-date mismatch makes the guard inert (entries
|
|
proceed under the other gates) rather than mass-blocking every entry. The
|
|
cost of the rare missed halt is bounded by the extension ceiling, which still
|
|
blocks the gap-through on resume.
|
|
"""
|
|
if gap_seconds <= 0:
|
|
return False
|
|
if sym_age_s is None or sym_age_s <= gap_seconds:
|
|
return False
|
|
return global_age_s is not None and global_age_s <= gap_seconds
|