Real-Time Prediction Market Monitoring
Lesson 1 of 2
Setting Up Kalshi Market Streaming
Estimated time: 10 minutes
Setting Up Kalshi Market Streaming
Kalshi API Rate Limits
Kalshi allows 10 requests per second. For monitoring multiple markets, polling alone won't scale. You need smart strategies.
Strategy 1: Batch Polling
Fetch multiple markets in one request:
const fetch = require('node-fetch');
async function batchFetchMarkets(tickers) {
const promises = tickers.map(ticker =>
fetch(`https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=${ticker}`)
.then(r => r.json())
);
return Promise.all(promises);
}
const markets = await batchFetchMarkets(['KXSB', 'KXEL', 'KXMA']);
Strategy 2: Polling with Exponential Backoff
Handle rate limiting gracefully:
async function fetchWithBackoff(url, maxRetries = 3) {
let delay = 100; // Start at 100ms
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const res = await fetch(url);
if (res.status === 429) { // Rate limited
console.log(`Rate limited. Waiting ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // Double the wait time
continue;
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (e) {
if (attempt < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2;
} else {
throw e;
}
}
}
}
Strategy 3: In-Memory Cache with TTL
Cache market data to reduce API calls:
class MarketCache {
constructor(ttlMs = 5000) {
this.cache = new Map();
this.ttlMs = ttlMs;
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > this.ttlMs) {
this.cache.delete(key);
return null;
}
return entry.data;
}
set(key, data) {
this.cache.set(key, { data, timestamp: Date.now() });
}
}
const cache = new MarketCache(5000); // 5 second TTL
async function getMarketData(ticker) {
const cached = cache.get(ticker);
if (cached) return cached;
const data = await fetchWithBackoff(
`https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=${ticker}`
);
cache.set(ticker, data);
return data;
}
Strategy 4: Interval-Based Polling
Poll at controlled intervals:
class MarketMonitor {
constructor(pollIntervalMs = 30000) {
this.pollIntervalMs = pollIntervalMs;
this.watchers = new Map();
this.pollingActive = false;
}
watch(ticker) {
this.watchers.set(ticker, { lastFetch: 0 });
if (!this.pollingActive) this.startPolling();
}
startPolling() {
this.pollingActive = true;
setInterval(async () => {
for (const [ticker, state] of this.watchers) {
try {
const data = await getMarketData(ticker);
this.onMarketUpdate(ticker, data);
state.lastFetch = Date.now();
} catch (e) {
console.error(`Failed to fetch ${ticker}:`, e);
}
}
}, this.pollIntervalMs);
}
onMarketUpdate(ticker, data) {
// Override this to handle updates
console.log(`${ticker}: ${data.markets.length} markets`);
}
}
const monitor = new MarketMonitor(30000); // Poll every 30 seconds
monitor.watch('KXSB');
monitor.watch('KXEL');
Handling Market Status
Kalshi markets have statuses. Only "active" markets have live odds:
function filterActiveMarkets(markets) {
return markets.filter(m => m.status === 'active');
}
Next Steps
Combine this polling strategy with a database (SQLite, PostgreSQL) to log price changes over time. Track volatility and detect sentiment shifts.