mirror of
https://github.com/multipleof4/KalBot.git
synced 2026-03-17 05:51:02 +00:00
Fix: Skip empty events and pick tradable market
This commit is contained in:
@@ -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 { KalshiWS } from '../kalshi/websocket.js';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
const OPEN_MARKET_STATUSES = new Set(['open', 'active', 'initialized', 'trading']);
|
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.
|
* 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() {
|
async _findAndSubscribe() {
|
||||||
try {
|
try {
|
||||||
const event = await getActiveBTCEvent();
|
const candidates = await getActiveBTCEvents(12);
|
||||||
|
|
||||||
if (!event) {
|
if (!candidates.length) {
|
||||||
if (!this.currentTicker) this.emit('update', null);
|
if (!this.currentTicker) this.emit('update', null);
|
||||||
console.log('[Tracker] No active BTC 15m event found. Retrying in 30s...');
|
console.log('[Tracker] No active BTC 15m event found. Retrying in 30s...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inlineMarkets = Array.isArray(event.markets) ? event.markets : [];
|
let selectedEvent = null;
|
||||||
const markets = inlineMarkets.length ? inlineMarkets : await getEventMarkets(event.event_ticker);
|
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) {
|
let markets = Array.isArray(event.markets) ? event.markets : [];
|
||||||
if (!this.currentTicker) {
|
if (!markets.length) {
|
||||||
this.currentEvent = event.event_ticker || null;
|
try {
|
||||||
this.marketData = null;
|
markets = await getEventMarkets(eventTicker);
|
||||||
this.orderbook = { yes: [], no: [] };
|
} catch (e) {
|
||||||
this.emit('update', null);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTicker = market.ticker;
|
const newTicker = selectedMarket.ticker;
|
||||||
if (newTicker === this.currentTicker) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +181,8 @@ export class MarketTracker extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.currentTicker = newTicker;
|
this.currentTicker = newTicker;
|
||||||
this.currentEvent = event.event_ticker;
|
this.currentEvent = selectedEvent.event_ticker;
|
||||||
this.marketData = market;
|
this.marketData = selectedMarket;
|
||||||
this.orderbook = { yes: [], no: [] };
|
this.orderbook = { yes: [], no: [] };
|
||||||
|
|
||||||
// Fetch fresh orderbook via REST
|
// Fetch fresh orderbook via REST
|
||||||
@@ -134,7 +195,7 @@ export class MarketTracker extends EventEmitter {
|
|||||||
|
|
||||||
// Subscribe via WS
|
// Subscribe via WS
|
||||||
this.ws.subscribeTicker(newTicker);
|
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('update', this.getState());
|
||||||
this.emit('market-rotated', { from: oldTicker, to: newTicker });
|
this.emit('market-rotated', { from: oldTicker, to: newTicker });
|
||||||
@@ -153,8 +214,11 @@ export class MarketTracker extends EventEmitter {
|
|||||||
const state = this.getState();
|
const state = this.getState();
|
||||||
this.emit('update', state);
|
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 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...`);
|
console.log(`[Tracker] Market ${this.currentTicker} settled (result: ${fresh.result}). Rotating...`);
|
||||||
this.emit('settled', { ticker: this.currentTicker, result: fresh.result });
|
this.emit('settled', { ticker: this.currentTicker, result: fresh.result });
|
||||||
this.currentTicker = null;
|
this.currentTicker = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user