Point-in-time futures reads: get_bars(..., asof=) - #25
Merged
Conversation
Additive back-adjustment re-anchors to whatever contract is front NOW, so every past price shifts on every roll and a study reads prices that were on no screen at the time. `end=` truncates today's restated series; `asof=` also removes the cumulative spread of every roll since, returning the vintage that was current then. The arithmetic is exact rather than approximate. The offset B - U is the cumulative spread of every roll after t, so the vintage current on a past date is today's series minus the single constant offset(asof). Measured on the live store, that offset is piecewise constant with zero non-roll movement across 11,714 HE bars, and the implementation agrees with an independent roll-by-roll reconstruction to 1e-4 on HE and 3e-3 on GC, which is float32 storage precision at those price levels. It matters only for RATIO-based logic, and there it is decisive. On HE against a June-2015 as-of, 25.1% of "20-day ROC above 10%" days and 21.3% of "5% above the 200-day mean" days differ from today's series, while a 50/200 crossover and a 20-day breakout differ on ZERO days of 9,237: a constant cancels out of a comparison between two points on one series and does not cancel out of a ratio between them. All three futures tiers honour it. `unadj` is as-traded and does not restate, so its vintage is a plain truncation. `backadj` re-anchors, and raises if `unadj` is absent since the offset IS the difference between them. `propadj` re-anchors BEFORE ratio-adjusting, because the other order scales segments by roll ratios from rolls that had not happened yet. Equities raise NotImplementedError. The equity vintage is a different derivation, not the same one with a different date: stored yfinance OHLC is already split-adjusted using splits AFTER the as-of date. Refusing beats ignoring the argument, since silently returning today's series for a point-in-time request is the failure this parameter exists to prevent. asof defaults to None, so every existing read is byte-identical. Docs, no behaviour change: - README "What a futures bar carries" documents the per-bar columns for the first time, including Delivery Month and FirstContract / SecondContract, which name the actual expiries and appeared in no document before. - design.md "Known holes" records that per-expiry OHLC is absent by SELECTION, not vendor limit: _reconstruct_volume already downloads full OHLCV for every individual contract and then keeps only Date, Volume and Symbol. Same shape as the reconstruction-columns defect recorded above it, one level deeper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What
get_bars(sym, tier, asof="2015-06-30")returns the futures series as it stood on that date, not today's restated one.Additive back-adjustment re-anchors to whatever contract is front now, so every past price shifts on every roll.
end=truncates today's restated series;asof=also removes the cumulative spread of every roll since.Why it matters
Only for ratio-based logic, and there it is decisive. Measured on HE against a June-2015 as-of:
A constant offset cancels out of a comparison between two points on one series, and does not cancel out of a ratio between them. So a moving-average system is immune to this and a percent-threshold system is not: a quarter of the ROC signals a backtest sees on hogs were not visible when they supposedly fired.
How
The arithmetic collapses. The offset
B - Uis the cumulative spread of every roll aftert, so the vintage current on a past date is today's series minus the single constantoffset(asof). Re-anchoring is one subtraction.Verified two ways:
1e-4(HE) and3e-3(GC), which is float32 storage precision at those price levelsAll three tiers honour it:
unadjis as-traded and does not restate, so its vintage is a plain truncationbackadjre-anchors, and raises ifunadjis absent since the offset is the difference between thempropadjre-anchors before ratio-adjusting; the other order scales segments by roll ratios from rolls that had not happened yetEquities raise
NotImplementedError. Deliberate scope call: stored yfinance OHLC is already split-adjusted using splits after the as-of date, so the equity vintage is a different derivation, not the same one with a different date. Refusing beats ignoring the argument, since silently returning today's series is the failure the parameter exists to prevent. Follow-up if wanted.Compatibility
asofdefaults toNone, so every existing read is byte-identical.Docs (no behaviour change)
Delivery MonthandFirstContract/SecondContract, which name the actual expiries and appeared in no document before. Includes a runnable reproducer for the table above._reconstruct_volumealready downloads full OHLCV for every individual contract and then keeps onlyDate,Volume,Symbol. Same shape as the reconstruction-columns defect recorded above it, one level deeper.Tests
11 new, on a hand-checkable fixture (three segments, roll spreads of 8, so expected vintage prices are arithmetic you can verify by eye). The headline test asserts both halves at once: difference signals invariant, ratio signals not.
242 passed, 12 skipped,ruff check src testsclean on 0.15.22.🤖 Generated with Claude Code