From 677050a22414eead47847ea8d70291f1ef7f53a9 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Sun, 15 Mar 2026 14:40:15 -0700 Subject: [PATCH] Fix: robust active event discovery --- lib/kalshi/rest.js | 73 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/lib/kalshi/rest.js b/lib/kalshi/rest.js index 4dbec36..1f9aa4e 100644 --- a/lib/kalshi/rest.js +++ b/lib/kalshi/rest.js @@ -1,5 +1,7 @@ import { signRequest, KALSHI_API_BASE } from './auth.js'; +const OPEN_EVENT_STATUSES = new Set(['open', 'active', 'initialized', 'trading']); + async function kalshiFetch(method, path, body = null) { const headers = signRequest(method, path); const opts = { method, headers }; @@ -13,15 +15,78 @@ async function kalshiFetch(method, path, body = null) { return res.json(); } +function getTimeMs(value) { + if (!value) return null; + const ts = new Date(value).getTime(); + return Number.isFinite(ts) ? ts : null; +} + +function getEventCloseTimeMs(event) { + return ( + getTimeMs(event?.close_time) || + getTimeMs(event?.expiration_time) || + getTimeMs(event?.settlement_time) || + getTimeMs(event?.end_date) || + null + ); +} + +function pickBestEvent(events = []) { + const now = Date.now(); + + const ranked = events + .filter(Boolean) + .map((event) => { + const status = String(event.status || '').toLowerCase(); + const closeTs = getEventCloseTimeMs(event); + const openLike = OPEN_EVENT_STATUSES.has(status); + const notClearlyExpired = closeTs == null || closeTs > now - 60_000; + return { event, openLike, closeTs, notClearlyExpired }; + }) + .filter((x) => x.openLike || x.notClearlyExpired); + + if (!ranked.length) return events[0] || null; + + ranked.sort((a, b) => { + if (a.openLike !== b.openLike) return a.openLike ? -1 : 1; + const aTs = a.closeTs ?? Number.MAX_SAFE_INTEGER; + const bTs = b.closeTs ?? Number.MAX_SAFE_INTEGER; + return aTs - bTs; + }); + + return ranked[0].event; +} + +async function fetchEvents(series, query) { + const path = `/trade-api/v2/events?series_ticker=${encodeURIComponent(series)}&${query}`; + const data = await kalshiFetch('GET', path); + return Array.isArray(data.events) ? data.events : []; +} + /** * Get events for the BTC 15-min series. * Returns the currently active event + its markets. */ export async function getActiveBTCEvent() { - const data = await kalshiFetch('GET', '/trade-api/v2/events?series_ticker=KXBTC15M&status=open&limit=1'); - const event = data.events?.[0]; - if (!event) return null; - return event; + const configuredSeries = process.env.KALSHI_SERIES_TICKER || 'KXBTC15M'; + const seriesCandidates = [...new Set([configuredSeries, configuredSeries.toUpperCase(), configuredSeries.toLowerCase()])]; + const eventMap = new Map(); + + for (const series of seriesCandidates) { + for (const query of ['status=open&limit=5', 'limit=25']) { + try { + const events = await fetchEvents(series, query); + for (const event of events) { + if (event?.event_ticker) eventMap.set(event.event_ticker, event); + } + } catch (e) { + console.error(`[Kalshi] Event fetch failed (${series}, ${query}):`, e.message); + } + } + if (eventMap.size) break; + } + + return pickBestEvent([...eventMap.values()]); } /**