Skip to content

feat(prices): add propadj (ratio) adjustment; salvage DC Class III Milk#23

Merged
mspinola merged 2 commits into
mainfrom
claude/trusting-galileo-f0ab8d
Jul 17, 2026
Merged

feat(prices): add propadj (ratio) adjustment; salvage DC Class III Milk#23
mspinola merged 2 commits into
mainfrom
claude/trusting-galileo-f0ab8d

Conversation

@mspinola

Copy link
Copy Markdown
Owner

Problem

DC (Class III Milk, internal symbol DC / Norgate &DC_CCB) is the flagship new-asset-class (Dairy) held-out generalization market for CMR, but its stored back-adjusted series is unusable: 46.7% of DC_backadj closes are ≤ 0 (range −9.83 to 23.09). Milk is a low-priced (~$15–20/cwt), seasonal, cash-settled contract; over ~29 years, Norgate's additive back-adjustment accumulates roll gaps past zero. CMR's price-based stops, R-multiples, and % returns are meaningless on a non-positive series, so DC can't be used at all.

Investigation

  1. Does Norgate offer ratio/proportional adjustment? No. Norgate's docs confirm continuous futures come only unadjusted and arithmetically (additive) back-adjusted (_CCB). No native ratio series, no alternate suffix.
  2. Can the pipeline build one from stored data? Yes. The additive series B and unadjusted U differ by an offset O = B − U that steps only at rolls (verified: every step >$0.0001 lands on a Delivery-Month change; the rest is sub-cent float dust). Each roll's calendar spread is recoverable and convertible to a multiplicative ratio.
  3. Fallbacks (restrict to positive era / drop DC)? Not needed — the proportional series is clean over full history.

Change

Adds a propadj (proportional / ratio) view derived on read from the already-stored unadj + backadj series — a pure function of two stored series, so no producer re-run, no schema bump, cross-platform (matches the existing reconstructed-volume derivation pattern). Available today via get_prices("DC", adjustment="propadj").

Method: the offset O = B − U is piecewise-constant, stepping only at rolls. At roll r the recovered spread s = O[r−1] − O[r] gives a roll ratio k = (U[r−1] + s)/U[r−1] = F_new/F_old. Each historical segment is scaled by the cumulative product of k for rolls at/after it, anchoring the most-recent segment to actual prices. O/H/L/C are scaled by the per-row segment factor; Volume / Open Interest / Delivery Month pass through.

Validation

  • 0% of closes ≤ 0 (DC range 4.68–25.01 over full 1997–2026 history)
  • anchored to the true current price (15.76)
  • within-segment percentage returns match unadj exactly
  • daily-move signs match backadj 100% including on roll days (corr 0.93 — residual is the intended additive→proportional rescaling)

Recommendation

CMR reads DC (and any similarly low-priced, long-history contract) with adjustment="propadj". DC survives as the held-out Dairy market with clean positive prices. Independent of the other 4 held-out markets (ZO, KE, EMD, NKD), which proceed on backadj unchanged.

Contents

  • prices.get_prices: accept adjustment="propadj"; new _ratio_adjust() derivation
  • tests/test_propadj.py: 10 tests (positivity, anchor, %-return + sign fidelity, hand-computed factors, no-Delivery-Month fallback, missing-series safety, reconstructed-volume composition)
  • README: documents propadj + the DC rationale

Notes

  • The 5 pre-existing test_norgate_provider.py failures (need Windows-only norgatedata) are unrelated — they fail identically on main.
  • Left the _check_roll_gaps "looks UNADJUSTED" heuristic untouched (its DC false-positive is a separate concern, out of scope here).

🤖 Generated with Claude Code

mspinola and others added 2 commits July 16, 2026 17:59
Norgate publishes continuous futures only unadjusted and ADDITIVELY
back-adjusted (_CCB) — no native ratio-adjusted series. For a low-priced,
~29-year contract like DC (Class III Milk, ~$15-20/cwt), additive
accumulation of roll gaps drives 46.7% of DC_backadj closes <= 0
(range -9.83..23.09), which makes price-based stops, R-multiples, and
percentage returns meaningless — so CMR cannot use DC's backadj at all.

Add a `propadj` view derived on read from the already-stored unadj +
backadj series (no producer re-run, no schema bump, cross-platform):
the offset O=B-U steps only at rolls, so each roll's calendar spread is
recoverable (s = O[r-1]-O[r]) and convertible to a multiplicative ratio
k = (U[r-1]+s)/U[r-1]. Scaling each segment by the cumulative product of
k, anchored to the most-recent segment, yields a strictly-positive series
(DC 4.68..25.01 over full 1997-2026 history) that preserves within-segment
percentage returns exactly and is sign-identical to backadj on every day
including rolls.

Recommendation: CMR reads DC (and any similarly low-priced, long-history
contract) with adjustment="propadj". Restricting DC to its positive-price
era (~2011+) or dropping it were the fallbacks; neither is needed.

- prices.get_prices: accept adjustment="propadj"; _ratio_adjust() derivation
- tests/test_propadj.py: positivity, anchor, %-return + sign fidelity,
  hand-computed factors, no-Delivery-Month fallback, missing-series safety,
  reconstructed-volume composition
- README: document propadj + the DC rationale

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mspinola
mspinola merged commit 104b750 into main Jul 17, 2026
5 checks passed
@mspinola
mspinola deleted the claude/trusting-galileo-f0ab8d branch July 17, 2026 02:04
mspinola added a commit that referenced this pull request Jul 20, 2026
#28)

* docs: fix dangling doc refs and backfill CHANGELOG

The docs/plan_promote_reconstructed_volume.md spec was removed in #15 but was
still referenced from config.py, store.py (a user-facing RuntimeError), and
README.md — each a dead pointer. Drop the references; the surrounding text and
the README reconstructed-volume section already explain the schema-v2 behavior.

Backfill the Unreleased CHANGELOG, which had only #23: add the Yahoo provider +
MSCI held-out markets (#24), fold #26 into the propadj entry, and record the
metadata-upsert (#25) and Yahoo-only-skip (#27) fixes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: commit uv.lock for reproducible installs

uv.lock was neither tracked nor ignored, so it perpetually showed as untracked.
The README promotes the uv workflow (`uv pip install -e .`), so track the lock
for reproducible resolution. Verified current with `uv lock --check`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: cover dormant databento provider and COT read API

The largest provider (databento, 280 LOC) had no tests and cot.py's consumer
read path was untested. Add:

- test_databento_provider.py — smoke tests mocking the databento SDK (an optional
  extra) that lock the hard-won statistics parsing: Open Interest from stat_type 9,
  and settlement (stat_type 3) overriding Close dated by ts_ref.
- test_cot.py — get_cot report dispatch, symbol/CFTC-code lookup, and the
  predecessor-code stitching (scale numeric cols, re-stamp code, keep primary on
  overlapping dates, sort).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(prices): fail fast on Norgate outage; drop null metadata rows

Two producer reliability fixes for the Norgate path.

1. Preflight NDU reachability. norgatedata retries each data call 10x then calls
   bare sys.exit() — which exits 0 (a scheduled --prices run looks "successful"
   while writing nothing and never triggers scheduler retry) and raises SystemExit
   past the per-symbol `except Exception`, killing the run on the first symbol.
   update()/update_metadata() now probe norgatedata.status() (a safe, non-exiting
   check) first and raise a clear RuntimeError → non-zero exit if NDU is down.

2. Guard all-null metadata rows. get_symbol_metadata catches each field error to
   None, so a transient Norgate failure on a covered symbol wrote an all-null spec
   row (and on a scoped upsert would overwrite good specs with nulls). Skip such
   rows with a warning.

Tests: preflight aborts update()/update_metadata() when status() is False; the
all-null row is skipped on a full run. Full suite 81 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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