feat(predictions): add Kalshi as prediction market data source#1355
feat(predictions): add Kalshi as prediction market data source#1355koala73 merged 10 commits intokoala73:mainfrom
Conversation
|
@Rau1CS is attempting to deploy a commit to the Elie Team on Vercel. A member of the Team first needs to authorize it. |
koala73
left a comment
There was a problem hiding this comment.
Thanks for adding Kalshi as a second prediction source, @Rau1CS! Great work on the parallel fetch pattern and graceful degradation. A few items to address:
BLOCKING
-
list-prediction-markets.ts:190— Kalshi fetch fires unconditionally, even when not needed
kalshiFetchstarts on every request, including tech-scoped ones whereincludeKalshi(line 278) will befalse. This wastes a network call + Redis read for every tech panel load. Restructure so the category check happens before kicking off the fetch. -
list-prediction-markets.ts:214— Bootstrap source detection is fragile(m as unknown as { source?: string }).source === 'kalshi'
Double cast through
unknownis a code smell. Define aBootstrapMarketinterface and use it for the bootstrap type instead. -
PredictionPanel.ts:48— Source comparison uses raw string, not the enum valueconst sourceLabel = p.source === 'kalshi' ? 'Kalshi' : 'Polymarket';
The proto sends
MarketSourceenum values (MARKET_SOURCE_KALSHI), but the panel compares against'kalshi'. This will likely always show "Polymarket". Verify which format the client actually receives.
SUGGESTION
-
seed-prediction-markets.mjs:108— Kalshi volume filtering threshold is low.
volume <= 1000is very permissive compared to Polymarket'sminVolume: 5000in the Gamma API params. Consider raising for signal quality parity. -
list-prediction-markets.ts:162-170vsseed-prediction-markets.mjs:108-118— Different market selection logic.
The seed script picks the highest-volume market per event (viareduce), but the RPC handler picks the first active binary market (breakafter first match). These should use the same selection logic. -
seed-prediction-markets.mjs:117— Falsy zero-price bugconst yesPrice = +(parseFloat(topMarket.last_price_dollars) * 100).toFixed(1) || 50;
If
last_price_dollarsis"0", this evaluates to0(falsy), falling back to50. A zero-price market would incorrectly show 50%. Use?? 50instead of|| 50. -
No CSS for
.prediction-sourcebadge. The panel adds<span class="prediction-source">but no styles are included in the diff. Will render as unstyled inline text. -
FINANCE_TAGSduplicated acrossseed-prediction-markets.mjs,list-prediction-markets.ts(inline check), andsrc/services/prediction/index.ts. Consider a shared constant. -
Kalshi API URL uses
api.elections.kalshi.com, which is the elections-specific subdomain. Verify this returns non-election markets too, or use the generaltrading-api.kalshi.comendpoint.
NITPICK
-
open_interestis added to the proto but always0for Polymarket and never displayed. Consider deferring this proto field if it's not used yet. -
Generated files (
service_client.ts,service_server.ts) appear committed manually. Confirm these matchbuf generateoutput.
- Gate Kalshi fetch behind category check to avoid wasted calls on tech-scoped requests - Replace fragile double-cast bootstrap typing with BootstrapMarket interface - Fix zero-price falsy bug in seed script using Number.isFinite guard - Align RPC market selection with seed script (highest-volume via single-pass loop) - Raise Kalshi volume threshold to 5000 for signal quality parity - Add missing .prediction-source badge CSS with per-source color variants
|
Thanks for the thorough review! All items addressed in e767132 — here's the rundown: BLOCKING (all fixed)
SUGGESTIONS (all fixed) Items 8-11 (shared constants, Kalshi API subdomain, open_interest proto field, generated files) not addressed in this pass — happy to follow up if you'd like. |
koala73
left a comment
There was a problem hiding this comment.
Sorry for the delay on this review, @Rau1CS. Nice work adding Kalshi as a second source. The parallel fetch pattern and graceful degradation are well done. A few issues to address:
P1: Live Kalshi path missing curation filters (seed/RPC divergence)
The seed script applies isExcluded() and a volume <= 5000 threshold, but fetchKalshiMarkets() in list-prediction-markets.ts applies neither. Users hitting the live RPC fallback (cache miss or stale bootstrap) will see unfiltered, low-volume, and potentially sports/entertainment markets that the seed path would have excluded.
Files: server/worldmonitor/prediction/v1/list-prediction-markets.ts (around line 184), scripts/seed-prediction-markets.mjs (line 113)
P2: FINANCE_TAGS includes markets but seed never fetches it
allTags in the seed script is [...GEOPOLITICAL_TAGS, ...TECH_TAGS]. The markets tag from FINANCE_TAGS is never fetched from the Gamma API, so finance bootstrap is missing those Polymarket events entirely.
File: scripts/seed-prediction-markets.mjs (line 148)
P2: Kalshi title mapping differs between seed and RPC
Seed uses topMarket.title || event.title (line 134), but the RPC handler uses market.yes_sub_title || market.title (line 150). The same contract can render with a different label depending on which code path served it, which also affects query filtering behavior.
Files: scripts/seed-prediction-markets.mjs (line 134), server/worldmonitor/prediction/v1/list-prediction-markets.ts (line 150)
P2: Finance variant falls back to geopolitical on cache miss
Both src/services/prediction/index.ts (line 64) and list-prediction-markets.ts (line 220) fall back to bootstrap.geopolitical when bootstrap.finance is absent. Older cached payloads without a finance key will silently serve geopolitical data to finance-variant users instead of falling through to the RPC path.
Files: src/services/prediction/index.ts (line 64), server/worldmonitor/prediction/v1/list-prediction-markets.ts (line 220)
- Apply isExcluded() filter and volume threshold (5000) to live Kalshi RPC path so cache-miss results match seed curation quality - Include FINANCE_TAGS in seed allTags so 'markets' tag is fetched - Align Kalshi title mapping (market.title || event.title) between seed and RPC handler - Remove silent geopolitical fallback for finance variant so missing finance bootstrap falls through to RPC fetch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
For multi-contract Kalshi events (e.g. papal election candidates), market.title is the generic event question while yes_sub_title identifies the specific contract. Use yes_sub_title when present in both seed and RPC paths so titles are accurate and consistent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Sorry for the delay — all P1/P2 items from the latest review are addressed in the last two commits. Ready for another look when you get a chance! |
|
Thanks a lot for the solid work on this, @Rau1CS! The parallel fetch pattern, graceful degradation, and seed/RPC parity fixes from the last two rounds are all looking great. This is almost ready to merge. Three suggestions before we close it out:
Items 1 is the most important. 2 and 3 can be follow-up PRs if you'd rather not block on them. Let me know! |
Switch from api.elections.kalshi.com (elections-only) to trading-api.kalshi.com so economy, crypto, and other non-election markets are included in the finance variant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the review!
|
Merge main's filterAndScore/tagRegions/input-sanitization changes with PR's Kalshi integration: - FINANCE_TAGS: union of both branches (interest-rates, recession, trade, tariffs, debt-ceiling from main + crypto, business, markets from PR) - Seed: use filterAndScore() for all variants, add source=kalshi filter for finance bucket - RPC: keep parallel Gamma+Kalshi fetch, use sanitized category/query from main, add source + openInterest to bootstrap mapping - Client: keep both source and regions fields on PredictionMarket
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…73#1355) * feat(predictions): add Kalshi as prediction market data source * fix(predictions): address Kalshi integration review feedback - Gate Kalshi fetch behind category check to avoid wasted calls on tech-scoped requests - Replace fragile double-cast bootstrap typing with BootstrapMarket interface - Fix zero-price falsy bug in seed script using Number.isFinite guard - Align RPC market selection with seed script (highest-volume via single-pass loop) - Raise Kalshi volume threshold to 5000 for signal quality parity - Add missing .prediction-source badge CSS with per-source color variants * fix(predictions): address P1/P2 review items for Kalshi integration - Apply isExcluded() filter and volume threshold (5000) to live Kalshi RPC path so cache-miss results match seed curation quality - Include FINANCE_TAGS in seed allTags so 'markets' tag is fetched - Align Kalshi title mapping (market.title || event.title) between seed and RPC handler - Remove silent geopolitical fallback for finance variant so missing finance bootstrap falls through to RPC fetch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(predictions): prefer yes_sub_title for Kalshi multi-contract events For multi-contract Kalshi events (e.g. papal election candidates), market.title is the generic event question while yes_sub_title identifies the specific contract. Use yes_sub_title when present in both seed and RPC paths so titles are accurate and consistent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(predictions): use general Kalshi trading API subdomain Switch from api.elections.kalshi.com (elections-only) to trading-api.kalshi.com so economy, crypto, and other non-election markets are included in the finance variant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Elie Habib <elie.habib@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Test plan
npm run typecheck— no new errorsnpm run dev(full variant) — predictions panel loads with Polymarket marketsnpm run dev:finance— predictions panel shows Kalshi + economy Polymarket marketsCloses #1285