feat: sign last_session_close out of hours (fix RAI-693)#12
Conversation
|
Warning Review limit reached
More reviews will be available in 49 minutes and 47 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The broker positions endpoint exposes `current_price` with no per-symbol timestamp. The d841a31 broker-mark cutover stamped `Utc::now()` at fetch time and called that `publish_time`, which only bounds the server's own freshness — `max-staleness` in the strategy therefore always sees a fresh stamp regardless of whether the underlying market data is hours stale. This week's verification (45h crontab sampler across all four market phases) confirmed: - During the extended session (04:00–20:00 ET on a trading day) the broker mark legitimately moves with pre-market and after-hours quotes. Signing `now` is correct. - During a full close (overnight, weekend, holiday) it freezes hard (31h × 4 symbols × zero changes in the weekend sample). Signing `now` lies; the strategy accepts an old close as fresh. The original "stopgap" (only re-stamp when current_price changes) was ruled out because broker marks can freeze for 15–45 min during real RTH on mid-volume names (SGOV/COIN/AMZN), which would false-positive strategies with a tight max-staleness window. This PR sources the truth from Alpaca's `/v1/calendar` instead: - New `market_hours` module: `MarketHoursCache` holds the extended session windows around today; `publish_time_for(now)` returns `now` inside an active window, the most recent past `session_close` outside. `anchor_session_to_utc` converts the calendar's HHMM ET strings through chrono-tz so EST/EDT transitions are handled correctly. - `AlpacaClient::fetch_calendar(start, end)` reads `/v1/calendar` and materialises each row into a `SessionWindow` via the new module. - Primed at startup with ±7 days around today (cold-start during a holiday still resolves to the previous trading day's close), then refreshed hourly in the background. A failed fetch logs and keeps the previous cache — matching the quote-poll loop's behavior. - `build_response_from_quote` consults the cache at sign time. The fetch_time on `QuoteData` is no longer used for the signed stamp. Cache stays empty until the first refresh succeeds, in which case `publish_time_for` falls back to `now` — preserving pre-fix behaviour rather than blocking the server during a transient calendar outage. Tests: - Unit tests cover every branch of `publish_time_for` (active session, out of session, holiday Monday with no entry falling through to the prior Friday close, weekday overnight, both session boundaries, empty cache) and `anchor_session_to_utc` (EDT, EST, malformed HHMM). - Integration tests confirm `/context/v1` signs `last_session_close` in the default out-of-session test app, and signs the request's wall-clock `now` when the app is configured with an active session window. Closes RAI-693. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
b6add3f to
d1732ad
Compare

The broker positions endpoint exposes
current_pricewith noper-symbol timestamp. The d841a31 broker-mark cutover stamped
Utc::now()at fetch time and called thatpublish_time, which onlybounds the server's own freshness —
max-stalenessin the strategytherefore always sees a fresh stamp regardless of whether the
underlying market data is hours stale.
This week's verification (45h crontab sampler across all four market
phases) confirmed:
broker mark legitimately moves with pre-market and after-hours
quotes. Signing
nowis correct.(31h × 4 symbols × zero changes in the weekend sample). Signing
nowlies; the strategy accepts an old close as fresh.The original "stopgap" (only re-stamp when current_price changes) was
ruled out because broker marks can freeze for 15–45 min during real
RTH on mid-volume names (SGOV/COIN/AMZN), which would false-positive
strategies with a tight max-staleness window.
This PR sources the truth from Alpaca's
/v1/calendarinstead:market_hoursmodule:MarketHoursCacheholds the extendedsession windows around today;
publish_time_for(now)returnsnowinside an active window, the most recent past
session_closeoutside.
anchor_session_to_utcconverts the calendar's HHMM ETstrings through chrono-tz so EST/EDT transitions are handled
correctly.
AlpacaClient::fetch_calendar(start, end)reads/v1/calendarandmaterialises each row into a
SessionWindowvia the new module.holiday still resolves to the previous trading day's close), then
refreshed hourly in the background. A failed fetch logs and keeps
the previous cache — matching the quote-poll loop's behavior.
build_response_from_quoteconsults the cache at sign time. Thefetch_time on
QuoteDatais no longer used for the signed stamp.Cache stays empty until the first refresh succeeds, in which case
publish_time_forfalls back tonow— preserving pre-fixbehaviour rather than blocking the server during a transient calendar
outage.
Tests:
publish_time_for(active session,out of session, holiday Monday with no entry falling through to the
prior Friday close, weekday overnight, both session boundaries,
empty cache) and
anchor_session_to_utc(EDT, EST, malformedHHMM).
/context/v1signslast_session_closein the default out-of-session test app, and signs the request's
wall-clock
nowwhen the app is configured with an active sessionwindow.
Closes RAI-693.
Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com