Skip to content
This repository was archived by the owner on Aug 8, 2026. It is now read-only.

Respecify the forced-flow test, and file the copy change it releases - #80

Merged
mspinola merged 2 commits into
mainfrom
claude/ffm-respecification
Aug 7, 2026
Merged

Respecify the forced-flow test, and file the copy change it releases#80
mspinola merged 2 commits into
mainfrom
claude/ffm-respecification

Conversation

@mspinola

@mspinola mspinola commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Stacked on #79, which closes the mechanism test. This files the correction beside the result rather than leaving it in a session's memory.

This is a specification correction, not a work order. §7 says what would have to change before any of it could be executed, and §8 states the count executing it would carry. The register row says the same, loudly, because a handoff without a completion status gets executed by a future session and this one must not be.

Nothing here changes a published number, a weight, a threshold in src/, or the verdict.

The defect

The mechanism test's §8 records four places its specification was wrong. This is a fifth, larger than those four together, and invisible until the flow composition was looked at.

The outcome variable is Δ pool_net. On the sell side an agreeing pool is long and sells when price falls to F*; a contradicted pool is short and adds shorts at the same level. Both are sell flow, both give Δnet < 0, and only the first is forced.

This is not a new insight about futures. It is the opening paragraph of src/crowdmon/futures/flow.py:

Net position change cannot tell them apart, because Δnet = ΔLong − ΔShort is the same either way, and that is the whole reason this module exists.

flow.decompose exists for exactly this distinction and flow_state is a published column. The frozen §5 references neither.

What the composition shows

Advisory and untested, from the artifact the verdict already commits. Share of the week following a crossing carrying each state:

report / side group long_liquidation new_shorts short_covering new_longs
disagg / sell agrees 20.1% 9.4% 2.6% 5.6%
disagg / sell contradicted 3.9% 25.8% 11.6% 2.8%
disagg / buy agrees 2.4% 6.7% 31.0% 4.3%
disagg / buy contradicted 7.3% 3.4% 8.0% 18.1%
tff / sell agrees 15.7% 4.3% 6.6% 8.9%
tff / sell contradicted 4.8% 9.0% 12.6% 6.3%
tff / buy agrees 5.4% 10.5% 11.2% 5.5%
tff / buy contradicted 12.6% 6.8% 4.7% 11.9%

All four cells: the named pool does what the trigger says, the contradicted pool does the opposite, a 3x to 5x separation that the net erases.

The obvious fix is not the fix

Swapping Δnet for flow_state would be a better test with the same structural flaw. The labels are partly mechanical: a net-long book has a bigger gross long leg, so its moves are likelier to dominate the classification and be labelled long_liquidation. The confound §8 found in the sign of pool_net reappears one level down.

§4 proposes the fractional change in the exposed leg instead — Δlong_pool / long_pool on the sell side — a level ratio rather than a category, scale-free, and needing no sign flip. Plus three closures: a within-group primary contrast, a falsification with a pass criterion testing the difference between contrasts directly (the missing criterion is what produced the original verdict), and a side-consistency condition, since TFF's surviving residual is sell −0.0072 against buy +0.0037.

§5 lists what carries over unchanged, so a re-specification does not rewrite the parts that worked: §3.1's vectorisation, the universe rule, the lookbacks and window, the block bootstrap, §5.9's pinning, and §3.2's scope refusal.

Two things worth reading even if this is never executed

§6, the presentation consequence, needs no re-run. When pool_agrees == False, report.format_offside and cot-analyzer's damage page both say the level "would force a book that is not there". That is half right: the forced book is not there, but price reaches the level and flow still follows, only slightly smaller. The row itself is rendered and named — what is wrong is the claim attached to it. See the correction below, which narrows this.

§7 records that waiting does not help. The sample spans 57 blocks of 13 weeks and a further year adds about 4 — roughly 7% more data and 3% tighter standard errors. The original's §1 chose this question partly because its evidence regenerates, which is true in the sense that it is never spent and false in the sense of getting usefully stronger on any horizon worth waiting for.

Also here: §6 corrected, and the work order it releases

Two corrections to §6, both narrowing it, because an overstated work order is how a copy change becomes a claim the data does not carry:

  • "Suppressing the row hides a real event" was wrong. The row is not hidden — it is plotted with a hollow marker, named in the legend, and given a grid cell. What is wrong is only the copy: "would force a book that is not there" implies nothing follows, and something does.
  • The quadrant suppression is correct and stays. Its severe axis is D, a conditional severity for the named pool, so a contradicted row must not be given a cell. Reading instruction 1 of cot-analyzer's damage page has this right.

§6 now also states that the composition split may not enter user-facing copy. "It arrives as fresh shorts" is the untested part of this file.

New handoff: 2026-08-06-trigger-contradicted-copy.md, status open — the only part of this lineage actionable without a re-run. It rests only on what the verdict tested: flow follows a crossing either way (Disaggregated sell, within-group, −0.0190 agreeing against −0.0184 contradicted) and the crossing-specific difference is small.

Its §5 is why it is a handoff and not a three-line patch. The phrase pool on the other side is authored locally in cot-analyzer's damage.py, in two places, and is not in test_damage_vocabulary.py's FRAGMENTS — even though it carries crowdmon's reading exactly as the QUADRANT phrases do. Change crowdmon's copy alone and you ship a producer and a page saying different things, with every test green. So: crowdmon first (named constant, COLUMN_DEFINITIONS, manifest), cot-analyzer second (read it, delete both copies, extend the guard). §4 names five things that must not change while doing it, including that SCHEMA_VERSION must not be bumped.

Plain-language bottom line

The mechanism test asked whether a book that is about to be forced out actually leaves, and measured it by watching the book's net position. But a net position falls both when longs leave and when fresh shorts arrive, and those are opposite situations — one is forced, one is voluntary. Looking instead at which leg actually moved, the two groups behave completely differently, and in the direction the original claim predicted. So the question is more alive than the verdict makes it look.

That is not licence to redo the test today. The obvious repair carries a subtler version of the same bias, the better repair needs data the published panel does not carry, and re-running on the same history is a second look that has to be priced as one. Filed so the next person starts from the corrected design rather than the flawed one.

🤖 Generated with Claude Code

mspinola and others added 2 commits August 6, 2026 19:48
…at cannot answer it

A specification correction, explicitly NOT a work order. Section 7 says what
would have to change before any of it could be executed, and section 8 states the
count executing it would carry. Nothing here changes a published number, a weight,
a threshold in src/, or the verdict.

The mechanism test's section 8 records four places its specification was wrong.
This is a fifth, larger than those four together and invisible until the flow
composition was looked at.

The outcome variable is Δ pool_net. On the sell side an agreeing pool is long and
sells when price falls to F*, while a contradicted pool is short and ADDS shorts
at the same level. Both are sell flow, both give Δnet < 0, and only the first is
forced. The variable cannot separate a forced exit from a fresh entry in the same
direction.

This is not a new insight about futures. It is the opening paragraph of
futures/flow.py: "Net position change cannot tell them apart, because
Δnet = ΔLong − ΔShort is the same either way, and that is the whole reason this
module exists." flow.decompose exists for the distinction and flow_state is a
published column; the frozen section 5 references neither.

Advisory and untested, from the artifact the verdict already commits: on the
published flow_state of the week following a crossing, the named pool does what
the trigger says and the contradicted pool does the opposite, in all four
(report type, side) cells, a 3x to 5x separation the net erases. Disaggregated
sell shows long_liquidation at 20.1% agreeing against 3.9% contradicted, and
new_shorts at 9.4% against 25.8%.

Swapping Δnet for flow_state is NOT the fix and the file says so: the labels are
partly mechanical, because a net-long book has a bigger gross long leg and is
likelier to be labelled long_liquidation, so the same confound reappears one level
down. Section 4 proposes the fractional change in the exposed leg instead
(Δlong_pool/long_pool on the sell side), which is a level ratio rather than a
category and needs no sign flip. It also moves the primary contrast to crossed vs
not crossed WITHIN the agreeing group, gives the falsification a pass criterion
testing the difference between contrasts directly, and adds a side-consistency
condition: TFF's surviving residual is sell -0.0072 against buy +0.0037.

Section 5 lists what carries over unchanged, so a re-specification does not
rewrite the parts that worked. Section 6 is a separate presentation consequence
that needs no re-run: pool_agrees == False currently SUPPRESSES the reading in
report.format_offside and cot-analyzer's damage page, where the measurement says
the level is live and only the flow's composition differs.

Section 7 also records that waiting does not help. The sample spans 57 blocks of
13 weeks and a further year adds about 4, roughly 7% more data and 3% tighter
standard errors. The original's section 1 chose this question partly because its
evidence regenerates, which is true in the sense that it is never spent and false
in the sense of getting usefully stronger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two corrections to section 6 of the respecification, both narrowing it, made
because an overstated work order is how a copy change becomes a claim the data
does not carry:

- "Suppressing the row hides a real event" was wrong. The row is not hidden. It
  is plotted with a hollow marker, named in the legend, and given a grid cell.
  What is wrong is only the copy: "would force a book that is not there" implies
  nothing follows, and something does.
- The quadrant suppression itself is correct and stays. The quadrant's severe
  axis is D, a conditional severity for the NAMED pool, so placing a contradicted
  row in a cell would attach the wrong pool's severity to a real level. Reading
  instruction 1 of cot-analyzer's damage page has this right.

Section 6 also now states that the composition split may not enter user-facing
copy. "It arrives as fresh shorts rather than as long liquidation" is the untested
part of that file, and a rendered surface asserting it would convert an advisory
crosstab into a published claim.

New handoff 2026-08-06-trigger-contradicted-copy.md, status open, the only part of
this lineage actionable without a re-run. It rests only on what the verdict tested:
flow follows a crossing either way (disaggregated sell, within-group, -0.0190
agreeing against -0.0184 contradicted) and the crossing-specific difference is
small (-0.0014 and -0.0027).

Its section 5 is the reason it is a handoff rather than a three-line patch. The
phrase "pool on the other side" is authored locally in cot-analyzer's damage.py in
two places and is NOT in test_damage_vocabulary.py's FRAGMENTS, even though it
carries crowdmon's reading exactly as the QUADRANT phrases do. A session that
changes only crowdmon's copy ships a producer and a page saying different things
with every test green. So the work is: crowdmon first (named constant,
COLUMN_DEFINITIONS, manifest), cot-analyzer second (read it, delete both local
copies, extend the guard).

Section 4 names five things that must not change while doing it, including that
SCHEMA_VERSION must not be bumped, since publish.py records that a bump ahead of a
consumer release loses the whole /damage page rather than the new key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mspinola mspinola changed the title File the forced-flow respecification: the test measured a variable that cannot answer it Respecify the forced-flow test, and file the copy change it releases Aug 6, 2026
Base automatically changed from claude/ffm-outcome to main August 7, 2026 00:03
@mspinola
mspinola merged commit 60b7619 into main Aug 7, 2026
@mspinola
mspinola deleted the claude/ffm-respecification branch August 7, 2026 00:04
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant