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>
89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
"""Alpaca fetch window / feed selection.
|
|
|
|
Regression cover for the 2026-07-20 outage: the near-close scan silently ran on
|
|
the previous session's close because ``end`` resolved to midnight on end_date,
|
|
which is *before* that day's bar timestamp (04:00Z under EDT). Widening the
|
|
window also has to stay clear of the delayed-data period, which rejects the whole
|
|
request.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
|
|
from app.providers.alpaca import AlpacaOHLCVProvider
|
|
|
|
|
|
class _CapturingClient:
|
|
"""Stands in for StockHistoricalDataClient, recording the request."""
|
|
|
|
def __init__(self) -> None:
|
|
self.request = None
|
|
|
|
def get_stock_bars(self, request):
|
|
self.request = request
|
|
return {"AAPL": []}
|
|
|
|
|
|
def _provider() -> tuple[AlpacaOHLCVProvider, _CapturingClient]:
|
|
provider = AlpacaOHLCVProvider("key", "secret")
|
|
client = _CapturingClient()
|
|
provider._client = client
|
|
return provider, client
|
|
|
|
|
|
def _midnight(day: date) -> datetime:
|
|
"""Naive-UTC midnight — the SDK strips tzinfo from request datetimes."""
|
|
return datetime.combine(day, datetime.min.time())
|
|
|
|
|
|
def _utcnow() -> datetime:
|
|
return datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_todays_in_progress_bar_is_inside_the_window():
|
|
"""The whole near-close design depends on today's bar being fetchable."""
|
|
provider, client = _provider()
|
|
today = date.today()
|
|
|
|
await provider.fetch_ohlcv("AAPL", today - timedelta(days=5), today)
|
|
|
|
# Daily bars are stamped at session start (04:00Z); a midnight end drops them.
|
|
assert client.request.end > _midnight(today)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_window_stays_out_of_the_delayed_data_period():
|
|
"""A window reaching the last ~15 minutes fails the entire request."""
|
|
provider, client = _provider()
|
|
|
|
await provider.fetch_ohlcv("AAPL", date.today() - timedelta(days=5), date.today())
|
|
|
|
assert client.request.end <= _utcnow() - timedelta(minutes=15)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_completed_past_day_is_fully_covered():
|
|
"""Clamping must not swallow the last day of a historical window."""
|
|
provider, client = _provider()
|
|
end_date = date.today() - timedelta(days=3)
|
|
|
|
await provider.fetch_ohlcv("AAPL", end_date - timedelta(days=5), end_date)
|
|
|
|
assert client.request.end > _midnight(end_date)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_window_collapsing_to_nothing_skips_the_call():
|
|
"""A start inside the delayed period yields no request at all, not an error."""
|
|
provider, client = _provider()
|
|
tomorrow = date.today() + timedelta(days=1)
|
|
|
|
records = await provider.fetch_ohlcv("AAPL", tomorrow, tomorrow)
|
|
|
|
assert records == []
|
|
assert client.request is None
|