Skip to content

Port the databento futures producer (ADR-0007 step 2, the last part) - #15

Merged
mspinola merged 1 commit into
mainfrom
claude/port-databento
Aug 9, 2026
Merged

Port the databento futures producer (ADR-0007 step 2, the last part)#15
mspinola merged 1 commit into
mainfrom
claude/port-databento

Conversation

@mspinola

@mspinola mspinola commented Aug 9, 2026

Copy link
Copy Markdown
Owner

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.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: paged resumable ingest (paid), the batch-API variant, the both-directions reconcile 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 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-named fetch_daily_ohlc is npf's own shim over get_bars. It stays in cotdata's git history.

One rule changed rather than moved

cotdata defaults a symbol's databento root to its internal symbol unconditionally, which is safe with one 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, 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 backadj and unadj) and the manifest's vendor key.

Two deliberate design calls

_targets() does not filter on resolve_source, unlike the Norgate provider. --build-databento is 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-symbol price_source override still wins — that is a statement about the symbol, not the machine.

databento keeps its own CLI actions rather than becoming 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 --bars 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.

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/ beside bars/futures/databento/ — which is the comparison that layout was designed to make possible. --norgate-store/--databento-store now default to $MARKETDATA_STORE and 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 scripts clean. MARKETDATA_NO_NETWORK=1 pytest tests/200 passed, 12 skipped (152 on main + 48 here).


Generated by Claude Code

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
mspinola marked this pull request as ready for review August 9, 2026 14:58
@mspinola
mspinola merged commit 404842d into main Aug 9, 2026
5 checks passed
@mspinola
mspinola deleted the claude/port-databento branch August 24, 2026 23:30
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.

2 participants