fix(kalshi): merge live volume_fp from market_updates_raw into market_details - #9689
Conversation
…_details `kalshi.markets_raw.volume_fp` can freeze at creation-time for actively traded markets because the market-feed `markets-sync` cron queries Kalshi with `min_created_ts` and never re-fetches existing markets. The `kalshi_market_details` spell then filters on `volume_fp >= 100` against that stale value, dropping markets that look idle on the raw side but trade heavily in reality (e.g. KXIPLGAME-26MAY15CSKLSG-CSK: 21k trades, 20.5M contracts on 5/15 but markets_raw.volume_fp = 0 since 5/8). This silently dropped 50-91% of recent Kalshi trades from the downstream `kalshi.market_trades` spell — which INNER JOINs market_details on ticker — starting 2026-05-08, the day market-feed PR #110 routed the ticker WebSocket stream into the new `kalshi.market_updates_raw` table instead of refreshing markets_raw. Fix: source live volume_fp from `market_updates_raw` (per-tick state stream), apply the >= 100 filter against the coalesced value, and project the live value downstream. Existing fallback semantics preserved for tickers with no updates in the incremental window. Validated against prod: rescues 469,719 tickers and ~6.75B contracts of trade volume currently being filtered out. Also adds `market_updates_raw` to `sources/kalshi/_sources.yml` and documents the markets_raw freshness caveat on its description. A `--full-refresh` of `kalshi.market_details` (and a downstream rebuild of `kalshi.market_trades`) is needed after merge to recover the ~2 weeks of dropped trades; the incremental cadence alone won't pick up tickers whose `updated_time` is older than the incremental window. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR SummaryMedium Risk Overview Adds a Reviewed by Cursor Bugbot for commit 1364658. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Source timestamp used instead of ingest timestamp
- Changed incremental predicate from ts (source timestamp) to received_hour (ingest timestamp) to prevent silently dropping updates with backdated timestamps.
Or push these changes by commenting:
@cursor push 3b86ca1de5
Preview (3b86ca1de5)
diff --git a/dbt_subprojects/daily_spellbook/models/_projects/kalshi/kalshi_market_details.sql b/dbt_subprojects/daily_spellbook/models/_projects/kalshi/kalshi_market_details.sql
--- a/dbt_subprojects/daily_spellbook/models/_projects/kalshi/kalshi_market_details.sql
+++ b/dbt_subprojects/daily_spellbook/models/_projects/kalshi/kalshi_market_details.sql
@@ -29,7 +29,7 @@
from
{{ source('kalshi', 'market_updates_raw') }}
{% if is_incremental() -%}
- where {{ incremental_predicate('from_unixtime(ts)') }}
+ where {{ incremental_predicate('received_hour') }}
{% endif -%}
group by
market_tickerYou can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 1364658. Configure here.
|
will wait to deploy. |


Summary
kalshi.markets_raw.volume_fpcan freeze at creation-time for actively traded markets because the market-feedmarkets-synccron queries Kalshi withmin_created_tsand never re-fetches existing markets. Thekalshi_market_detailsspell then filters onvolume_fp >= 100against that stale value, dropping markets that look idle on the raw side but trade heavily in reality. The downstreamkalshi.market_tradesspell INNER JOINsmarket_detailsonticker, so any dropped market also drops every trade for it.This regression went live on 2026-05-08, the day market-feed PR #110 routed the ticker WebSocket stream into a new
kalshi.market_updates_rawtable instead of refreshingmarkets_raw. The dbt spell still read frommarkets_rawonly.Measured impact (prod, before this fix)
Example:
KXIPLGAME-26MAY15CSKLSG-CSKhad 21,627 trades / 10M+ contracts on 5/15 butmarkets_raw.volume_fp = 0andupdated_timefrozen at 2026-05-08.market_updates_raw.volume_fp = 20,546,389for the same ticker.Fix
latest_market_updatesCTE that pulls live per-ticker volume fromkalshi.market_updates_raw(scoped byincremental_predicate('from_unixtime(ts)')in incremental runs).markets_rawin themarketsCTE.>= 100filter againstcoalesce(updates.volume_fp, markets_raw.volume_fp)so freshly active markets are no longer dropped.volume_fpdownstream; consumers (incl.kalshi.market_tradesvia the INNER JOIN) get the current value.kalshi.market_updates_rawinsources/kalshi/_sources.yml(the table existed in raw catalog but wasn't wired into spellbook) and add a freshness caveat to themarkets_rawdescription.Validation
Run on prod via Dune (see query
7550793for the per-ticker spot-check and7550797for the aggregate rescue count):KXIPLGAME-26MAY15CSKLSG-CSKnow passes the filter witheffective_volume_fp = 20,546,389(live, from updates_raw) instead of0(stale, from markets_raw).uv run dbt --warn-error compile -s kalshi_market_detailsis clean.kalshi.market_detailsisincrementalonm.updated_time; tickers whoseupdated_timeis older than the incremental window won't refresh on the next scheduled run even after this merge lands. Please run:to recover the ~2 weeks of dropped trades, otherwise the chart at
/collection/prediction-markets/overviewand any other consumer ofprediction_markets.tradesstays partially broken until natural ticker churn catches up.Follow-up (separate PR)
The ingest-side root cause is
market-feed'smarkets-syncusingmin_created_tsas the cursor.ListMarketsParams.MinUpdatedTSalready exists inclient/kalshi.goand would refresh existing markets on state change. Worth filing.Test plan
uv run dbt --warn-error compile -s kalshi_market_details).--full-refreshonkalshi.market_detailsandkalshi.market_tradesin the deploy.kalshi.market_tradesrow count returns to ~3M trades/day post-refresh./collection/prediction-markets/overviewrecovers.🤖 Generated with Claude Code