Skip to content

feat(compliance): the asymmetric lower-bound liquidity gate (#696, pa… - #711

Merged
eaitbrahim merged 2 commits into
mainfrom
feat-696-asymmetric-gate
Sep 3, 2026
Merged

feat(compliance): the asymmetric lower-bound liquidity gate (#696, pa…#711
eaitbrahim merged 2 commits into
mainfrom
feat-696-asymmetric-gate

Conversation

@eaitbrahim

Copy link
Copy Markdown
Contributor

…rt 2 of 2)

Part 1 recorded WHICH feed a series came from. This is what reads it.

THE GATE. Venue volume is a LOWER BOUND on consolidated volume, never an upper one, so the two directions are not equally informative and the gate treats them differently:

  • at or above the floor, on ANY feed -> CONCLUSIVE PASS. A name that traded that much on one venue alone necessarily traded at least that much in total. A gate that refused here would ban MSFT and AAPL for thinness they do not have, and would turn a data-vendor pricing tier into a prerequisite for running the engine at all.
  • below the floor, on a partial feed -> NOT A VERDICT. Equally consistent with a thin asset and with a liquid one that barely trades HERE. Refused as liquidity_unmeasured, naming the feed and both dollar figures, and saying consolidated volume is UNMEASURED rather than low. A test asserts the line never contains "illiquid" or "too thin": the refusal must not assert the half it cannot see.
  • below the floor, on a consolidated feed -> unchanged. The crypto path is untouched, because Coinbase's own volume IS the scale the floor was calibrated against.
  • below the floor, scope UNRECORDED -> unchanged verdict, annotated. Every series cached before v17 is unrecorded, and treating unrecorded as partial would refuse the entire existing universe over metadata nobody wrote down. It self-heals as series are re-fetched.

market_facts reads ONE_DAY provenance specifically -- that is the granularity the statistic medians, so an hourly series on another feed says nothing about it.

doctor gains data.feed_scope, WARN and never FAIL: a free single-venue tier is a legitimate configuration, and the bound admits its liquid names honestly. What is not legitimate is not knowing, and the screen only speaks for a candidate somebody is actively screening while slippage_for_quote_volume prices off the same number every cycle in silence. Partial and unrecorded series are reported SEPARATELY: one should be re-fetched under a different feed, the other may already be consolidated.

The cost-fidelity record is amended rather than rewritten (docs/experiments is append-only). The amendment retracts two claims that were asserted rather than measured -- "roughly 2% ... approximately IEX's share" (IEX publishes ~3.8% for Q2 2026, so the figure was simply wrong) and the "~50x" understatement -- notes that no number in the document depended on either, and records that what shipped is better than what that section proposed.

Tests (written first, red before green): 27 across three files.
tests/compliance/test_liquidity_lower_bound.py -- 11
tests/compliance/test_market_facts_carries_feed_scope.py -- 6
tests/commands/test_doctor_feed_scope.py -- 8 (+2 existing)

Two EXISTING reachability guards -- one in tests/compliance, one in tests/test_proposer -- correctly failed: they assert every DATA_DERIVED_FAILURES tag really appears in screen_asset output, and liquidity / liquidity_unmeasured are mutually exclusive by construction, so no single set of facts emits both. Both now screen the partial-feed arm too, rather than the tag being dropped as unreachable.

Mutation-verified, 7 mutants. One SURVIVED first time: gutting the fix line's explanatory half left keel fetch intact, and the assertion only looked for "keel". Re-fetching under the SAME feed changes nothing, so the pin now requires the word "consolidated" -- an operator who re-fetches identically gets the same warning and concludes the report is broken.

Closes #696

Claude-Session: https://claude.ai/code/session_01NzuKAe2RVrPt9acVAWjRyL

What & why

Tests-first evidence

  • Tests written first, seen failing for the right reason

Gates (all must pass)

  • uv run ruff check clean
  • uv run mypy clean
  • uv run pytest -q green

Scope check

  • This PR touches a rail or a default classification — checked means it DOES;
    leave checked only if true, and if so: cite the source and open the discussion
    BEFORE review (CONTRIBUTING.md, "Governance: rulings vs. machinery").
  • New dependency added (needs discussion first)

eaitbrahim and others added 2 commits September 3, 2026 13:21
…rt 2 of 2)

Part 1 recorded WHICH feed a series came from. This is what reads it.

THE GATE. Venue volume is a LOWER BOUND on consolidated volume, never an upper
one, so the two directions are not equally informative and the gate treats them
differently:

* at or above the floor, on ANY feed -> CONCLUSIVE PASS. A name that traded that
  much on one venue alone necessarily traded at least that much in total. A gate
  that refused here would ban MSFT and AAPL for thinness they do not have, and
  would turn a data-vendor pricing tier into a prerequisite for running the
  engine at all.
* below the floor, on a partial feed -> NOT A VERDICT. Equally consistent with a
  thin asset and with a liquid one that barely trades HERE. Refused as
  `liquidity_unmeasured`, naming the feed and both dollar figures, and saying
  consolidated volume is UNMEASURED rather than low. A test asserts the line
  never contains "illiquid" or "too thin": the refusal must not assert the half
  it cannot see.
* below the floor, on a consolidated feed -> unchanged. The crypto path is
  untouched, because Coinbase's own volume IS the scale the floor was calibrated
  against.
* below the floor, scope UNRECORDED -> unchanged verdict, annotated. Every series
  cached before v17 is unrecorded, and treating unrecorded as partial would refuse
  the entire existing universe over metadata nobody wrote down. It self-heals as
  series are re-fetched.

`market_facts` reads ONE_DAY provenance specifically -- that is the granularity
the statistic medians, so an hourly series on another feed says nothing about it.

`doctor` gains `data.feed_scope`, WARN and never FAIL: a free single-venue tier is
a legitimate configuration, and the bound admits its liquid names honestly. What
is not legitimate is not knowing, and the screen only speaks for a candidate
somebody is actively screening while `slippage_for_quote_volume` prices off the
same number every cycle in silence. Partial and unrecorded series are reported
SEPARATELY: one should be re-fetched under a different feed, the other may already
be consolidated.

The cost-fidelity record is amended rather than rewritten (`docs/experiments` is
append-only). The amendment retracts two claims that were asserted rather than
measured -- "roughly 2% ... approximately IEX's share" (IEX publishes ~3.8% for
Q2 2026, so the figure was simply wrong) and the "~50x" understatement -- notes
that no number in the document depended on either, and records that what shipped
is better than what that section proposed.

Tests (written first, red before green): 27 across three files.
  tests/compliance/test_liquidity_lower_bound.py            -- 11
  tests/compliance/test_market_facts_carries_feed_scope.py  -- 6
  tests/commands/test_doctor_feed_scope.py                  -- 8 (+2 existing)

Two EXISTING reachability guards -- one in tests/compliance, one in tests/test_proposer
-- correctly failed: they assert every `DATA_DERIVED_FAILURES` tag really appears
in `screen_asset` output, and `liquidity` / `liquidity_unmeasured` are mutually
exclusive by construction, so no single set of facts emits both. Both now screen
the partial-feed arm too, rather than the tag being dropped as unreachable.

Mutation-verified, 7 mutants. One SURVIVED first time: gutting the fix line's
explanatory half left `keel fetch` intact, and the assertion only looked for
"keel". Re-fetching under the SAME feed changes nothing, so the pin now requires
the word "consolidated" -- an operator who re-fetches identically gets the same
warning and concludes the report is broken.

Closes #696

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzuKAe2RVrPt9acVAWjRyL
Three findings from the review of #711. The first silenced the report in the exact
configuration the feature exists for.

1. `feed_scope_findings` returned early on the partial group, so unrecorded series
   were dropped whenever any partial series existed -- which is the ORDINARY state
   of this deployment: equities on a single-venue feed alongside crypto series
   cached before provenance existed. Verified: `{MSFT-USD: (alpaca:iex,),
   ETH-USD: ()}` reported only MSFT.

   Every test in the new file exercised one category alone, which is exactly how
   that survived a suite written test-first. Both groups now report in ONE finding
   -- one rather than two so `data.feed_scope` stays a single name for the
   name-coverage pin -- with a headline counting both and a fix naming both
   remedies, which are genuinely different actions: a bounded series needs
   re-fetching under a CONSOLIDATED feed, an unrecorded one needs re-fetching
   under any feed just to stamp it.

2. The doctor hook enumerated `products x granularities` unconditionally, so a
   never-fetched series read as "predates feed provenance". On the seeded test
   database that produced nine phantom warnings. A series with no bars has no
   provenance because it has no BARS, and `data.missing` already reports it. The
   hook now filters on `n_candles > 0`, reusing the `assess_products` rows the
   health report was already computing rather than sweeping twice.

3. `f"{Decimal('5E+5'):,}"` is `5E+5`, not `500,000` -- Decimal keeps whatever
   exponent its arithmetic produced, and `median = volume * close` can land there.
   The refusal line is what an operator reads to decide whether a series is worth
   re-fetching, and `5E+5` against `1,000,000` is not a comparison anyone should
   be asked to make. Both figures now go through a `_dollars` helper that
   normalises the exponent away.

Tests written first, red before green: 4 new (3 on the coexisting groups, 1 on the
exponent form) plus `test_feed_scope_ignores_series_with_no_bars`, which failed
with all nine phantom series before the fix.

Mutation-verified, 3 mutants, all killed: the early return restored; the
bar-count filter widened to `>= 0`; the dollar helper's `quantize` removed.

Refs #696

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzuKAe2RVrPt9acVAWjRyL
@eaitbrahim
eaitbrahim merged commit a34e78a into main Sep 3, 2026
4 checks passed
@eaitbrahim
eaitbrahim deleted the feat-696-asymmetric-gate branch September 3, 2026 18:47
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.

Volume-keyed models read every US equity as ~50x thinner than it is: the IEX feed reports ~2% of consolidated volume

1 participant