fix(sessions): read Alpaca bars as open-stamped - #529
Merged
Merged
Conversation
…sitive #520 keys market_data_store on sorted symbols; the docstring still said tuple(symbols). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Alpaca stamps a bar at its open and serves extended hours, but every raw-bar session filter applied the close-stamp rule (inclusive 16:00). So: - minute mode kept the 16:00-16:05 after-hours source bar: the day's last equity mark was an after-hours price, and the 16:00 decision filled only if the tape happened to serve that bar (SIP does, carrying the closing auction; IEX often not); - the leaderboard's raw 1h path kept 10:00-16:00, i.e. the 16:00-17:00 after-hours bar in and the 09:00 bar holding the 09:30 open out. is_in_session takes open_stamped_minutes (an open-stamped bar is in session when it closes in one), profiles.bars_open_stamped says which sources need it (Alpaca; not iFinD or vnpy), and bar_aggregation.plan_execution_fills is the one fill planner for the engine and the protocol store: the final bucket fills at the last in-session source bar's close. Seven decisions a day either way; the hourly board becomes 09:00-15:00. Also imports DEFAULT_MARKET into market_data_store from sessions instead of restating it. Cached leaderboard rows keep the old rule until a force refresh. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 16:00 decision now fills at the 15:55 close instead of being dropped, so a 5m-source run executes 7 decisions a day, not 6. The verified contract gains `session_close_fill: last_source_bar_close` (additive, so stored runs keep their label) and the backtest details label names it. The planner also returned the decision stamp on an exact match instead of the equal source bar, which re-stamped every trade in UTC. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
FlyM1ss
marked this pull request as draft
September 23, 2026 20:41
Address PR #529 review findings: the loader stamps open-stamped frames, session filters read that attr instead of a per-caller parameter (the baseline worker path lost every 16:00 close), aggregation refuses unlabelled sources, and one ExecutionFill record replaces the parallel execution lists. The session-close fill is stamped 16:00, and session_close_fill now passes the API allow-list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FlyM1ss
marked this pull request as ready for review
September 23, 2026 21:12
A dataset whose decision bars are its source bars has no execution plan, and its default ExecutionFill named each bar's open. The executor ignored that field outside intraday mode and filled at the close, matching the decision_bar_close contract, so no run was mispriced. But the record contradicted both the contract and engine._plan_executions, and one unconditional execution_prices would have turned it into look-ahead. Default to close in MarketDataset and _effective_execution_fills, and pin the unaggregated fill price with a regression test. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alpaca stamps bars at their open and serves extended hours, but the session filter treated every stamp as a close. So the US filter kept the after-hours 16:00 bar.
AlpacaDataLoader.fetch_barsstampssessions.FRAME_ATTR_OPEN_STAMPED_MINUTESon every frame it returns, cache hits included. Each session filter reads it withframes_open_stamped_minutes(), and aggregation drops it (decision bars are stamped at their close). There is no per-caller parameter: the first revision passed one by hand, and the protocol baseline worker, which never callsload_data, filtered aggregated bars as raw 5m and lost every 16:00 close. A loader without the attr (iFinD, vnpy, legacy doubles) keeps the close-stamp rule.session_close_fill.plan_execution_fillsreturns oneExecutionFill(bar, price_field, filled_at)per step. It replaces the parallel lists in the dataset, the protocol session and the engine. The 16:00 ET decision fills at the close of the bar ending then (15:55), and the trade is stamped 16:00, not 15:55. A 5m-source run now makes 7 decisions a day (it was 6).ExecutionFillnames the bar'sclose, matchingdecision_bar_closeand the engine. The executor already filled there; the recordedopenwas a latent look-ahead.session_close_fill, so the details label renders it. The label suffix appears only undernext_source_bar_open.⚠ Cached leaderboard rows were built with the old 10:00–16:00 filter. A force refresh (
POST /api/v1/leaderboard/refresh?force=true) is billable and is intentionally not part of this PR.Tests: full backend suite green (5411 passed, 165 skipped). The new tests were mutation-checked, and all 11 mutations were caught.
🤖 Generated with Claude Code