Paper trader Phase 1.B.3 + 1.C.1: market clock via Alpaca + scoring dispatch log

- _is_market_open() now queries Alpaca's get_clock() so holidays, early
  closes, and halted markets no longer skip orders; falls back to weekday
  9:30–16:00 ET only on broker error (1.B.3)
- AlpacaBroker.get_clock() + MarketClock dataclass wrapping alpaca-py's
  TradingClient.get_clock()
- PaperTradingEngine logs scoring_model at session startup (WARNING level)
  so multi-session daemon makes the live-path scorer dispatch explicit
  in every boot log — verifies v7.356 config's return_max_long_v13e
  actually reaches _compute_score (1.C.1)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
I Luk Kim 4 months ago
parent 2b6cea57b2
commit d83a666f30

@ -61,6 +61,13 @@ class PortfolioHistory:
profit_loss_pct: list[float]
@dataclass
class MarketClock:
is_open: bool
next_open: dt.datetime | None
next_close: dt.datetime | None
class AlpacaBroker:
"""Thin wrapper around alpaca-py TradingClient for paper trading."""
@ -99,6 +106,15 @@ class AlpacaBroker:
# Account
# ------------------------------------------------------------------ #
def get_clock(self) -> MarketClock:
"""Return Alpaca's market clock (respects holidays, early closes, halts)."""
clk = self._trading.get_clock()
return MarketClock(
is_open=bool(clk.is_open),
next_open=clk.next_open,
next_close=clk.next_close,
)
def get_account(self) -> AccountInfo:
acct = self._trading.get_account()
equity = float(acct.equity or 0)

@ -134,6 +134,14 @@ class PaperTradingEngine:
self._parking_brake_cooldown_remaining: int = 0
self._parking_brake_skip_buy_today: bool = False # skip same-day re-buy after brake fires
logger.warning(
"paper_trader_scoring_dispatch",
session_id=session.session_id,
session_name=session.session_name,
scoring_model=self._config.signal.scoring_model,
config_path=session.config_path,
)
def _get_candidate_capital_bucket_id(self, candidate: Candidate) -> str | None:
return candidate.engine_capital_bucket_id
@ -438,12 +446,21 @@ class PaperTradingEngine:
)
return None, reason
@staticmethod
def _is_market_open() -> bool:
"""Return True if US equity market is currently open (9:3016:00 ET, weekdays)."""
def _is_market_open(self) -> bool:
"""Return True if US equity market is currently open.
Authoritative: Alpaca `get_clock()` respects holidays, early closes,
halted markets. Falls back to a weekday 9:3016:00 ET clock-only check
on any broker error (better to fail-open during rare API hiccups than
miss an event entirely; Alpaca will reject rejected orders downstream).
"""
try:
return self._broker.get_clock().is_open
except Exception as exc:
logger.warning("paper_engine_clock_fallback", error=str(exc))
import zoneinfo
now_et = dt.datetime.now(tz=zoneinfo.ZoneInfo("America/New_York"))
if now_et.weekday() >= 5: # Saturday=5, Sunday=6
if now_et.weekday() >= 5:
return False
market_open = now_et.replace(hour=9, minute=30, second=0, microsecond=0)
market_close = now_et.replace(hour=16, minute=0, second=0, microsecond=0)

Loading…
Cancel
Save