Port the databento futures producer (ADR-0007 step 2, the last part) - #15
Merged
Conversation
The one part of ADR-0007 that step 2 did not implement. The ADR is explicit that vendors are providers and never packages, and its own step-2 inventory names providers/databento.py as the largest single file that moves; it stayed in cotdata because porting it is not a file move and deleting it would have thrown away an ADR-0006-validated alternative. WHAT MOVED. The live two-stage producer: ingest (paid, resumable, paged), the batch-API variant, the reconcile-both-directions repair of the resume ledger, and build (free, offline). Plus both parity harnesses and their tests. WHAT DID NOT. The dormant per-symbol EOD path -- fetch_daily_ohlc, run_batch_backfill, update_all_daily_prices. cotdata's docstring kept it for "the intraday news-failure work", and the code does not support that claim: it fetches ohlcv-1d, so it is a DAILY path that duplicates what the two-stage producer does properly, with its own parallel cache and a yfinance fallback. A fleet-wide sweep found no caller -- npf's fetch_daily_ohlc is npf's own shim over get_bars. It stays in cotdata's git history. ONE RULE CHANGED, because this registry has two domains where cotdata's had one. cotdata defaults `databento` to the internal symbol unconditionally, which is safe with a single domain. Here that would hand every equity a GLBX root and let resolve_source route SPY to a vendor that cannot serve it, so the default is futures-only and equities are always None. The eight markets GLBX does not carry (ICE softs, lumber, the dollar index) are explicit nulls: 41 capable of 49, which matches cotdata exactly. _targets() deliberately does NOT filter on resolve_source the way the Norgate provider does. --build-databento is an explicit ask for this vendor, and every research box defaults to another one, so honouring the deployment default would make the command silently do nothing. A per-symbol price_source override still wins, because that is a statement about the symbol rather than the machine. build() now gathers both frames before writing either, so the both-tiers-or-neither invariant holds on this producer too rather than resting on two adjacent writes. The CLI keeps databento as its own actions rather than a --bars source: it is the only two-stage producer here, which does not fit a flag meaning "go get today's bars", and folding it in would let an unscoped run on a research box spend money by accident. --batch and --windowed-n1-stats are refused rather than ignored when there is no ingest to modify, same rule as --require-final. The parity harness becomes a ONE-store comparison by default. With the vendor as a path component both series sit in the same store, which is the comparison that layout was designed to make possible; the two --*-store flags remain for stores produced on different machines. A test pins that the two vendors cannot read each other's series -- in the old flat layout they were the same path, so a comparison could read a series against itself and report perfect agreement. ruff clean; 200 pass, 12 skip (152 + 48 here).
mspinola
marked this pull request as ready for review
August 9, 2026 14:58
This was referenced Aug 9, 2026
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.
The one part of ADR-0007 that step 2 did not implement. Merge this before cotdata#108, which removes the original.
The ADR is explicit that vendors are providers and never packages, and its own step-2 inventory names
providers/databento.pyas the largest single file that moves. It stayed in cotdata because porting it is not a file move, and deleting it would have thrown away an ADR-0006-validated alternative.What moved
The live two-stage producer: paged resumable
ingest(paid), the batch-API variant, the both-directionsreconcileof the resume ledger, andbuild(free, offline). Plus both parity harnesses and their tests.What did not
The dormant per-symbol EOD path —
fetch_daily_ohlc,run_batch_backfill,update_all_daily_prices. cotdata's docstring kept it for "the intraday news-failure work" and the code does not support that claim: it fetchesohlcv-1d, so it is a daily path duplicating what the two-stage producer does properly, with its own parallel cache and a yfinance fallback. A fleet-wide sweep found no caller — npf's same-namedfetch_daily_ohlcis npf's own shim overget_bars. It stays in cotdata's git history.One rule changed rather than moved
cotdata defaults a symbol's
databentoroot to its internal symbol unconditionally, which is safe with one domain. Here that would hand every equity a GLBX root and letresolve_sourceroute SPY to a vendor that cannot serve it — so the default is futures-only, and equities are alwaysNone.The eight markets GLBX does not carry (ICE softs, lumber, the dollar index) are explicit nulls: 41 capable of 49, matching cotdata exactly.
This is the third time this port has turned up a rule that was right in a single-domain package and wrong in a two-domain one. The first two were the store's tier axis (one frame per symbol cannot hold
backadjandunadj) and the manifest's vendor key.Two deliberate design calls
_targets()does not filter onresolve_source, unlike the Norgate provider.--build-databentois an explicit ask for this vendor, and every research box defaults to another one, so honouring the deployment default would make an explicit command silently do nothing. A per-symbolprice_sourceoverride still wins — that is a statement about the symbol, not the machine.databento keeps its own CLI actions rather than becoming a
--barssource. It is the only two-stage producer here, which does not fit a flag meaning "go get today's bars", and folding it in would let an unscoped--barsrun on a research box spend money by accident.--batchand--windowed-n1-statsare refused rather than ignored when there is no ingest to modify — same rule as--require-final.build()now gathers both frames before writing either, so both-tiers-or-neither holds on this producer by construction rather than resting on two adjacent writes.The parity harness gets simpler
With the vendor as a path component, both series sit in one store —
bars/futures/norgate/besidebars/futures/databento/— which is the comparison that layout was designed to make possible.--norgate-store/--databento-storenow default to$MARKETDATA_STOREand remain for stores produced on different machines.A test pins that the two vendors cannot read each other's series. In the old flat layout they were the same path, so a comparison could read a series against itself and report perfect agreement.
Verification
ruff check src tests scriptsclean.MARKETDATA_NO_NETWORK=1 pytest tests/— 200 passed, 12 skipped (152 onmain+ 48 here).Generated by Claude Code