Repository navigation
Ingest the CFTC Supplemental (Commodity Index Trader) report - #96
Merged
Merged
Conversation
Adds `supplemental` as a fourth report_type: a new producer action (--cot-supplemental, also in --cot-all) writing cot_supplemental/, a cotdata.get_cot(report="supplemental") reader, and vintage capture + canonicalisation from 2006 onward. Purely additive: nothing joins the natural key, VALUE_FIELDS or ALL_COLUMNS, so no stored row_sha256 moves. Four things came back different from the brief and from CFTC's own prose, all measured against the real 2006-2026 archives: - Coverage is 12 markets, then 13 from 2013 when Soybean Meal entered. Both circulating counts are right for part of the history. Three markets were renamed without changing code, so coverage keys on market_code. - The report is futures-and-options combined and the file cannot say so: it carries no FutOnly_or_Combined column, so _combined_flag would have defaulted it to False and put a guessed value in the natural key. Established by matching open interest against both Legacy series, 390/390 against combined and 0/390 against futures-only, so canonicalize_supplemental asserts it. - Index traders are carved out of three buckets, not the two CFTC describes. The third is non-commercial spreading, ~0% of the index book in 2006 and ~9% now. - No 2006-2016 history bundle, and every year from 2006 returns 200 where disagg/TFF 404 before 2010, so annual_sources needed its own floor. The open-interest identity is exact on only ~55% of market-weeks and that is rounding: the residual never exceeds 2 contracts against a tolerance of 4, the rate is flat across 21 years, and on the same weeks Legacy futures-only is exact on 99.7% while Legacy combined shows the identical +/-1 pattern. Combined reports round delta-weighted option equivalents per category, which is the n-addends mechanism rounding_tolerance was already derived from. Scope recorded in ADR-0002; measurements in docs/analysis/; the executed handoff is closed in docs/handoffs/ with every correction inline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A cold-context review of the previous commit found three real defects, all in the new coverage code, which was its least-exercised part. None touched the store's existing output or the permanent row_sha256. - derive_coverage/coverage() picked the market name by lexicographic max, which is not "current" and reported the STALE name on four real markets: Cocoa, Cotton, Sugar and Coffee all renamed from "... - NEW YORK BOARD OF TRADE" to "... - ICE FUTURES U.S." in 2007, and 'I' < 'N'. The wheat pair came out right under either rule, which is how it survived a first reading. Now takes the name at the latest report date. - market_name is descriptive, so validate() does not require it and a canonical frame may carry nulls. astype(str) turned those into the literal "nan" under pandas 2, which is lowercase and beats every real uppercase CFTC name, and left a float in place under pandas 3, where the comparison raised TypeError. Both are now routed through clean_market_name. The suite runs green on both pandas majors. - coverage_changes compared whatever years were present as though they were adjacent. A store holding 2006 and 2026 and nothing between reported Soybean Meal as entering in 2026 — an ingest artifact presented as a fact about the market. It now compares consecutive years only and emits a `gap` record. Also from the review: - The provider and the vintage layer had two coverage implementations that had already drifted on the name rule. Now one shared _coverage_from, with their agreement asserted in full rather than on year/code alone. The producer also derives coverage over what it WROTE rather than over everything in the files. - _cmd_coverage wrote before checking for an empty result, so pointing COTDATA_STORE at the wrong root truncated a good artifact and then said "nothing ingested yet". write_coverage now also takes the ingest write lock, for the read underneath it rather than for the atomic write. - _resolve shipped with no tests despite sitting on the row_sha256 path. Added, including the property that made the widening safe: no candidate spelling of one field is a candidate of another, and no target matches two real columns in the shipped 2026 headers. It also now handles Spead/Spread and composes up to three substitutions, so the realistic upstream cleanup of NComm_Postions_Spread_All_NoCIT (two defects in one name) resolves instead of raising. A genuine rename still raises. - A vacuous test assertion (a prefix no CFTC column has ever had) replaced with one that checks what its name claims. - Prose corrections: six renames rather than three, KCBT to CBOT rather than to CME, and the CHANGELOG had attached the breach-rate range (62.6-71.7%) to the exact-rate sentence (~55%), which is arithmetically impossible. Per-year exact is 51.6-59.5%. The analysis doc is corrected in place, on the same day and before it existed off this branch, and frozen from here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8 of 11 tasks
mspinola
added a commit
that referenced
this pull request
Aug 3, 2026
Removes docs/handoffs/2026-08-03-index-share.md, added by #97. Its own header names crowdmon as its home and target: Lives at: crowdmon/docs/handoffs/2026-08-03-index-share.md Target: Claude Code session, crowdmon worktree Now committed there as crowdmon#36 (c243c125), body byte-identical, so this is the other half of one move rather than a deletion. The location matters beyond filing. The handoff orders analysis relating index flow to crowdmon's Swap Dealer fragility weight, and crowdmon/tests/test_boundaries.py refuses an import of crucible precisely so a package cannot render a verdict on its own output. A work order for that analysis sitting in the producer repo invites it to be executed here, against the store rather than across the seam. The #96 brief already said so in terms: "Do not do the analysis. That is the companion handoff in crowdmon, and it should run against a merged, tested ingestion rather than alongside one." What stays is 2026-08-03-cit-supplemental.md, the ingestion half, closed as complete. That one is genuinely cotdata's: it was executed here. Docs only. No package code, no tests, no store shape touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Description
Adds the CFTC Supplemental (Commodity Index Trader) report as a fourth
report_typealongsidelegacy,disaggregatedandtff: producer action, store domain, consumer reader, and vintage capture + canonicalisation from 2006 onward.Executes
docs/handoffs/2026-08-03-cit-supplemental.md, which is closed in this PR with every correction inline. Unblocks the companion handoff incrowdmon, which needs the index/non-index split to fix a Swap Dealer fragility weight that measurement showed doing opposite work on cocoa and gold.Scope recorded in ADR-0002: the Supplemental is CFTC positioning, so it sits inside ADR-0007's narrowed boundary on the same argument that admitted vintage provenance. No change is proposed to ADR-0007 or ADR-0008 — this applies a rule they already state.
Purely additive. Nothing joins the natural key,
VALUE_FIELDSorALL_COLUMNS, so no storedrow_sha256moves and no existing observation is touched. A store that never runs the new action is unchanged.Four things the data contradicted
All measured against the real 2006-2026 archives (21 files, 13,584 market-weeks). Full detail in docs/analysis/2026-08-03-cit-supplemental-measurements.md.
market_code.FutOnly_or_Combinedcolumn, so the existing_combined_flagwould have defaulted it toFalseand put a guessed value in the natural key. Established by matching open interest against both Legacy series: 390/390 against combined, 0/390 against futures-only. Now asserted, with no override.annual_sourcesneeded its own floor.The identity exception rate, and why it is not alarming
Exact on ~55% of market-weeks, residual never above 2 contracts against a tolerance of 4, and flat across 21 years (per-year 51.6-59.5%, sd 2.1pp).
That is combined-report rounding, established by control rather than asserted: on the same 2026 weeks, Legacy futures-only is exact on 99.7% of rows while Legacy combined shows the identical +/-1 pattern. A combined report publishes delta-weighted option equivalents rounded to whole contracts, independently per category — the same n-addends mechanism
rounding_tolerancewas already derived from.validate()raises zero warnings across the full history.Worth your eyes
The one deviation from written instructions. The brief asked for categories
non_commercial/index_traders/non_reportable; this shipscommercial/noncommercial/index_trader/nonreportable. A new spelling would makecategory == "nonreportable"silently miss every Supplemental row, whilecommercial— the genuinely confusable label — is identical either way, so the distinguish-by-spelling argument fails on exactly the category that needed it.report_typedoes the disambiguating, as the brief itself specifies. Reasoning in the closed handoff §2.A cold-context review ran before this PR and found three real defects, all in the new coverage code (second commit): a lexicographic-max name rule that reported the stale pre-2007 NYBOT name on four live markets, a null-handling path that produced the literal
"nan"on pandas 2 and aTypeErroron pandas 3, andcoverage_changesreporting an ingest gap as a market entry. It also found that_resolve— which sits on the permanent-hash path — had shipped untested.Limits, stated rather than implied
Type of change
Checklist:
pytest) and they pass locallyVerified locally: 289 tests pass on both pandas 2 and pandas 3 (Python 3.11 only — the 3.10/3.12/3.13/3.14 legs are what this PR's CI is for),
ruff check src tests scriptsclean, and a full green run against an empty store with no network. Separately reproduced end to end against the live CFTC archives: 21 raw snapshots captured, 54,336 observations, zero revisions, zero warnings, and a full 21-snapshot replay wrote nothing.🤖 Generated with Claude Code