From 556cdc0ff1a02e46f2d9cc6edcc9a04ac56a6b77 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Mon, 16 Mar 2026 11:31:45 -0700 Subject: [PATCH] Feat: Export getPositions + getBalance for live --- lib/kalshi/rest.js | 78 +++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/lib/kalshi/rest.js b/lib/kalshi/rest.js index 40460f6..a8d1921 100644 --- a/lib/kalshi/rest.js +++ b/lib/kalshi/rest.js @@ -8,20 +8,17 @@ const DEFAULT_HTTP_RETRIES = Math.max(0, Number(process.env.KALSHI_HTTP_RETRIES const BASE_BACKOFF_MS = Math.max(100, Number(process.env.KALSHI_HTTP_BACKOFF_MS || 350)); const EVENTS_CACHE_TTL_MS = Math.max(1000, Number(process.env.KALSHI_EVENTS_CACHE_TTL_MS || 5000)); -const eventsCache = new Map(); // key -> { expiresAt, data } -const inflightEvents = new Map(); // key -> Promise +const eventsCache = new Map(); +const inflightEvents = new Map(); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); function parseRetryAfterMs(value) { if (!value) return null; - const asSeconds = Number(value); if (Number.isFinite(asSeconds) && asSeconds >= 0) return asSeconds * 1000; - const asDate = new Date(value).getTime(); if (Number.isFinite(asDate)) return Math.max(0, asDate - Date.now()); - return null; } @@ -55,11 +52,7 @@ async function kalshiFetch(method, path, body = null, opts = {}) { if (res.status === 204) return {}; const text = await res.text(); if (!text) return {}; - try { - return JSON.parse(text); - } catch { - return {}; - } + try { return JSON.parse(text); } catch { return {}; } } const text = await res.text(); @@ -95,20 +88,16 @@ function getEventCloseTimeMs(event) { function rankEvents(events = []) { const now = Date.now(); - return events .filter(Boolean) .map((event) => { const status = String(event.status || '').toLowerCase(); const closeTs = getEventCloseTimeMs(event); - const openLike = OPEN_EVENT_STATUSES.has(status); const tradableLike = TRADABLE_EVENT_STATUSES.has(status); const notClearlyExpired = closeTs == null || closeTs > now - 60_000; - const delta = closeTs == null ? Number.MAX_SAFE_INTEGER : closeTs - now; const closenessScore = delta < 0 ? Math.abs(delta) + 3_600_000 : delta; - return { event, openLike, tradableLike, notClearlyExpired, closenessScore }; }) .filter((x) => x.openLike || x.notClearlyExpired) @@ -124,18 +113,12 @@ function rankEvents(events = []) { function getCachedEvents(key) { const hit = eventsCache.get(key); if (!hit) return null; - if (Date.now() > hit.expiresAt) { - eventsCache.delete(key); - return null; - } + if (Date.now() > hit.expiresAt) { eventsCache.delete(key); return null; } return hit.data; } function setCachedEvents(key, events) { - eventsCache.set(key, { - expiresAt: Date.now() + EVENTS_CACHE_TTL_MS, - data: events - }); + eventsCache.set(key, { expiresAt: Date.now() + EVENTS_CACHE_TTL_MS, data: events }); } async function fetchEvents(series, query) { @@ -165,22 +148,16 @@ async function fetchEvents(series, query) { return task; } -/** - * Return ranked candidate events for BTC 15m. - */ export async function getActiveBTCEvents(limit = 12) { const seriesCandidates = [SERIES_TICKER]; const eventMap = new Map(); for (const series of seriesCandidates) { try { - // Use only known-good filter to avoid 400s from unsupported statuses. const openEvents = await fetchEvents(series, 'status=open&limit=25'); for (const event of openEvents) { if (event?.event_ticker) eventMap.set(event.event_ticker, event); } - - // Fallback if endpoint returns empty. if (!openEvents.length) { const fallbackEvents = await fetchEvents(series, 'limit=25'); for (const event of fallbackEvents) { @@ -195,51 +172,60 @@ export async function getActiveBTCEvents(limit = 12) { return rankEvents([...eventMap.values()]).slice(0, limit); } -/** - * Backward-compatible: return single best candidate event. - */ export async function getActiveBTCEvent() { const events = await getActiveBTCEvents(1); return events[0] || null; } -/** - * Get markets for a specific event ticker. - */ export async function getEventMarkets(eventTicker) { const data = await kalshiFetch('GET', `/trade-api/v2/events/${eventTicker}`); const markets = data?.event?.markets ?? data?.markets ?? data?.event_markets ?? []; return Array.isArray(markets) ? markets : []; } -/** - * Get orderbook for a specific market ticker. - */ export async function getOrderbook(ticker) { const data = await kalshiFetch('GET', `/trade-api/v2/markets/${ticker}/orderbook`); return data.orderbook || data; } -/** - * Get single market details. - */ export async function getMarket(ticker) { const data = await kalshiFetch('GET', `/trade-api/v2/markets/${ticker}`); return data.market || data; } -/** - * Place a real order on Kalshi. NOT used in paper mode. - */ export async function placeOrder(params) { return kalshiFetch('POST', '/trade-api/v2/portfolio/orders', params); } -/** - * Get wallet balance. - */ export async function getBalance() { return kalshiFetch('GET', '/trade-api/v2/portfolio/balance'); } +export async function getPositions(params = {}) { + let path = '/trade-api/v2/portfolio/positions'; + const qs = []; + if (params.settlement_status) qs.push(`settlement_status=${params.settlement_status}`); + if (params.limit) qs.push(`limit=${params.limit}`); + if (params.event_ticker) qs.push(`event_ticker=${params.event_ticker}`); + if (qs.length) path += `?${qs.join('&')}`; + return kalshiFetch('GET', path); +} + +export async function getOrder(orderId) { + return kalshiFetch('GET', `/trade-api/v2/portfolio/orders/${orderId}`); +} + +export async function cancelOrder(orderId) { + return kalshiFetch('DELETE', `/trade-api/v2/portfolio/orders/${orderId}`); +} + +export async function getFills(params = {}) { + let path = '/trade-api/v2/portfolio/fills'; + const qs = []; + if (params.ticker) qs.push(`ticker=${params.ticker}`); + if (params.limit) qs.push(`limit=${params.limit}`); + if (qs.length) path += `?${qs.join('&')}`; + return kalshiFetch('GET', path); +} + export { kalshiFetch };