Skip to content

Make the futures provider actually runnable: norgate extra, volume=, and the port verification - #8

Merged
mspinola merged 6 commits into
mainfrom
claude/adr7-step2-followups
Aug 9, 2026
Merged

Make the futures provider actually runnable: norgate extra, volume=, and the port verification#8
mspinola merged 6 commits into
mainfrom
claude/adr7-step2-followups

Conversation

@mspinola

@mspinola mspinola commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Follow-up to #7, which merged at 340b2ef — the provider alone. As merged, --domain futures cannot run: the provider imports norgatedata and nothing installs it.

These five commits are what the Windows box asked for, and they were written after #7 merged, so they need their own PR rather than being stacked onto merged history.

What the real box found that offline testing could not

Two defects, on the first two contacts, and the pattern matters more than either.

1. --domain futures stopped at the import guard. The provider shipped without its dependency: cotdata declares norgate = ["norgatedata"] and this repo had no such extra. The guard behaved correctly — it refused rather than half-producing — but its message sent the reader to --domain equities, which is right for a Mac or Linux box and exactly wrong for the machine that is supposed to produce futures. A missing package and a Windows-only vendor are two problems with different fixes, and the message now separates them and names the install.

2. get_bars had no volume= parameter. The producer half of volume reconstruction was ported — the provider writes Volume_Reconstructed, FirstVolume, SecondVolume, Volume_Source — and the consumer switch that serves them was not, so cotdata's get_prices(volume="reconstructed") had no counterpart. npf's ml/labels.py:50 passes volume= through, so a repointed call would have raised TypeError.

The docstring carries crowdmon's measurement rather than restating the names, because the names are a trap pointing opposite to intuition: reconstructed sums exactly two expiries and is 0.52 of total volume in natural gas and 0.54 in crude, so front is what a whole-market denominator wants. A test pins reconstructed < front so the docstring cannot drift from it. Equities refuse the parameter outright.

Neither defect is visible to a suite that cannot install the vendor or call a parameter that does not exist.

The verification, and the harness that produced it

scripts/verify_against_cotdata.py compares a marketdata futures store against cotdata's, reading both stores' parquet directly — no network, no imports of either package except the optional --check-propadj. Exit code gates the promotion. Same posture as cotdata's validate_databento_vs_norgate.py: real data, kept out of CI, comparison logic unit tested.

The tolerance is stricter than that neighbour's, and deliberately. It compares two independent vendors whose roll calendars and anchors differ, so only shape can agree. This compares one vendor through two code paths, so the passthrough columns must be exactly equal.

Result on the Windows producer, 2026-08-09:

symbol rows per tier passthrough reconstruction
ES 7,279 identical identical
CL 10,887 identical identical
GC 12,156 identical identical
ZS 12,271 identical identical
DC 7,299 identical identical

49,892 rows per tier, both tiers, plus contract specs for all five, exit 0. 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 harness reported silence as agreement, which is how defect 2 surfaced

It printed reconstruction columns only when they differed, so "compared and identical" and "never compared" rendered as the same nothing — a PASS could not be distinguished from a PASS that skipped half the frame. It now names every column family it skipped and prints the ones that matched. The first real run printed no reconstruction lines at all, and chasing that silence is what found the missing volume= switch.

One expectation corrected

The reconstruction columns were expected to drift, since each producer reconstructs incrementally over its own store's history and marketdata's was fresh where cotdata's had months of accumulation. They agree exactly: Norgate's historical individual-contract volumes are immutable and the algorithm is identical, so the incremental path converges on what a full recompute produces. That makes --strict-volume usable rather than theoretical.

Also here

MARKETDATA_STORE on Windows. That box now runs two producers and needs both store roots set at once against different roots — new with the futures domain, and documented nowhere. setx rather than set, with the reason (a variable living only in the current prompt is the usual cause of a scheduled task failing where an interactive shell works), --check as the offline confirmation, and an explicit warning against one shared root: each package keeps a manifest.json at its own root and does a read-modify-write on it.

Testing

119 pass (-m "not network"), ruff clean, plus the live comparison above. The harness's own tests cover that a single changed bar in 36,000 is caught and dated, that matching NaNs are agreement rather than difference, that a reconstruction difference does not mask a price difference, and that a typo'd store path exits 2 instead of reading as "nothing differed".


Generated by Claude Code

mspinola and others added 6 commits August 9, 2026 02:07
The provider cannot be exercised anywhere but the Windows box, and the one
consumer that would have failed loudly on a wrong series — crowdmon, whose
strict tier requirements were why the work order put it first — was deprecated
with no consumers left. Nothing exercises propadj until the deferred npf pass.
So the comparison against cotdata's existing store is the only evidence the
port preserved the numbers, and it is available only until ADR-0007 §7.5
deletes the half being compared against.

That makes it worth more than two commands and an eyeball. The script reads
both stores' parquet directly, no network and no imports of either package, and
exits non-zero so it can gate the promotion. Same shape as cotdata's
validate_databento_vs_norgate.py, which is the precedent for a real-data
harness kept out of CI with its logic unit tested.

The tolerance is the part worth noting. That neighbouring harness compares two
INDEPENDENT vendors, whose roll calendars and back-adjust anchors differ, so
only shape can agree. This compares one vendor through two code paths, so the
passthrough columns must be EXACTLY equal and a float away is a port bug. The
exception is the volume reconstruction: both producers compute it identically
but incrementally over their own store's history, so a fresh marketdata store
and a months-old cotdata one legitimately differ there. Those columns are
reported rather than failed on, with --strict-volume as the opt-in and a
pointer at cotdata's --full.

The harness itself is tested here, because a verifier that reports identical on
frames that differ is worse than none — it would clear the port for deletion of
the only thing it can ever be compared against. 19 tests: a single changed bar
is caught and located, matching NaNs are agreement rather than difference, a
reconstruction difference does not mask a price difference, MME/MFS are an
expected skip rather than a failure, and a typo'd store path exits 2 instead of
reading as "nothing differed".

Verified end to end on seeded stores: one planted bad bar in 36,000 rows was
found and dated, with the reconstruction drift correctly a note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo4iovRfc2fzE9MwcLp7r2
`--domain futures` stopped at the import guard on the Windows producer because
nothing installs norgatedata: the provider landed with a dependency no extra
declares. cotdata has carried `norgate = ["norgatedata"]` all along and the port
did not bring it across.

The guard behaved correctly — it refused rather than half-producing — but its
message sent the reader to --domain equities, which is right for a Mac or Linux
box and exactly wrong for the machine that is supposed to produce futures. A
missing package and a Windows-only vendor are two problems with two different
fixes, and the message now separates them and names the install.

Two tests, because the guard being right is what hid this. One asserts the extra
exists and carries norgatedata, so a provider whose vendor package nothing
installs cannot ship again. The other asserts the message names both the extra
and the platform constraint, so it cannot quietly regress to advice that only
suits the machines which were never going to run it.

README gains the Windows producer install line for the same reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo4iovRfc2fzE9MwcLp7r2
… meet

The variable was documented as required, and config.py refuses by name when it
is unset, but every example was `export ...` — bash, on a repo whose only
producer machine runs Windows. cotdata carries a whole Windows setup guide with
`set`, System Settings, .env and an `echo %VAR%` check; marketdata carried
nothing.

The larger gap is newer than the port. That box now runs BOTH producers, so it
needs COTDATA_STORE and MARKETDATA_STORE set at the same time against different
roots, and nothing said so. Until ADR-0007 moved bars here, COTDATA_STORE alone
was the whole story.

So: setx rather than set, with the reason spelled out, since a variable that
lives only in the current prompt is the usual cause of a scheduled task failing
where an interactive shell works. Then --check as the cheap confirmation, being
manifest-only and offline. And an explicit warning against one shared root:
sharing a parent folder is fine and makes the pair easy to sync, but each
package keeps a manifest.json at its own root and does a read-modify-write on
it, so one root means the two producers eventually drop each other's entries.

Python, venv and Task Scheduler setup are identical to cotdata's and are
pointed at rather than duplicated. Only the norgate extra and these two
variables are marketdata-specific.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo4iovRfc2fzE9MwcLp7r2
…ence as agreement

The first real run passed on every passthrough column across 49,892 rows and
five symbols, and printed nothing at all about the reconstruction columns. That
was predicted to print notes, so the silence was the finding.

Two causes, both mine.

The verifier reported reconstruction columns only when they DIFFERED, so
"compared and identical" and "never compared" rendered as the same nothing. A
PASS therefore could not be read as "everything was compared", which is the one
thing this harness exists to establish before ADR-0007 §7.5 deletes the only
store it can ever compare against. It now names every column family it skipped
and why, and prints the ones that matched.

And the reason there may have been nothing to compare: get_bars never grew the
volume= parameter. The producer half was ported — _reconstruct_volume writes
Volume_Reconstructed, FirstVolume, SecondVolume and Volume_Source — but the
consumer switch that serves them was not, so cotdata's
get_prices(volume="reconstructed") had no counterpart here. npf's ml/labels.py
passes volume= through, so a repointed call would have raised TypeError.

The docstring carries crowdmon's measurement rather than restating the names,
because the names are a trap that points the opposite way to intuition:
`reconstructed` sums exactly two expiries and is 0.52 of total volume in natural
gas and 0.54 in crude, so `front` is what a whole-market denominator wants. A
test pins that reconstructed < front so the docstring cannot drift from it.
Equities refuse the parameter outright, since summing two expiries is not a
thing an equity has.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo4iovRfc2fzE9MwcLp7r2
Run on the Windows producer against a cotdata store built by the original
producer. Every compared series identical, exit 0, over five symbols spanning an
index, an energy, a metal and the two markets whose backadj history goes
non-positive. Exact equality rather than a tolerance, since both producers drive
the same Norgate install through two code paths.

This is what ADR-0007 §7.5 needs before cotdata's price code is deleted, and it
was obtainable only while both halves still exist.

Two things recorded beyond the verdict.

The reconstruction columns matched, which this repo's own docs said to expect
drift on. Both producers reconstruct incrementally over their own store's
history, so a fresh store recomputing 12,000 bars against one that accumulated
them over months looked like a real source of difference. It is not, and the
reason generalises: Norgate's historical individual-contract volumes are
immutable and the algorithm is identical, so incremental converges on full. That
makes --strict-volume usable rather than theoretical.

And the two defects the real box found were both invisible to the offline
suite — a dependency no extra declared, and a producer-side column family with
no consumer-side switch to serve it. A test that cannot install the vendor
cannot catch the first, and no offline test calls a parameter that does not
exist. The harness caught the second only because it was changed to report the
columns it had NOT compared rather than staying silent about them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo4iovRfc2fzE9MwcLp7r2
CI failed on test (3.10) alone. tomllib is stdlib only from 3.11, and this
package declares >=3.10, so the test I added to stop the packaging gap
recurring could not run on the floor version — the one most likely to be a
stale producer environment, and therefore the one the check is worth most on.

Reading the file as text asserts the same fact on every supported version. It
is also honest about what the test is: a check that a line is declared in
packaging metadata, not a check of TOML semantics.

Verified on 3.10.20 as well as 3.11 this time, rather than on the interpreter
that happened to be in the venv. That is the same class of mistake as the two
the Windows box found — a green suite proving something narrower than it
appeared to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo4iovRfc2fzE9MwcLp7r2
@mspinola
mspinola merged commit 307e7a2 into main Aug 9, 2026
5 checks passed
@mspinola
mspinola deleted the claude/adr7-step2-followups branch August 9, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant