Skip to content

Dated futures curves: ingest, store, and analyze real term structure#21

Merged
oldhero5 merged 6 commits into
mainfrom
feat/dated-futures-curves
Jun 14, 2026
Merged

Dated futures curves: ingest, store, and analyze real term structure#21
oldhero5 merged 6 commits into
mainfrom
feat/dated-futures-curves

Conversation

@oldhero5

Copy link
Copy Markdown
Owner

Dated futures curves: ingest, store, and analyze real term structure

energex stored only the continuous front-month proxies (CL=F, BZ=F, NG=F)
in a single intraday_prices table, so term structure, roll yield, and basis
were structurally meaningless (see ASSESSMENT.md). This adds the dated contract
strip as a first-class dimension, so the platform can serve genuine forward
curves and cross-sectional term-structure analytics.

What's new

  • Storage: a daily_contracts table (one daily settlement per dated
    contract), keyed by (Commodity, ContractMonth, Datetime) with an idempotent
    ON CONFLICT upsert, mirroring insert_intraday_data (column validation,
    canonical order, TIMESTAMPTZ/UTC). reset() and init updated; migration path
    untouched.
  • Ingestion: EnergyDataFetcher.fetch_dated_contracts / fetch_all_dated
    enumerate the next N monthly NYMEX contracts per commodity (CME month codes,
    year rollover handled), fetch each via the existing retry/timeout path, and
    tag rows with Commodity / ContractMonth. A pure _dated_tickers helper
    makes the enumeration unit-testable without network. Daily bars are stamped
    UTC explicitly.
  • Source adapter: DataSource.fetch_dated added across base / yfinance /
    stubs; run_dated_ingestion in the pipeline.
  • Analytics: new DatedFuturesAnalyzer with curve_as_of,
    term_structure_slope (annualized front-to-back), roll_yield (annualized
    adjacent-pair, null-guarded on degenerate spans), and shape
    (backwardation / contango / flat). Sign-consistent across all three; correct
    Polars 1.x idioms (no GroupBy.mutate, no list-vs-scalar comparisons). The
    continuous-proxy FuturesAnalyzer's gated methods now point here.
  • Service: /curve endpoint (commodity + optional asof, 404 on empty),
    dated ingestion wired into the scheduler (gated on dated_enabled, failure
    isolated from startup), and /healthz reports a daily_contracts count.
  • Config: DataFetchConfig.dated_enabled / dated_lookback_days /
    dated_contract_count.

Testing

  • +17 tests (105 -> 122 passing). Storage idempotence/PK/upsert/read-only,
    analyzer math with asserted backwardated and contango cases, ticker
    enumeration incl. year rollover, and stubbed source/pipeline coverage.
  • All tests are network-free (stub source + fixtures). ruff clean on new
    code; new code adds no mypy errors.

Scope

Generic platform capability only; no commodity-specific market views.

oldhero5 added 6 commits June 14, 2026 16:17
Store the dated contract strip per commodity (daily settlements) in a new
daily_contracts table keyed by (Commodity, ContractMonth, Datetime), with an
idempotent ON CONFLICT upsert mirroring intraday_prices. Add DataFetchConfig
knobs for the dated strip (enabled, lookback, contract count).
Enumerate forward monthly NYMEX contracts (CL/BZ/NG roots, month codes F-Z)
and fetch each contract's recent daily bars, tagging Commodity and
ContractMonth. A single empty/failed contract is logged and skipped. Expose a
pure _dated_tickers helper for unit testing and a fetch_dated method on the
DataSource adapter.
Real forward-curve analytics over dated contracts: curve_as_of, annualized
term_structure_slope, per-pair roll_yield, and curve shape classification.
Point the gated continuous-proxy FuturesAnalyzer methods at this new capability.
Add run_dated_ingestion (fetch -> upsert -> checkpoint), run it alongside
intraday ingestion in the scheduler gated on dated_enabled, expose a /curve
route, and report a daily_contracts row count from /healthz.
Add storage idempotence/PK tests, DatedFuturesAnalyzer backwardation/contango
cases, pure ticker-enumeration unit tests, source/pipeline fetch_dated
coverage, a sample_daily_contracts fixture, and README notes.
roll_yield now emits null (not silent inf) for same- or inverted-maturity
adjacent pairs. Daily contract bars are tz-naive from the vendor, so stamp
them UTC explicitly rather than relying on the session TimeZone at insert.
@oldhero5 oldhero5 merged commit 694bd2c into main Jun 14, 2026
6 checks passed
@oldhero5 oldhero5 deleted the feat/dated-futures-curves branch June 14, 2026 21:50
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