Files
KalBot/lib/kalshi/rest.js

147 lines
4.4 KiB
JavaScript

import { signRequest, KALSHI_API_BASE } from './auth.js';
const SERIES_TICKER = 'KXBTC15M';
const OPEN_EVENT_STATUSES = new Set(['open', 'active', 'initialized', 'trading']);
const TRADABLE_EVENT_STATUSES = new Set(['open', 'active', 'trading']);
async function kalshiFetch(method, path, body = null) {
const headers = signRequest(method, path);
const opts = { method, headers };
if (body) opts.body = JSON.stringify(body);
const res = await fetch(`${KALSHI_API_BASE}${path}`, opts);
if (!res.ok) {
const text = await res.text();
throw new Error(`Kalshi API ${method} ${path}${res.status}: ${text}`);
}
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 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;
// Prefer near-future close times; heavily penalize stale/past
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, closeTs };
})
.filter((x) => x.openLike || x.notClearlyExpired)
.sort((a, b) => {
if (a.tradableLike !== b.tradableLike) return a.tradableLike ? -1 : 1;
if (a.openLike !== b.openLike) return a.openLike ? -1 : 1;
if (a.notClearlyExpired !== b.notClearlyExpired) return a.notClearlyExpired ? -1 : 1;
return a.closenessScore - b.closenessScore;
})
.map((x) => x.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 : [];
}
/**
* Return ranked candidate events for BTC 15m.
*/
export async function getActiveBTCEvents(limit = 12) {
const seriesCandidates = [...new Set([SERIES_TICKER, SERIES_TICKER.toLowerCase()])];
const eventMap = new Map();
for (const series of seriesCandidates) {
for (const query of [
'status=open&limit=10',
'status=active&limit=10',
'status=initialized&limit=10',
'limit=50'
]) {
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);
}
}
}
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 { kalshiFetch };