Skip to content

fix(lore-0165): publish peg assets in price_usd_series via a zero-weight arm - #188

Merged
karczuRF merged 2 commits into
developfrom
fix/0165_price-usd-series-peg-fill
Aug 10, 2026
Merged

fix(lore-0165): publish peg assets in price_usd_series via a zero-weight arm#188
karczuRF merged 2 commits into
developfrom
fix/0165_price-usd-series-peg-fill

Conversation

@karczuRF

Copy link
Copy Markdown
Collaborator

price_usd_series* emit one row per base asset. USDC is our top-preference quote, so canonicalisation makes it the quote on essentially every pair and the base of almost nothing — it got no row at all. 0 of BE's 1,433 USDC-legged pools were priceable in any window: 67.8% of every never-priced pool they hold.

The control that proves the mechanism: USDC at the canonical issuer is 0/1,433 priceable; USDC at 56 other issuers is 228/233 (97.9%). Same asset code, quote preference the sole variable.

The fix

Both grains now UNION a zero-weight placeholder keyed on the quote leg, before the GROUP BY. Adding 0 to both numerator and denominator cannot perturb a weighted average, so the placeholder's only jobs are to create the group key and flag it. Precedence then falls out of the arithmetic rather than being coded as a rule:

case result
any non-peg asset reduces to the historical expression byte-identically
peg, quote-only bucket the fallback
peg, also traded as base market value wins — the zero-weight row adds 0/0

Three simpler-looking shapes, rejected and documented at the view

  • Appending a $1 row after the GROUP BY emits two rows for one key wherever a peg asset also trades. BE joins on (identity, bucket) → duplicate keys silently double every downstream aggregate.
  • Letting the peg arm own peg identities flattens USDT's 102 genuinely priceable pools from their market rate to $1 — a regression dressed as a fix.
  • An anti-join costs two full FINAL scans, because ClickHouse substitutes CTEs textually.

One deviation from the settled design

The guard is countIf(is_peg = 0) = 0, not the specified sum(w) = 0. They agree everywhere the design enumerated, and differ in one case: a peg asset whose only candles carry volume_base = 0 has traded rows summing to zero weight, where the historical expression yields NULL. sum(w) = 0 would convert that into a fabricated $1 — moving the pre-existing nullIf edge that the ACs require be left untouched in either direction.

Provenance

Adds method ('traded' / 'peg', with 'oracle' reserved for 0168), appended last. Without it, once 0168 lands a real rate a consumer cannot tell a measured 1.0000 from a fallback 1.0000 — the close_usd = 0 mistake in a new surface. The $1 is documented at the view as a placeholder that contradicts our own candles (the oracle tier already prices TF/USDC at close × 0.9993).

Audit of the sibling surfaces

  • usd_reference / _1hclean, they join base and quote.
  • identity_by_contractN/A, reads no candles.
  • current_price_usd — 🔴 confirmed in code to share the base-only defect: current.sql groups by asset_id and quote_asset_id appears nowhere in the MV. Scoped out — it is a refreshable-MV DROP + recreate, the operation that wiped the coarse tables in 0095. Prod measurement query is in the task file.

Tests

New three-case regression over both grains, confirmed to fail without the fix two ways: on the missing method column, and — the behavioural proof — the pre-existing count assertion going 3 → 4, which is USDC appearing for the first time. That assertion change is intentional and annotated.

11 unit + 15 Docker-gated CH integration tests green on the 26.3.10.60 prod pin (the whole prices-clickhouse suite). fmt clean; the 2 clippy warnings are pre-existing on develop, verified by stashing.

⚠️ Not deployed

views.sql needs a privileged applier on ch-prod-01 — CREATE OR REPLACE VIEW requires a DROP VIEW grant unconditionally on 26.3.10.60, which no scoped runtime user has. This merges as a repo change and takes effect on an operator apply. Verification queries — including the USDT non-regression check and its rollback trigger — are in the task file's new Deploy section.

This does not fix GET /assets/{USDC}/ohlcv — that is a different code path, tracked as 0170.

…ght arm

USDC is our top-preference quote, so canonicalisation makes it the quote on
essentially every pair and the base of almost nothing. price_usd_series* emit
one row per BASE asset, so USDC got no row at all: 0 of BE's 1,433 USDC-legged
pools were priceable in any window, 67.8% of every never-priced pool they hold.

Both grains now UNION a zero-weight placeholder keyed on the QUOTE leg before
the GROUP BY. Adding 0 to both numerator and denominator cannot perturb a
weighted average, so the placeholder only creates the group key and flags it,
and precedence falls out of the arithmetic: a non-peg asset reduces to the
historical expression byte-identically, a quote-only peg asset gets the
fallback, and a peg asset that also trades keeps its market value.

Rejected three simpler-looking shapes, documented at the view: appending a $1
row after the GROUP BY emits duplicate keys and silently doubles BE's
aggregates; letting the peg arm own peg identities flattens USDT's 102
priceable pools to $1; the anti-join costs two FINAL scans because ClickHouse
substitutes CTEs textually.

Deviation from the settled design: the guard is countIf(is_peg = 0) = 0, not
sum(w) = 0. The latter would convert a genuine NULL - a peg asset whose only
candles carry volume_base = 0 - into a fabricated $1, moving the pre-existing
nullIf edge the ACs require be left alone.

Adds the `method` provenance column (traded/peg, oracle reserved for 0168) so a
measured 1.0000 is never confusable with a fallback 1.0000, and the $1 is
documented at the view as a placeholder that contradicts our own oracle-tier
candles.

Audit: usd_reference/_1h are clean (they join both legs), identity_by_contract
is N/A, and current_price_usd is confirmed in code to share the base-only
defect - scoped out, it is a refreshable-MV rebuild.

Tests: new three-case regression over both grains, confirmed to fail without
the fix. 11 unit + 15 CH integration tests green on the 26.3.10.60 prod pin.

NOT DEPLOYED - views.sql needs a privileged applier on ch-prod-01.
…MIN as 'traded'

Code review caught the one deliberate deviation and it was wrong. The guard is
now the originally specified sum(w) = 0.

My justification asserted the historical expression "yields NULL via nullIf". It
does not, and I did not test it. close_usd is a NON-NULLABLE Decimal(38,14), so
CAST strips the Nullable nullIf introduces and a zero denominator lands as
Decimal128::MIN (-1701411834604692317316873.03) rather than NULL. The countIf
guard therefore published that value, flagged method='traded', for a peg asset
whose only candles carry zero volume - a catastrophic negative number labelled
as measured, in the column BE multiplies into TVL.

Two more review findings fixed. The "no NULL introduced" assertion was vacuous -
IS NULL on a non-Nullable column is structurally always 0, which is why it never
caught this - and now asserts countIf(toFloat64(close_usd) <= 0) = 0. The 1h
fixture was hoisted above the loop where it had been a precondition disguised as
an iteration step.

Two review claims corrected rather than accepted. Its reproduction fixture (a
single USDT/USDC candle) fails under BOTH guards, because USDT is a base only
there so arm B emits no placeholder and max(is_peg) = 0; sum(w) = 0 only wins
where the peg asset is also a quote leg. Both fixtures were measured before
changing the guard, and the new test uses the one that discriminates. The
residual - any asset whose only priced candles carry zero volume - is
pre-existing and not peg-specific, so it is spawned as 0171 rather than widened
into this task: the fix needs a contract decision with BE, omit the row versus
substitute an unweighted statistic.

Also corrected two overstated comments: arm B is a full second FINAL pass, not a
cheap narrow projection; and arm B is not subject to the close_usd > 0 predicate,
so a peg identity can read status='ok' where usd_reference is empty - intended,
but it means the 12.3 discriminator is not universal.

11 unit + 16 CH integration tests green on the 26.3.10.60 pin. The new
zero-volume test confirmed to fail with countIf and pass with sum(w) = 0.
@karczuRF
karczuRF merged commit d78f6e9 into develop Aug 10, 2026
3 checks passed
@karczuRF
karczuRF deleted the fix/0165_price-usd-series-peg-fill branch August 10, 2026 11:39
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