fix: fetch today's in-progress bar; name weekday crons
Two independent bugs left the near-close scan running on the previous session's close, silently degrading live execution to the stale_close floor (~1.57 Sharpe) instead of the intended ~1.77 close-fill case. 1. OHLCV window never covered the current day. Daily bars are stamped at session start (04:00Z under EDT), so an end of midnight-on-end_date landed before that day's bar and dropped it. Widening the window alone fails the whole request with 'subscription does not permit querying recent SIP data', so end is also clamped to now-20min. Today's bar is now returned, roughly 20 minutes behind live -- within the staleness the near-close design already assumed. Intraday runs therefore store a partial bar and ingestion progress reaches today, which made incremental resume skip the after-close refresh entirely. collect_ohlcv_final() re-pulls the last sessions so the consolidated bar overwrites the partial one before outcome eval. 2. APScheduler's from_crontab() passes day-of-week to its own field where 0=Monday, so '1-5' meant Tue-Sat: every Monday was skipped and the scanner ran Saturdays on stale data. Weekday schedules now use names. Stored settings already corrected via Admin; this fixes the defaults. Tests cover both: today's bar inside the window, the delayed-data clamp, historical windows untruncated, and a week of fire times asserting Monday is present and weekends are not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+31
-3
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import date
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
|
||||
from alpaca.data.historical import StockHistoricalDataClient
|
||||
from alpaca.data.requests import StockBarsRequest
|
||||
@@ -16,6 +16,11 @@ from app.providers.protocol import OHLCVData
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Free plans may not query data from the most recent ~15 minutes, and a window
|
||||
# reaching into it fails the *entire* request — which would silently leave the
|
||||
# near-close scan on yesterday's close. Margin over the documented boundary.
|
||||
_RECENT_DATA_CUTOFF = timedelta(minutes=20)
|
||||
|
||||
|
||||
class AlpacaOHLCVProvider:
|
||||
"""Fetches daily OHLCV bars from Alpaca Markets Data API."""
|
||||
@@ -25,6 +30,26 @@ class AlpacaOHLCVProvider:
|
||||
raise ProviderError("Alpaca API key and secret are required")
|
||||
self._client = StockHistoricalDataClient(api_key, api_secret)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_window(start_date: date, end_date: date) -> tuple[datetime, datetime]:
|
||||
"""Return the instants covering ``start_date``..``end_date`` inclusive.
|
||||
|
||||
Two boundaries have to be right or today's bar disappears:
|
||||
|
||||
* Daily bars are stamped at the session start in UTC (04:00Z under EDT),
|
||||
so an ``end`` of midnight on ``end_date`` lands *before* that day's bar
|
||||
and silently drops it — extend to the following midnight instead.
|
||||
* The window must stay out of the delayed-data period, otherwise the
|
||||
request is rejected outright with "subscription does not permit
|
||||
querying recent SIP data". Clamping keeps today's in-progress bar
|
||||
available, roughly 20 minutes behind live.
|
||||
"""
|
||||
start = datetime.combine(start_date, time.min, tzinfo=timezone.utc)
|
||||
end = datetime.combine(
|
||||
end_date + timedelta(days=1), time.min, tzinfo=timezone.utc
|
||||
)
|
||||
return start, min(end, datetime.now(timezone.utc) - _RECENT_DATA_CUTOFF)
|
||||
|
||||
@staticmethod
|
||||
def _to_alpaca_symbol(symbol: str) -> str:
|
||||
"""Convert internal symbol format (BRK-B) to Alpaca format (BRK.B)."""
|
||||
@@ -40,12 +65,15 @@ class AlpacaOHLCVProvider:
|
||||
) -> list[OHLCVData]:
|
||||
"""Fetch daily OHLCV bars for *ticker* between *start_date* and *end_date*."""
|
||||
alpaca_symbol = self._to_alpaca_symbol(ticker)
|
||||
start, end = self._resolve_window(start_date, end_date)
|
||||
if end <= start:
|
||||
return []
|
||||
try:
|
||||
request = StockBarsRequest(
|
||||
symbol_or_symbols=alpaca_symbol,
|
||||
timeframe=TimeFrame.Day,
|
||||
start=start_date,
|
||||
end=end_date,
|
||||
start=start,
|
||||
end=end,
|
||||
adjustment=Adjustment.SPLIT,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user