mirror of
https://github.com/multipleof4/KalBot.git
synced 2026-03-17 05:51:02 +00:00
Fix: Rank BTC events and expose candidates
This commit is contained in:
@@ -2,6 +2,7 @@ import { signRequest, KALSHI_API_BASE } from './auth.js';
|
|||||||
|
|
||||||
const SERIES_TICKER = 'KXBTC15M';
|
const SERIES_TICKER = 'KXBTC15M';
|
||||||
const OPEN_EVENT_STATUSES = new Set(['open', 'active', 'initialized', 'trading']);
|
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) {
|
async function kalshiFetch(method, path, body = null) {
|
||||||
const headers = signRequest(method, path);
|
const headers = signRequest(method, path);
|
||||||
@@ -32,30 +33,33 @@ function getEventCloseTimeMs(event) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickBestEvent(events = []) {
|
function rankEvents(events = []) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
const ranked = events
|
return events
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((event) => {
|
.map((event) => {
|
||||||
const status = String(event.status || '').toLowerCase();
|
const status = String(event.status || '').toLowerCase();
|
||||||
const closeTs = getEventCloseTimeMs(event);
|
const closeTs = getEventCloseTimeMs(event);
|
||||||
|
|
||||||
const openLike = OPEN_EVENT_STATUSES.has(status);
|
const openLike = OPEN_EVENT_STATUSES.has(status);
|
||||||
|
const tradableLike = TRADABLE_EVENT_STATUSES.has(status);
|
||||||
const notClearlyExpired = closeTs == null || closeTs > now - 60_000;
|
const notClearlyExpired = closeTs == null || closeTs > now - 60_000;
|
||||||
return { event, openLike, closeTs, notClearlyExpired };
|
|
||||||
|
// 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);
|
.filter((x) => x.openLike || x.notClearlyExpired)
|
||||||
|
.sort((a, b) => {
|
||||||
if (!ranked.length) return events[0] || null;
|
if (a.tradableLike !== b.tradableLike) return a.tradableLike ? -1 : 1;
|
||||||
|
|
||||||
ranked.sort((a, b) => {
|
|
||||||
if (a.openLike !== b.openLike) return a.openLike ? -1 : 1;
|
if (a.openLike !== b.openLike) return a.openLike ? -1 : 1;
|
||||||
const aTs = a.closeTs ?? Number.MAX_SAFE_INTEGER;
|
if (a.notClearlyExpired !== b.notClearlyExpired) return a.notClearlyExpired ? -1 : 1;
|
||||||
const bTs = b.closeTs ?? Number.MAX_SAFE_INTEGER;
|
return a.closenessScore - b.closenessScore;
|
||||||
return aTs - bTs;
|
})
|
||||||
});
|
.map((x) => x.event);
|
||||||
|
|
||||||
return ranked[0].event;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchEvents(series, query) {
|
async function fetchEvents(series, query) {
|
||||||
@@ -65,15 +69,19 @@ async function fetchEvents(series, query) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get events for the BTC 15-min series.
|
* Return ranked candidate events for BTC 15m.
|
||||||
* Returns the currently active event + its markets.
|
|
||||||
*/
|
*/
|
||||||
export async function getActiveBTCEvent() {
|
export async function getActiveBTCEvents(limit = 12) {
|
||||||
const seriesCandidates = [...new Set([SERIES_TICKER, SERIES_TICKER.toLowerCase()])];
|
const seriesCandidates = [...new Set([SERIES_TICKER, SERIES_TICKER.toLowerCase()])];
|
||||||
const eventMap = new Map();
|
const eventMap = new Map();
|
||||||
|
|
||||||
for (const series of seriesCandidates) {
|
for (const series of seriesCandidates) {
|
||||||
for (const query of ['status=open&limit=5', 'limit=25']) {
|
for (const query of [
|
||||||
|
'status=open&limit=10',
|
||||||
|
'status=active&limit=10',
|
||||||
|
'status=initialized&limit=10',
|
||||||
|
'limit=50'
|
||||||
|
]) {
|
||||||
try {
|
try {
|
||||||
const events = await fetchEvents(series, query);
|
const events = await fetchEvents(series, query);
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
@@ -83,10 +91,17 @@ export async function getActiveBTCEvent() {
|
|||||||
console.error(`[Kalshi] Event fetch failed (${series}, ${query}):`, e.message);
|
console.error(`[Kalshi] Event fetch failed (${series}, ${query}):`, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (eventMap.size) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pickBestEvent([...eventMap.values()]);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,7 +109,8 @@ export async function getActiveBTCEvent() {
|
|||||||
*/
|
*/
|
||||||
export async function getEventMarkets(eventTicker) {
|
export async function getEventMarkets(eventTicker) {
|
||||||
const data = await kalshiFetch('GET', `/trade-api/v2/events/${eventTicker}`);
|
const data = await kalshiFetch('GET', `/trade-api/v2/events/${eventTicker}`);
|
||||||
return data.event?.markets || [];
|
const markets = data?.event?.markets ?? data?.markets ?? data?.event_markets ?? [];
|
||||||
|
return Array.isArray(markets) ? markets : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user