Skip to content

Add the cross-asset crowding strip - #36

Merged
mspinola merged 2 commits into
mainfrom
claude/positioning-crowding-report-views-59c47d
Aug 22, 2026
Merged

Add the cross-asset crowding strip#36
mspinola merged 2 commits into
mainfrom
claude/positioning-crowding-report-views-59c47d

Conversation

@mspinola

Copy link
Copy Markdown
Owner

A new page at /strip: the whole board on one screen, one row per market, grouped by asset class and sorted so the crowded ends of each group collect at its top and bottom. It reads the same get_matrix_data frame the Heatmap reads and computes nothing of its own. The only thing it changes is scan cost, since a grid is read column by column.

Modelled on a printed cross-asset positioning report, and deliberately unlike it wherever that report defeats its own purpose. docs/design/positioning-report-view-ideas.md evaluates the source, quantifies what it gets wrong from its own numbers, and records what shipped and why.

The marks

No range bar. In index space a min..max rule is always the full axis, because the index is the position within the range. The printed version also draws each market's windows at identical width when the underlying ranges differ by a median of 2.5x and up to 8.2x, so the fact worth seeing is the one thing it removes. A diamond marks the value against an axis ticked at the model's own gate values instead. A Bar mode is kept for the deviation-from-neutral reading.

Shape is the verdict, colour is whose leg it is. A row the model has something to say about gets a diamond in the verdict colour; a quiet row gets a tick, the same mark the speculator legs use, in the app's Commercial colour (palette[0], as everywhere else). Leg ticks take slots 1 and 2 and say whether they are gating through opacity.

Only the legs the model gates on. The strip asks the model, so the NPF strip carries no Large Spec mark at all, and the source report's single "net spec" series, which quietly folds Non-Reportables into the speculator leg, is not reproduced.

Colour follows the ROW's setup state, not each value's own level, the same rule the Heatmap's index cells follow. A lit mark deep inside a band is a market at an extreme with another leg blocking it, which is the interesting case rather than a defect.

Where it stood six weeks ago, as a hollow mark on the same row, clamped to the axis. Reads the momentum column belonging to the selected model's basis, which needs mspinola/cotmetrics#19. Without that column the mark simply does not draw.

Controls

Model, lookback and date follow the existing global stores. Added: Show (all / setups / setups and near) and Side (both / bullish / bearish), two axes that cannot contradict each other because a bull setup is above neutral by construction; Mark (dot / bar); Columns (1 / 2). Two columns break only between classes and balance on row count rather than class count, since the classes are wildly uneven.

The caption carries the report date, the model, the window, and counts anything hidden by a filter or dropped for having no index. It says the window is per-market on the Custom lookback, which is a hazard for a cross-asset ranking specifically.

Refactors

Two moves, both to avoid a second copy rather than for tidiness:

  • grid_colors / GridColors / DIM_TEXT leave pages/analytics/heatmap.py for components/plot_colors.py, now that two surfaces resolve the same palette into bull/bear/near. Re-exported, so heatmap.grid_colors and its tests still resolve.
  • next_date_selection leaves the Heatmap for app_utils, so this page follows a CFTC release the same way without one page importing another.

Testing

  • pytest -q: 221 passed (33 new in tests/test_strip_traces.py)
  • ruff check src tests: clean
  • python scripts/check_dep_floors.py: OK. No floor bump: the momentum column degrades to no mark on an older cotmetrics rather than raising, so this does not hard-require Carry index momentum on both bases in the Signal Matrix cotmetrics#19 to be released first.
  • Driven in a browser against the live store across both models, both marks, one and two columns, and several window widths.

The figure logic in components/strip_traces.py is pure over the frame plus a colour set, so the layout is tested without a store, a palette file or a browser. pages/analytics/strip.py is controls, caption and wiring.

🤖 Generated with Claude Code

mspinola and others added 2 commits August 21, 2026 22:35
The Heatmap already carries every number this draws. What it does not do is let
you see the whole board at once, because reading a grid means reading it column
by column. `/strip` is the same verdicts as a picture: one row per market,
grouped by asset class, sorted so the crowded ends of each group collect at its
top and bottom. It reads the same get_matrix_data frame and computes nothing.

Modelled on a printed positioning report, and deliberately unlike it in the
places that report gets wrong. Written up in
docs/design/positioning-report-view-ideas.md, which evaluates the source and
records what shipped.

No range bar. In index space a min..max rule is always the full axis, because
the index IS the position within the range, so it would carry nothing; the
printed version also draws windows differing by up to eight times at identical
width. A diamond marks the value against a banded, ticked axis instead, and a
Bar mode is available for the deviation-from-neutral reading.

Shape is the verdict and colour is whose leg it is. A row the model has
something to say about gets a diamond in the verdict colour; a quiet row gets a
tick, the same mark the speculator legs use, in the app's Commercial colour.
The legs drawn are the ones the selected model gates on, so the NPF strip has no
Large Spec mark at all. Colour follows the ROW's setup state rather than each
value's own level, same rule the Heatmap's index cells follow, so a lit mark
deep in a band is an extreme with another leg blocking it.

A hollow mark shows where the index stood MOMENTUM_PERIOD weeks ago, read from
the momentum column belonging to the selected model's basis. Needs the
"Comm Move Norm" column added in cotmetrics; degrades to no mark without it.

Filters on two axes that cannot contradict each other: Show takes the verdict,
Side takes which half of its own range the market sits in. Two columns on a wide
screen, breaking only between classes and balancing on row count, since the
classes are wildly uneven. The caption carries the report date, the model, the
window, and counts anything hidden or missing.

Two supporting moves, both to avoid a second copy: grid_colors leaves the
Heatmap page for components.plot_colors, and next_date_selection leaves it for
app_utils, so this page follows a release the same way without one page
importing another.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The six-weeks-ago circles drew teal green against a red key.

An OPEN Plotly symbol takes its outline from marker.color; marker.line is a
second stroke around that. Only the line colour was set, so marker.color was
None, which is not an error, it is the template colourway, whose third entry is
#00cc96. The legend proxy did set marker.color, which is the only reason the
disagreement showed up at all rather than just looking like a colour choice.

The general test written for it found a second instance straight away: the
speculator leg ticks set only marker.line.color too. Those render correctly,
because a line-* symbol happens to draw from marker.line, so that one sat one
symbol change away from silently becoming a template colour. Both now name
marker.color, and the test asserts it for every drawn trace under both marks
instead of pinning the single case that was noticed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mspinola
mspinola merged commit 430990b into main Aug 22, 2026
3 checks passed
@mspinola
mspinola deleted the claude/positioning-crowding-report-views-59c47d branch August 22, 2026 02:43
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