Skip to content

Exposure: the contract percentile, per market - #64

Merged
mspinola merged 3 commits into
mainfrom
claude/exposure-contracts-percentile
Aug 24, 2026
Merged

Exposure: the contract percentile, per market#64
mspinola merged 3 commits into
mainfrom
claude/exposure-contracts-percentile

Conversation

@mspinola

Copy link
Copy Markdown
Owner

A gold-and-silver positioning report draws each market twice, in contracts and in dollar notional, each against its whole history. Evaluated against this page, almost all of it was already here: Markets = Gold, Leg = Speculators, Unit = Notional, Lookback = All history renders the report's own chart, and our version adds the volatility panel, the Large-against-Small split and the publication lag.

One thing was missing, and it was the thing the report is built on. The contract percentile lived only in the single-market sentence under the headline, because it is read off the member frame. Selecting Gold and Silver therefore removed the comparison rather than doubling it. This adds it to the contributions table as a column, joined per member.

On the live week the report's four numbers become two rows:

Market USD notional (bn) USD daily risk (m) %ile Contracts %ile
Gold 114.2 1,753.5 100 88
Silver 14.3 384.8 98 52

Which says out loud what the report leaves to the reader: silver's extreme is almost entirely price and volatility, not more contracts.

It is also a cross-check of the store against an outside source. Their contract figures (258,418 lots at the 88th percentile for gold, 44,792 at the 52nd for silver) reproduce exactly; the dollar figures differ by about a percent because they price off Stooq/FMP/Yahoo and this stack prices futures off Norgate unadjusted.

Three decisions in the column

  • Ranked against all history, not against the Lookback control. contribution_table ranks the dollar columns that way, and two percentiles on one row measured over different stretches of time is exactly the confusion this column exists to remove. That the table does not follow the control while the headline does is a pre-existing inconsistency, recorded in the design doc and deliberately not fixed here.
  • Read off the member frame, never the aggregate. cotmetrics The damage page plots this site's universe, and names every bubble on it #21 removed the aggregate's summed net_contracts because it added ES contracts to corn contracts; this column would have been the first thing to bring that back.
  • The signed count rides the rowData for the hover, because a percentile has no side and this column's subject is a position that has one. The dollar percentile's header tooltip now names its unit, since two columns headed "%ile" and "Contracts %ile" leave the first to be inferred.

The design doc gains a section evaluating the report chart by chart, including what was left behind (their "spec = mirror of commercials" label folds Small Traders into the speculator leg).

Testing

463 passed, 6 of them new, ruff check src tests clean. Verified in the browser on Gold + Silver: the column, the values above, the hover, and no console or server errors.

Note: this touches the tail of docs/design/positioning-report-view-ideas.md, as does #63. Whichever merges second needs a one-hunk rebase there.

🤖 Generated with Claude Code

mspinola and others added 3 commits August 24, 2026 18:30
A single market gets a sentence saying where contracts put the same week that the
dollars are extreme in. A set got nothing, because that sentence reads the one member
frame, so selecting two markets removed the comparison rather than doubling it. This
puts it in the contributions table as a column, where it exists for every member.

The report this was measured against draws gold and silver, each in contracts and in
notional, each against its whole history. Its numbers reproduce here exactly on the
contract side (258,418 lots at the 88th percentile for gold, 44,792 at the 52nd for
silver) and within about a percent on the dollar side, where its price source differs
from Norgate. With the column, those four numbers are two rows of one table: gold 100
in dollars against 88 in contracts, silver 98 against 52, so silver's extreme is almost
entirely price and volatility rather than more contracts.

Ranked against ALL history rather than against the Lookback control, because
contribution_table ranks the dollar columns that way and two percentiles on one row
measured over different stretches of time is the confusion this column removes. That
the table does not follow the control while the headline does is a pre-existing
inconsistency, recorded in the design doc and not fixed here.

Read off the member frame, never off the aggregate: cotmetrics #21 removed the summed
net_contracts because it added ES contracts to corn contracts. The signed count rides
the rowData for the hover, because a percentile has no side and this column's subject
is a position that has one. The dollar percentile's header tooltip now names its unit,
since two columns headed "%ile" and "Contracts %ile" leave the first to be inferred.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found while checking the column against the live board rather than against a fixture:
gold reads 88 on the contract count alone and 81 inside an equities-and-metals set,
because aggregate_exposure restricts every member frame to the weeks the total can
price and Russell does not start until 2002. The dollar percentiles beside it have
always done this, and this column follows them deliberately, so the fix is words rather
than arithmetic: the header tooltip now says "over the weeks this set covers", the
docstring carries the worked example, and a test pins the behaviour so nobody corrects
this column into disagreeing with the two beside it.

The table's own label still says "each market against its own history", which is a
shade imprecise for every percentile in the table rather than for this one. Left for
whoever next edits that label, which is the session fixing the Lookback inconsistency,
since editing it here would collide with that change for one word.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The comment said a 52-week window would give this fixture the same answer as all
history and that the assertion therefore pinned nothing about the window. Measured on
the fixture itself, the three answers are 0.91 expanding, 1.92 at 52 weeks and 3.85 at
26, because the denominator is how many weeks were compared. The assertion does
discriminate; the reasoning printed beside it was wrong.

Found by the session stacked on this branch, which measured it rather than reading it,
and reproduced here independently before changing anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mspinola
mspinola force-pushed the claude/exposure-contracts-percentile branch from 7e4cada to 379bbeb Compare August 24, 2026 22:31
@mspinola
mspinola merged commit 3d3f1c6 into main Aug 24, 2026
3 checks passed
@mspinola
mspinola deleted the claude/exposure-contracts-percentile branch August 24, 2026 22:33
mspinola added a commit that referenced this pull request Aug 24, 2026
The headline, the band, the %ile scale and the caption all ranked over whatever
Lookback resolved to. The contributions table under them ranked every member
against all history whatever the control said, because contribution_table called
expanding_pct_rank unconditionally. So on "52 weeks" the page said "higher than
97% of the last 52 weeks" three lines above a column headed "%ile" measured
against twenty years, with both on screen at once and nothing on either saying
they were different questions. Live, Gold alone on 52 weeks: headline 62nd
percentile, table row 99. Now 62 and 61.5.

Threading the window through rather than explaining it in copy. The expanding
rank has a real claim on this page, since no unit here is stationary through
time and it is the only form that can say "the most ever", but that is an
argument for keeping All history as the DEFAULT, which it is, and not for
ignoring a control after the reader has moved it. Copy can describe two bases;
it cannot make them comparable, and the question the table answers is "which
market is driving the number in the headline".

Three columns move together or none of them do. The dollar percentiles ride the
`window` parameter cotmetrics 0.9.0 added to contribution_table, which is why
the floor moves with this: against 0.8.0 the keyword raises TypeError on every
render and no setting of the control avoids it. The Contracts %ile column from
#64 follows in the same change, since it was written to match whatever the
dollar columns did and moving one without the other puts the row back to mixing
histories by the other route.

The copy that goes false the moment the arithmetic lands, all in here rather
than in a follow-up: the dollar column's "Against all of it, whatever the
Lookback control says", the table label, and the standing explanation, which
told a reader who moved the control that the band moved with it and said nothing
about the table. The label also absorbs the second imprecision #64 left for
whoever next edited it, that a member's "own history" is the weeks the SET
covers (gold 88 alone, 81 in a set starting in 2002 because Russell does). Both
restrictions are named, and the set clause is dropped on a single market, where
it has nothing to qualify.

The default is asserted rather than left to a constant: attach_contracts_rank
without a window reads 0.91 on the fixture against 1.92 at 52 weeks and 3.85 at
26, so the expanding assertion discriminates, and the test now says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mspinola added a commit that referenced this pull request Aug 24, 2026
The headline, the band, the %ile scale and the caption all ranked over whatever
Lookback resolved to. The contributions table under them ranked every member
against all history whatever the control said, because contribution_table called
expanding_pct_rank unconditionally. So on "52 weeks" the page said "higher than
97% of the last 52 weeks" three lines above a column headed "%ile" measured
against twenty years, with both on screen at once and nothing on either saying
they were different questions. Live, Gold alone on 52 weeks: headline 62nd
percentile, table row 99. Now 62 and 61.5.

Threading the window through rather than explaining it in copy. The expanding
rank has a real claim on this page, since no unit here is stationary through
time and it is the only form that can say "the most ever", but that is an
argument for keeping All history as the DEFAULT, which it is, and not for
ignoring a control after the reader has moved it. Copy can describe two bases;
it cannot make them comparable, and the question the table answers is "which
market is driving the number in the headline".

Three columns move together or none of them do. The dollar percentiles ride the
`window` parameter cotmetrics 0.9.0 added to contribution_table, which is why
the floor moves with this: against 0.8.0 the keyword raises TypeError on every
render and no setting of the control avoids it. The Contracts %ile column from
#64 follows in the same change, since it was written to match whatever the
dollar columns did and moving one without the other puts the row back to mixing
histories by the other route.

The copy that goes false the moment the arithmetic lands, all in here rather
than in a follow-up: the dollar column's "Against all of it, whatever the
Lookback control says", the table label, and the standing explanation, which
told a reader who moved the control that the band moved with it and said nothing
about the table. The label also absorbs the second imprecision #64 left for
whoever next edited it, that a member's "own history" is the weeks the SET
covers (gold 88 alone, 81 in a set starting in 2002 because Russell does). Both
restrictions are named, and the set clause is dropped on a single market, where
it has nothing to qualify.

The default is asserted rather than left to a constant: attach_contracts_rank
without a window reads 0.91 on the fixture against 1.92 at 52 weeks and 3.85 at
26, so the expanding assertion discriminates, and the test now says so.

Co-authored-by: Claude Opus 5 <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