Norgate futures provider (ADR-0007 step 2) - #7
Merged
Conversation
ADR-0007 makes cotdata CFTC-positioning-only and moves every bar here. This is
the provider that has to exist first, ported from cotdata's rather than moved.
The store could not hold the result as it stood. The one-frame-per-symbol
layout rests on a property equities never violate: corporate actions are dated
events the vendor hands over with the bars, so one stored frame reconstructs
any tier. Norgate's back-adjustment is roll splicing the vendor performed, and
the stitched calendar spread appears in no other series it publishes, so
backadj cannot be derived from unadj or the reverse. Both are stored facts, and
on the old layout they resolved to the same path — the second write would have
silently replaced the first. Hence the stored-tier filename component and
schema v2. Equities keep the flat v1 path and read through the same code, so an
existing store is extended rather than migrated.
Worth recording that the futures domain was declared but not reachable:
get_bars(..., "backadj") raised "tier must be one of ('split','raw','total')"
because check_tier accepted the tier and adjust() then rejected it. The
declared DOMAIN_TIERS entry was a promise about error messages, not a path.
propadj is derived from the two stored tiers, and it is the only futures tier
whose percent returns are correct — 52.3% of ZS's back-adjusted closes and
41.2% of DC's are non-positive, where propadj has one such bar across all 47
symbols. So both tiers or neither, enforced in the producer (nothing is written
until every tier is in hand), in the consumer (a half-stored symbol raises and
names the missing tier instead of reading as "no data"), and in a test. The
loudness is deliberate: additive back-adjusted percent vol is ~200x too high
for soybeans and 0.47x for gold, and 0.47x passes every implausibility screen.
provenance() and --pin also assumed one series per symbol; an unscoped --pin
took futures manifest keys as symbol names and failed lookup. Both are now
tier-aware, and a snapshot entry is one stored series so --symbols ES pins both
of ES's tiers — pinning one would leave propadj half covered and let a
volatility figure verify green against a store that had moved. v1 snapshots
still verify unchanged.
Only Windows can produce this half, since norgatedata drives a local Norgate
Data Updater install. --bars skips futures with a message elsewhere rather than
failing the run, and --domain futures explains why instead of raising
ModuleNotFoundError.
MME/MFS are not ported: Norgate carries no continuous series for either, and
serving them off their ETF proxies needs a futures-domain path in the yfinance
provider. Absent beats present-and-unserviceable.
Every test here is offline. The norgatedata call sites are ported from code
that has run in production in cotdata, but they are unexercised in this repo
until someone runs them on the Windows box.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo4iovRfc2fzE9MwcLp7r2
11 tasks
mspinola
marked this pull request as ready for review
August 9, 2026 01:23
This was referenced Aug 9, 2026
Merged
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.
ADR-0007 makes
cotdataCFTC-positioning-only and moves every bar here. This is the provider that has to exist first — written againstcotdata's as a reference rather than moved across a seam, sinceproviders/held onlyyfinance.py.Companion record on the
cotdataside: mspinola/cotdata#103 (merged) and mspinola/cotdata#104. Nothing is deleted fromcotdataand no consumer is repointed yet, so both producers can run side by side.Verified against cotdata on the Windows box, 2026-08-09
Run with
scripts/verify_against_cotdata.pyagainst acotdatastore built by the original producer:49,892 rows per tier, both tiers, plus contract specs for all five, exit 0. Exact equality, not a tolerance — both producers drive the same Norgate install through two code paths, so a difference would have been a port bug rather than vendor disagreement. Symbols span an index, an energy, a metal, and the two markets whose
backadjhistory goes non-positive.That is what ADR-0007 §7.5 needs before
cotdata's price code is deleted, and it was obtainable only while both halves exist.The store could not hold the result
bars/<domain>/<source>/<symbol>.parquetgavebackadjandunadjthe same path. The second write would have silently replaced the first.The one-frame-per-symbol layout rests on a property equities never violate: corporate actions are dated events the vendor hands over with the bars, so one stored frame reconstructs any tier. Norgate's back-adjustment is roll splicing the vendor performed, and the stitched calendar spread at each roll appears in no other series it publishes —
backadjcannot be derived fromunadj, or the reverse. Both are stored facts.Hence a stored-tier filename component (
<symbol>_<tier>.parquet) andSCHEMA_VERSION1 → 2. Equities keep the flat v1 path and read through the same code, so an existing store is extended rather than migrated.Worth recording that the futures domain was declared but not reachable. On
main:check_tieraccepted the tier for the futures domain andadjust()then rejected it. TheDOMAIN_TIERS["futures"]entry — added so error messages would be right from day one — was a promise about error messages, not a path.Both tiers or neither
propadjis derived from the two stored frames, and it is the only futures tier whose percent returns are correct. Measured on thecotdatastore: 52.3% of ZS's back-adjusted closes and 41.2% of DC's are non-positive, against one such bar forpropadjacross all 47 symbols (CL, 2020-04-20, because WTI really settled at −37.63 — ratio adjustment preserves the underlying's sign rather than imposing one).So the rule is enforced three times, and it cannot be lost by editing any one of them:
propadjagainst one stored tier raises and names the missing one. Empty would read as "no data for this symbol" when the truth is "the producer half-finished".Loudness is deliberate: additive back-adjusted percent volatility is ~200x too high for soybeans and 0.47x for gold, and 0.47x never goes negative and passes every implausibility screen a spot check would apply.
What the Windows box found that offline testing could not
Two defects, on the first two contacts, and the pattern matters more than either:
--domain futuresstopped at the import guard. The provider shipped without its dependency:cotdatadeclaresnorgate = ["norgatedata"]and this repo had no such extra, so nothing installed it. The guard was right to refuse; its message was wrong for the one machine that matters, sending the Windows producer to--domain equities. Now declared, with the message naming the extra and a test asserting both.get_barshad novolume=parameter. The producer half of volume reconstruction was ported and the consumer half was not, so the columns were written and nothing served them.npf'sml/labels.py:50passesvolume=through, so a repointed call would have raisedTypeError. The docstring carries crowdmon's measurement rather than restating the names, because they are a trap:reconstructedsums exactly two expiries — 0.52 of total volume in natural gas, 0.54 in crude — sofrontis the whole-market series. A test pinsreconstructed < front.Neither is visible to a suite that cannot install the vendor or call a parameter that does not exist.
Three things the port broke quietly, all fixed
provenance()and--pinassumed one series per symbol.provenance("ES")returnedNone, and an unscoped--pintook manifest keys as symbol names, so futures arrived as the "symbol"ES_backadj. Both are tier-aware now. Snapshot v1 → v2: an entry is one stored series, so--symbols ESpinsES_backadjandES_unadj— pinning one would leavepropadjhalf covered and let a volatility figure verify green against a store that had moved. v1 snapshots still verify unchanged.--checkragged every row — a fixed 10-character symbol column againstfutures/norgate/ES_backadj. Width now comes from the data.volume=switch was found.Windows-only, and the CLI says so
norgatedatadrives a local Norgate Data Updater install, which is Windows-only.--barsskips the futures half with a message on other machines rather than failing the run;--domain futuresexplains why instead of raisingModuleNotFoundError. The README documentssetxfor both store roots, since that box now runs two producers against two roots.Not ported
MME/MFS(MSCI EM and EAFE). Norgate carries no continuous series for either, andcotdataprices them off the EEM and EFA ETF proxies through yfinance. Serving them here needs a futures-domain path in the yfinance provider — separate work from the Norgate producer. They are absent from the futures registry rather than present and unserviceable. Confirmed this costscrowdmonnothing: both already fail itscoverage()for the same underlying reason.Testing
119 pass (
-m "not network"), ruff clean, plus the live comparison above.scripts/verify_against_cotdata.pyneeds two real stores so it cannot run in CI; its comparison logic is unit tested against synthetic frames, including that a single changed bar in 36,000 is caught and dated — same posture ascotdata'svalidate_databento_vs_norgate.py.