From 647b46d1b81a4ce95e212b9d8682c1c07f436479 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Sun, 15 Mar 2026 15:47:51 -0700 Subject: [PATCH] Fix: Handle dollar-string WS fields, fix orderbook --- lib/market/tracker.js | 150 +++++++++++++++++++++++++++++------------- 1 file changed, 103 insertions(+), 47 deletions(-) diff --git a/lib/market/tracker.js b/lib/market/tracker.js index 9372862..afb5d5b 100644 --- a/lib/market/tracker.js +++ b/lib/market/tracker.js @@ -5,6 +5,17 @@ import { EventEmitter } from 'events'; const OPEN_MARKET_STATUSES = new Set(['open', 'active', 'initialized', 'trading']); const TRADABLE_MARKET_STATUSES = new Set(['open', 'active', 'trading']); +/** + * Converts a dollar string like "0.4200" to cents integer (42). + * Returns null if not parseable. + */ +function dollarsToCents(val) { + if (val == null) return null; + const n = Number(val); + if (!Number.isFinite(n)) return null; + return Math.round(n * 100); +} + /** * Tracks the currently active BTC 15-min market. * Auto-rotates when the current market expires. @@ -122,18 +133,25 @@ export class MarketTracker extends EventEmitter { return null; }; - return { - yesBid: pick('yes_bid', 'yesBid'), - yesAsk: pick('yes_ask', 'yesAsk'), - noBid: pick('no_bid', 'noBid'), - noAsk: pick('no_ask', 'noAsk'), - lastPrice: pick('last_price', 'lastPrice', 'yes_price', 'yesPrice') - }; + // Try cents first (from REST API), then dollar-string fields (from WS ticker) + let yesBid = pick('yes_bid', 'yesBid'); + let yesAsk = pick('yes_ask', 'yesAsk'); + let noBid = pick('no_bid', 'noBid'); + let noAsk = pick('no_ask', 'noAsk'); + let lastPrice = pick('last_price', 'lastPrice', 'yes_price', 'yesPrice'); + + // WS ticker sends dollar strings — convert to cents + if (yesBid == null) yesBid = dollarsToCents(market?.yes_bid_dollars); + if (yesAsk == null) yesAsk = dollarsToCents(market?.yes_ask_dollars); + if (noBid == null) noBid = dollarsToCents(market?.no_bid_dollars); + if (noAsk == null) noAsk = dollarsToCents(market?.no_ask_dollars); + if (lastPrice == null) lastPrice = dollarsToCents(market?.price_dollars); + + return { yesBid, yesAsk, noBid, noAsk, lastPrice }; } _normalizeBookSide(levels) { if (!Array.isArray(levels)) return []; - const out = []; for (const level of levels) { @@ -141,18 +159,34 @@ export class MarketTracker extends EventEmitter { let qty = null; if (Array.isArray(level)) { - price = level[0]; - qty = level[1]; + // New API format: ["0.4200", "300.00"] (dollar strings) + // Or old format: [42, 300] (cents integers) + const rawPrice = level[0]; + const rawQty = level[1]; + + // Detect dollar-string format (contains a decimal point and is < 1.01) + if (typeof rawPrice === 'string' && rawPrice.includes('.')) { + price = dollarsToCents(rawPrice); + qty = this._num(rawQty); + } else { + price = this._num(rawPrice); + qty = this._num(rawQty); + } } else if (level && typeof level === 'object') { - price = level.price ?? level[0]; - qty = level.qty ?? level.quantity ?? level.size ?? level.count ?? level[1]; + // Object format + const rawPrice = level.price ?? level.price_dollars ?? level[0]; + const rawQty = level.qty ?? level.quantity ?? level.size ?? level.count ?? level[1]; + + if (typeof rawPrice === 'string' && rawPrice.includes('.')) { + price = dollarsToCents(rawPrice); + } else { + price = this._num(rawPrice); + } + qty = this._num(rawQty); } - const p = this._num(price); - const q = this._num(qty); - - if (p == null || q == null || q <= 0) continue; - out.push([p, q]); + if (price == null || qty == null || qty <= 0) continue; + out.push([price, qty]); } return out.sort((a, b) => b[0] - a[0]); @@ -161,8 +195,9 @@ export class MarketTracker extends EventEmitter { _normalizeOrderbook(book) { const root = book?.orderbook && typeof book.orderbook === 'object' ? book.orderbook : book; return { - yes: this._normalizeBookSide(root?.yes), - no: this._normalizeBookSide(root?.no) + // Support both old fields (yes/no) and new fields (yes_dollars_fp/no_dollars_fp) + yes: this._normalizeBookSide(root?.yes ?? root?.yes_dollars_fp ?? root?.yes_dollars), + no: this._normalizeBookSide(root?.no ?? root?.no_dollars_fp ?? root?.no_dollars) }; } @@ -325,11 +360,19 @@ export class MarketTracker extends EventEmitter { if (msg.market_ticker !== this.currentTicker) return; if (msg.type === 'orderbook_snapshot') { + // New format: yes_dollars_fp / no_dollars_fp this.orderbook = this._normalizeOrderbook(msg); + console.log(`[Tracker] Orderbook snapshot: ${this.orderbook.yes.length} yes levels, ${this.orderbook.no.length} no levels`); } else if (msg.type === 'orderbook_delta') { const side = String(msg.side || '').toLowerCase(); - const price = this._num(msg.price); - const delta = this._num(msg.delta); + + // New format uses price_dollars + delta_fp + let price = this._num(msg.price); + if (price == null) price = dollarsToCents(msg.price_dollars); + + let delta = this._num(msg.delta); + if (delta == null) delta = this._num(msg.delta_fp); + const absoluteQty = this._num(msg.qty ?? msg.quantity ?? msg.size); if ((side === 'yes' || side === 'no') && price != null) { @@ -344,8 +387,11 @@ export class MarketTracker extends EventEmitter { this.orderbook[side] = [...map.entries()].sort((a, b) => b[0] - a[0]); } else { - if (Array.isArray(msg.yes)) this.orderbook.yes = this._applyDelta(this.orderbook.yes, msg.yes); - if (Array.isArray(msg.no)) this.orderbook.no = this._applyDelta(this.orderbook.no, msg.no); + // Batch delta arrays (old format fallback) + const yesArr = msg.yes ?? msg.yes_dollars_fp; + const noArr = msg.no ?? msg.no_dollars_fp; + if (Array.isArray(yesArr)) this.orderbook.yes = this._applyDelta(this.orderbook.yes, yesArr); + if (Array.isArray(noArr)) this.orderbook.no = this._applyDelta(this.orderbook.no, noArr); } } @@ -356,21 +402,22 @@ export class MarketTracker extends EventEmitter { if (msg.market_ticker !== this.currentTicker) return; if (this.marketData) { - const yesBid = this._num(msg.yes_bid); - const yesAsk = this._num(msg.yes_ask); - const noBid = this._num(msg.no_bid); - const noAsk = this._num(msg.no_ask); - const lastPrice = this._num(msg.last_price); - const volume = this._num(msg.volume); + // New API sends dollar-string fields; store them for _extractMarketQuotes + const fields = [ + 'yes_bid', 'yes_ask', 'no_bid', 'no_ask', 'last_price', 'volume', + 'yes_bid_dollars', 'yes_ask_dollars', 'no_bid_dollars', 'no_ask_dollars', + 'price_dollars', 'volume_fp', 'open_interest_fp', + 'dollar_volume', 'dollar_open_interest' + ]; - Object.assign(this.marketData, { - yes_bid: yesBid ?? this.marketData.yes_bid, - yes_ask: yesAsk ?? this.marketData.yes_ask, - no_bid: noBid ?? this.marketData.no_bid, - no_ask: noAsk ?? this.marketData.no_ask, - last_price: lastPrice ?? this.marketData.last_price, - volume: volume ?? this.marketData.volume - }); + for (const key of fields) { + if (msg[key] != null) this.marketData[key] = msg[key]; + } + + // Also map dollar_volume / dollar_open_interest to standard fields + if (msg.dollar_volume != null) this.marketData.volume = this._num(msg.dollar_volume) ?? this.marketData.volume; + if (msg.dollar_open_interest != null) this.marketData.open_interest = this._num(msg.dollar_open_interest) ?? this.marketData.open_interest; + if (msg.volume_fp != null && this.marketData.volume == null) this.marketData.volume = this._num(msg.volume_fp); } this.emit('update', this.getState()); @@ -384,19 +431,28 @@ export class MarketTracker extends EventEmitter { let qty = null; if (Array.isArray(delta)) { - price = delta[0]; - qty = delta[1]; + const rawPrice = delta[0]; + const rawQty = delta[1]; + if (typeof rawPrice === 'string' && rawPrice.includes('.')) { + price = dollarsToCents(rawPrice); + } else { + price = this._num(rawPrice); + } + qty = this._num(rawQty); } else if (delta && typeof delta === 'object') { - price = delta.price ?? delta[0]; - qty = delta.qty ?? delta.quantity ?? delta.size ?? delta[1]; + const rawPrice = delta.price ?? delta.price_dollars ?? delta[0]; + const rawQty = delta.qty ?? delta.quantity ?? delta.size ?? delta[1]; + if (typeof rawPrice === 'string' && rawPrice.includes('.')) { + price = dollarsToCents(rawPrice); + } else { + price = this._num(rawPrice); + } + qty = this._num(rawQty); } - const p = this._num(price); - const q = this._num(qty); - if (p == null || q == null) continue; - - if (q <= 0) map.delete(p); - else map.set(p, q); + if (price == null || qty == null) continue; + if (qty <= 0) map.delete(price); + else map.set(price, qty); } return [...map.entries()].sort((a, b) => b[0] - a[0]);