Fix: Skip empty events and pick tradable market

This commit is contained in:
2026-03-15 14:54:51 -07:00
parent 51177b5b8a
commit d57c0402d1

View File

@@ -1,8 +1,9 @@
import { getActiveBTCEvent, getEventMarkets, getOrderbook, getMarket } from '../kalshi/rest.js';
import { getActiveBTCEvents, getEventMarkets, getOrderbook, getMarket } from '../kalshi/rest.js';
import { KalshiWS } from '../kalshi/websocket.js';
import { EventEmitter } from 'events';
const OPEN_MARKET_STATUSES = new Set(['open', 'active', 'initialized', 'trading']);
const TRADABLE_MARKET_STATUSES = new Set(['open', 'active', 'trading']);
/**
* Tracks the currently active BTC 15-min market.
@@ -79,35 +80,95 @@ export class MarketTracker extends EventEmitter {
};
}
_toTs(value) {
if (!value) return null;
const ts = new Date(value).getTime();
return Number.isFinite(ts) ? ts : null;
}
_pickBestMarket(markets = []) {
const now = Date.now();
const ranked = markets
.filter(Boolean)
.map((market) => {
const status = String(market?.status || '').toLowerCase();
const closeTs =
this._toTs(market?.close_time) ||
this._toTs(market?.expiration_time) ||
this._toTs(market?.settlement_time) ||
null;
const tradable = TRADABLE_MARKET_STATUSES.has(status);
const openLike = OPEN_MARKET_STATUSES.has(status);
const notClearlyExpired = closeTs == null || closeTs > now - 60_000;
return { market, tradable, openLike, notClearlyExpired, closeTs };
})
.filter((x) => x.openLike || x.notClearlyExpired);
if (!ranked.length) return markets[0] || null;
ranked.sort((a, b) => {
if (a.tradable !== b.tradable) return a.tradable ? -1 : 1;
if (a.openLike !== b.openLike) return a.openLike ? -1 : 1;
if (a.notClearlyExpired !== b.notClearlyExpired) return a.notClearlyExpired ? -1 : 1;
const aTs = a.closeTs ?? Number.MAX_SAFE_INTEGER;
const bTs = b.closeTs ?? Number.MAX_SAFE_INTEGER;
return aTs - bTs;
});
return ranked[0].market;
}
async _findAndSubscribe() {
try {
const event = await getActiveBTCEvent();
const candidates = await getActiveBTCEvents(12);
if (!event) {
if (!candidates.length) {
if (!this.currentTicker) this.emit('update', null);
console.log('[Tracker] No active BTC 15m event found. Retrying in 30s...');
return;
}
const inlineMarkets = Array.isArray(event.markets) ? event.markets : [];
const markets = inlineMarkets.length ? inlineMarkets : await getEventMarkets(event.event_ticker);
let selectedEvent = null;
let selectedMarket = null;
const market = markets.find((m) => OPEN_MARKET_STATUSES.has(String(m?.status || '').toLowerCase())) || markets[0];
for (const event of candidates) {
const eventTicker = event?.event_ticker;
if (!eventTicker) continue;
if (!market) {
if (!this.currentTicker) {
this.currentEvent = event.event_ticker || null;
this.marketData = null;
this.orderbook = { yes: [], no: [] };
this.emit('update', null);
let markets = Array.isArray(event.markets) ? event.markets : [];
if (!markets.length) {
try {
markets = await getEventMarkets(eventTicker);
} catch (e) {
console.error(`[Tracker] Failed loading markets for ${eventTicker}:`, e.message);
continue;
}
console.log(`[Tracker] Event ${event.event_ticker} has no active market yet. Retrying...`);
}
if (!markets.length) continue;
const market = this._pickBestMarket(markets);
if (!market?.ticker) continue;
selectedEvent = event;
selectedMarket = market;
break;
}
if (!selectedEvent || !selectedMarket) {
if (!this.currentTicker) this.emit('update', null);
console.log('[Tracker] No tradable BTC 15m market found yet. Retrying...');
return;
}
const newTicker = market.ticker;
const newTicker = selectedMarket.ticker;
if (newTicker === this.currentTicker) {
this.currentEvent = event.event_ticker;
this.currentEvent = selectedEvent.event_ticker || this.currentEvent;
this.marketData = { ...(this.marketData || {}), ...selectedMarket };
this.emit('update', this.getState());
return;
}
@@ -120,8 +181,8 @@ export class MarketTracker extends EventEmitter {
}
this.currentTicker = newTicker;
this.currentEvent = event.event_ticker;
this.marketData = market;
this.currentEvent = selectedEvent.event_ticker;
this.marketData = selectedMarket;
this.orderbook = { yes: [], no: [] };
// Fetch fresh orderbook via REST
@@ -134,7 +195,7 @@ export class MarketTracker extends EventEmitter {
// Subscribe via WS
this.ws.subscribeTicker(newTicker);
console.log(`[Tracker] Now tracking: ${newTicker} (${market.title || market.subtitle})`);
console.log(`[Tracker] Now tracking: ${newTicker} (${selectedMarket.title || selectedMarket.subtitle || selectedEvent.event_ticker})`);
this.emit('update', this.getState());
this.emit('market-rotated', { from: oldTicker, to: newTicker });
@@ -153,8 +214,11 @@ export class MarketTracker extends EventEmitter {
const state = this.getState();
this.emit('update', state);
const status = String(fresh?.status || '').toLowerCase();
const settledLike = status === 'closed' || status === 'settled' || status === 'expired' || status === 'finalized';
// If market closed/settled, find the next one
if (fresh.status === 'closed' || fresh.status === 'settled' || fresh.result) {
if (settledLike || fresh.result) {
console.log(`[Tracker] Market ${this.currentTicker} settled (result: ${fresh.result}). Rotating...`);
this.emit('settled', { ticker: this.currentTicker, result: fresh.result });
this.currentTicker = null;