fix: rewrite makerWs to handle CLOB WebSocket message formats correctly
- Handle initial book snapshot (arrives as array without event_type) - Handle price_change deltas (incremental updates to bid/ask levels) - Use Maps for O(1) price level updates instead of full array replace - Use proper WebSocket ping frames instead of string "ping" - Subscribe all assets in single message - Add getBestBid/getBestAsk methods using cached values from events - Improve TUI orderbook display with depth bars and spread indicator Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
991fd6caa4
commit
23bb74127c
+8
-11
@@ -70,18 +70,15 @@ async function printStatus() {
|
|||||||
` | P&L: ${sign}$${pnl.toFixed(4)}`,
|
` | P&L: ${sign}$${pnl.toFixed(4)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Orderbook snapshot in sim mode
|
// Orderbook snapshot
|
||||||
if (config.dryRun) {
|
for (const [label, tokenId] of [['UP', pos.up.tokenId], ['DOWN', pos.down.tokenId]]) {
|
||||||
for (const [label, tokenId] of [['UP', pos.up.tokenId], ['DOWN', pos.down.tokenId]]) {
|
const bestBid = orderbookWs.getBestBid(tokenId);
|
||||||
|
const bestAsk = orderbookWs.getBestAsk(tokenId);
|
||||||
|
if (bestBid || bestAsk) {
|
||||||
const book = orderbookWs.getBook(tokenId);
|
const book = orderbookWs.getBook(tokenId);
|
||||||
const bestBid = book.bids[0];
|
const bidDepth = book.bids.slice(0, 3).map(b => `${b.price.toFixed(2)}×${b.size.toFixed(0)}`).join(' ');
|
||||||
const bestAsk = book.asks[0];
|
const askDepth = book.asks.slice(0, 3).map(a => `${a.price.toFixed(2)}×${a.size.toFixed(0)}`).join(' ');
|
||||||
if (bestBid || bestAsk) {
|
logger.info(` ${label}: [${bidDepth}] | [${askDepth}]`);
|
||||||
logger.info(
|
|
||||||
` ${label} book: bid $${bestBid?.price.toFixed(3) || '-'} × ${bestBid?.size.toFixed(0) || '-'}` +
|
|
||||||
` | ask $${bestAsk?.price.toFixed(3) || '-'} × ${bestAsk?.size.toFixed(0) || '-'}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-24
@@ -126,44 +126,37 @@ async function buildStatusContent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orderbook display (simulation mode)
|
// Orderbook display (always show when tokens are active)
|
||||||
if (config.dryRun && activeWsTokens.up) {
|
if (activeWsTokens.up) {
|
||||||
lines.push('{bold}LIVE ORDERBOOK{/bold}');
|
lines.push('{bold}LIVE ORDERBOOK{/bold}');
|
||||||
|
|
||||||
for (const [label, tokenId] of [['UP', activeWsTokens.up], ['DOWN', activeWsTokens.down]]) {
|
for (const [label, tokenId] of [['UP', activeWsTokens.up], ['DOWN', activeWsTokens.down]]) {
|
||||||
if (!tokenId) continue;
|
if (!tokenId) continue;
|
||||||
const book = orderbookWs.getBook(tokenId);
|
const book = orderbookWs.getBook(tokenId);
|
||||||
const lastPrice = orderbookWs.getLastPrice(tokenId);
|
const bestBid = orderbookWs.getBestBid(tokenId);
|
||||||
|
const bestAsk = orderbookWs.getBestAsk(tokenId);
|
||||||
|
const mid = bestBid && bestAsk ? ((bestBid + bestAsk) / 2) : 0;
|
||||||
|
|
||||||
lines.push(` {cyan-fg}${label}{/cyan-fg} (last: $${lastPrice.toFixed(3)})`);
|
lines.push(` {cyan-fg}${label}{/cyan-fg} mid: $${mid.toFixed(3)} | bid: $${bestBid.toFixed(2)} ask: $${bestAsk.toFixed(2)}`);
|
||||||
|
|
||||||
// Top 3 asks (reversed so lowest is closest to spread)
|
// Top 5 asks (reversed so lowest is closest to spread)
|
||||||
const topAsks = book.asks.slice(0, 3).reverse();
|
const topAsks = book.asks.slice(0, 5).reverse();
|
||||||
for (const ask of topAsks) {
|
for (const ask of topAsks) {
|
||||||
lines.push(` {red-fg}ASK $${ask.price.toFixed(3)} × ${ask.size.toFixed(0)}{/red-fg}`);
|
const bar = '█'.repeat(Math.min(10, Math.round(ask.size / 100)));
|
||||||
|
lines.push(` {red-fg}$${ask.price.toFixed(2)} ${ask.size.toFixed(0).padStart(7)} ${bar}{/red-fg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spread
|
// Spread line
|
||||||
const bestBid = book.bids[0]?.price || 0;
|
|
||||||
const bestAsk = book.asks[0]?.price || 0;
|
|
||||||
if (bestBid && bestAsk) {
|
if (bestBid && bestAsk) {
|
||||||
lines.push(` {gray-fg}--- spread: $${(bestAsk - bestBid).toFixed(3)} ---{/gray-fg}`);
|
const spread = bestAsk - bestBid;
|
||||||
|
lines.push(` {yellow-fg}── spread $${spread.toFixed(2)} ──{/yellow-fg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top 3 bids
|
// Top 5 bids
|
||||||
const topBids = book.bids.slice(0, 3);
|
const topBids = book.bids.slice(0, 5);
|
||||||
for (const bid of topBids) {
|
for (const bid of topBids) {
|
||||||
lines.push(` {green-fg}BID $${bid.price.toFixed(3)} × ${bid.size.toFixed(0)}{/green-fg}`);
|
const bar = '█'.repeat(Math.min(10, Math.round(bid.size / 100)));
|
||||||
}
|
lines.push(` {green-fg}$${bid.price.toFixed(2)} ${bid.size.toFixed(0).padStart(7)} ${bar}{/green-fg}`);
|
||||||
|
|
||||||
// Recent trades
|
|
||||||
const trades = orderbookWs.getRecentTrades(tokenId, 3);
|
|
||||||
if (trades.length > 0) {
|
|
||||||
lines.push(` Trades:`);
|
|
||||||
for (const t of trades.reverse()) {
|
|
||||||
const color = t.side === 'BUY' ? 'green' : 'red';
|
|
||||||
lines.push(` {${color}-fg}${t.side} $${t.price.toFixed(3)} × ${t.size.toFixed(0)}{/${color}-fg}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
lines.push('');
|
lines.push('');
|
||||||
}
|
}
|
||||||
|
|||||||
+162
-90
@@ -3,17 +3,17 @@
|
|||||||
* WebSocket client for Polymarket CLOB orderbook + trade data.
|
* WebSocket client for Polymarket CLOB orderbook + trade data.
|
||||||
* Used by the simulation to display real-time orderbook and simulate fills.
|
* Used by the simulation to display real-time orderbook and simulate fills.
|
||||||
*
|
*
|
||||||
* Endpoints:
|
* Message formats from CLOB WS:
|
||||||
* - Book updates (bids/asks)
|
* 1. Book snapshot (initial): [{asset_id, bids, asks, timestamp, hash}] (array, no event_type)
|
||||||
* - Last trade price
|
* 2. price_change: {event_type:"price_change", price_changes:[{asset_id, price, size, side, best_bid, best_ask}]}
|
||||||
* - Trade history
|
* 3. last_trade_price: {event_type:"last_trade_price", asset_id, price}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import logger from '../utils/logger.js';
|
import logger from '../utils/logger.js';
|
||||||
|
|
||||||
const WS_URL = 'wss://ws-subscriptions-clob.polymarket.com/ws/market';
|
const WS_URL = 'wss://ws-subscriptions-clob.polymarket.com/ws/market';
|
||||||
const PING_INTERVAL = 10_000;
|
const PING_INTERVAL = 30_000;
|
||||||
const RECONNECT_DELAY = 3000;
|
const RECONNECT_DELAY = 3000;
|
||||||
const MAX_RECONNECT_DELAY = 30_000;
|
const MAX_RECONNECT_DELAY = 30_000;
|
||||||
|
|
||||||
@@ -29,8 +29,13 @@ export class OrderbookWs {
|
|||||||
this.assetIds = [];
|
this.assetIds = [];
|
||||||
this.conditionId = null;
|
this.conditionId = null;
|
||||||
|
|
||||||
// Orderbook state per asset
|
// Orderbook state per asset: Map<price, size>
|
||||||
this.books = new Map(); // assetId → { bids: [], asks: [] }
|
this.bids = new Map(); // assetId → Map<price, size>
|
||||||
|
this.asks = new Map(); // assetId → Map<price, size>
|
||||||
|
|
||||||
|
// Best bid/ask per asset (from price_change events)
|
||||||
|
this.bestBid = new Map(); // assetId → number
|
||||||
|
this.bestAsk = new Map(); // assetId → number
|
||||||
|
|
||||||
// Recent trades per asset
|
// Recent trades per asset
|
||||||
this.trades = new Map(); // assetId → [{ price, side, size, timestamp }]
|
this.trades = new Map(); // assetId → [{ price, side, size, timestamp }]
|
||||||
@@ -39,17 +44,24 @@ export class OrderbookWs {
|
|||||||
this.lastPrice = new Map(); // assetId → number
|
this.lastPrice = new Map(); // assetId → number
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
this.onBookUpdate = null; // (assetId, book) => void
|
this.onBookUpdate = null;
|
||||||
this.onTradeUpdate = null; // (assetId, trade) => void
|
this.onTradeUpdate = null;
|
||||||
this.onPriceUpdate = null; // (assetId, price) => void
|
this.onPriceUpdate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(conditionId, assetIds) {
|
subscribe(conditionId, assetIds) {
|
||||||
|
// Shutdown existing connection if any
|
||||||
|
if (this.ws) {
|
||||||
|
this.cleanup(false);
|
||||||
|
}
|
||||||
|
|
||||||
this.conditionId = conditionId;
|
this.conditionId = conditionId;
|
||||||
this.assetIds = assetIds;
|
this.assetIds = assetIds;
|
||||||
|
this.isShutdown = false;
|
||||||
|
|
||||||
for (const id of assetIds) {
|
for (const id of assetIds) {
|
||||||
this.books.set(id, { bids: [], asks: [] });
|
this.bids.set(id, new Map());
|
||||||
|
this.asks.set(id, new Map());
|
||||||
this.trades.set(id, []);
|
this.trades.set(id, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,16 +77,15 @@ export class OrderbookWs {
|
|||||||
logger.info('MAKER WS: connected to orderbook feed');
|
logger.info('MAKER WS: connected to orderbook feed');
|
||||||
this.reconnectDelay = RECONNECT_DELAY;
|
this.reconnectDelay = RECONNECT_DELAY;
|
||||||
|
|
||||||
// Subscribe to book + trades for all assets
|
// Subscribe to all assets in one message
|
||||||
for (const assetId of this.assetIds) {
|
const msg = {
|
||||||
this.ws.send(JSON.stringify({
|
auth: {},
|
||||||
auth: {},
|
type: 'subscribe',
|
||||||
type: 'subscribe',
|
markets: [],
|
||||||
markets: [this.conditionId],
|
assets_ids: this.assetIds,
|
||||||
assets_ids: [assetId],
|
channels: ['book'],
|
||||||
channels: ['book', 'trades'],
|
};
|
||||||
}));
|
this.ws.send(JSON.stringify(msg));
|
||||||
}
|
|
||||||
|
|
||||||
this.startPing();
|
this.startPing();
|
||||||
});
|
});
|
||||||
@@ -87,7 +98,8 @@ export class OrderbookWs {
|
|||||||
this.ws?.pong();
|
this.ws?.pong();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ws.on('close', () => {
|
this.ws.on('close', (code, reason) => {
|
||||||
|
logger.warn(`MAKER WS: disconnected (${code})`);
|
||||||
this.cleanup(true);
|
this.cleanup(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,61 +114,118 @@ export class OrderbookWs {
|
|||||||
try {
|
try {
|
||||||
msg = JSON.parse(raw.toString());
|
msg = JSON.parse(raw.toString());
|
||||||
} catch {
|
} catch {
|
||||||
const text = raw.toString().trim();
|
|
||||||
if (text === 'ping') this.ws?.send('pong');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.type === 'ping' || msg === 'ping') {
|
// Initial book snapshot comes as an array [{...}]
|
||||||
this.ws?.send('pong');
|
if (Array.isArray(msg)) {
|
||||||
|
for (const evt of msg) {
|
||||||
|
if (evt.asset_id && evt.bids) {
|
||||||
|
this.handleBookSnapshot(evt.asset_id, evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle different event types from the CLOB WS
|
// Subsequent messages are objects with event_type
|
||||||
const events = Array.isArray(msg) ? msg : [msg];
|
switch (msg.event_type) {
|
||||||
|
case 'book':
|
||||||
|
if (msg.asset_id) {
|
||||||
|
this.handleBookSnapshot(msg.asset_id, msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
for (const evt of events) {
|
case 'price_change':
|
||||||
const assetId = evt.asset_id;
|
this.handlePriceChange(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'last_trade_price':
|
||||||
|
if (msg.asset_id) {
|
||||||
|
this.handleLastPrice(msg.asset_id, msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Full book updates without event_type (non-array single object)
|
||||||
|
if (msg.asset_id && msg.bids) {
|
||||||
|
this.handleBookSnapshot(msg.asset_id, msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBookSnapshot(assetId, evt) {
|
||||||
|
if (!this.assetIds.includes(assetId)) return;
|
||||||
|
|
||||||
|
// Replace entire book for this asset
|
||||||
|
const bidMap = new Map();
|
||||||
|
for (const b of (evt.bids || [])) {
|
||||||
|
const price = parseFloat(b.price);
|
||||||
|
const size = parseFloat(b.size);
|
||||||
|
if (size > 0) bidMap.set(price, size);
|
||||||
|
}
|
||||||
|
this.bids.set(assetId, bidMap);
|
||||||
|
|
||||||
|
const askMap = new Map();
|
||||||
|
for (const a of (evt.asks || [])) {
|
||||||
|
const price = parseFloat(a.price);
|
||||||
|
const size = parseFloat(a.size);
|
||||||
|
if (size > 0) askMap.set(price, size);
|
||||||
|
}
|
||||||
|
this.asks.set(assetId, askMap);
|
||||||
|
|
||||||
|
if (this.onBookUpdate) {
|
||||||
|
this.onBookUpdate(assetId, this.getBook(assetId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePriceChange(msg) {
|
||||||
|
const changes = msg.price_changes || [];
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
const assetId = change.asset_id;
|
||||||
if (!assetId || !this.assetIds.includes(assetId)) continue;
|
if (!assetId || !this.assetIds.includes(assetId)) continue;
|
||||||
|
|
||||||
switch (evt.event_type) {
|
const price = parseFloat(change.price);
|
||||||
case 'book':
|
const size = parseFloat(change.size);
|
||||||
this.handleBook(assetId, evt);
|
const side = change.side; // "BUY" = bid, "SELL" = ask
|
||||||
break;
|
|
||||||
case 'last_trade_price':
|
if (side === 'BUY') {
|
||||||
this.handleLastPrice(assetId, evt);
|
const bidMap = this.bids.get(assetId);
|
||||||
break;
|
if (bidMap) {
|
||||||
case 'tick_size_change':
|
if (size > 0) {
|
||||||
break; // ignore
|
bidMap.set(price, size);
|
||||||
default:
|
} else {
|
||||||
// Could be trade data
|
bidMap.delete(price); // size 0 = remove level
|
||||||
if (evt.price && evt.side) {
|
|
||||||
this.handleTrade(assetId, evt);
|
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
} else if (side === 'SELL') {
|
||||||
|
const askMap = this.asks.get(assetId);
|
||||||
|
if (askMap) {
|
||||||
|
if (size > 0) {
|
||||||
|
askMap.set(price, size);
|
||||||
|
} else {
|
||||||
|
askMap.delete(price);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update best bid/ask from the event
|
||||||
|
if (change.best_bid) this.bestBid.set(assetId, parseFloat(change.best_bid));
|
||||||
|
if (change.best_ask) this.bestAsk.set(assetId, parseFloat(change.best_ask));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify for each affected asset
|
||||||
|
const affectedAssets = new Set(changes.map(c => c.asset_id).filter(id => this.assetIds.includes(id)));
|
||||||
|
for (const assetId of affectedAssets) {
|
||||||
|
if (this.onBookUpdate) {
|
||||||
|
this.onBookUpdate(assetId, this.getBook(assetId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBook(assetId, evt) {
|
|
||||||
const bids = (evt.bids || []).map((b) => ({
|
|
||||||
price: parseFloat(b.price),
|
|
||||||
size: parseFloat(b.size),
|
|
||||||
})).sort((a, b) => b.price - a.price);
|
|
||||||
|
|
||||||
const asks = (evt.asks || []).map((a) => ({
|
|
||||||
price: parseFloat(a.price),
|
|
||||||
size: parseFloat(a.size),
|
|
||||||
})).sort((a, b) => a.price - b.price);
|
|
||||||
|
|
||||||
this.books.set(assetId, { bids, asks, timestamp: evt.timestamp });
|
|
||||||
|
|
||||||
if (this.onBookUpdate) {
|
|
||||||
this.onBookUpdate(assetId, { bids, asks });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLastPrice(assetId, evt) {
|
handleLastPrice(assetId, evt) {
|
||||||
|
if (!this.assetIds.includes(assetId)) return;
|
||||||
const price = parseFloat(evt.price || '0');
|
const price = parseFloat(evt.price || '0');
|
||||||
this.lastPrice.set(assetId, price);
|
this.lastPrice.set(assetId, price);
|
||||||
|
|
||||||
@@ -165,38 +234,31 @@ export class OrderbookWs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTrade(assetId, evt) {
|
/**
|
||||||
const trade = {
|
* Get sorted orderbook for an asset
|
||||||
price: parseFloat(evt.price || '0'),
|
*/
|
||||||
side: evt.side || '',
|
getBook(assetId) {
|
||||||
size: parseFloat(evt.size || evt.amount || '0'),
|
const bidMap = this.bids.get(assetId) || new Map();
|
||||||
timestamp: evt.timestamp || new Date().toISOString(),
|
const askMap = this.asks.get(assetId) || new Map();
|
||||||
};
|
|
||||||
|
|
||||||
const trades = this.trades.get(assetId) || [];
|
const bids = Array.from(bidMap.entries())
|
||||||
trades.push(trade);
|
.map(([price, size]) => ({ price, size }))
|
||||||
if (trades.length > 50) trades.splice(0, trades.length - 50);
|
.sort((a, b) => b.price - a.price);
|
||||||
this.trades.set(assetId, trades);
|
|
||||||
|
|
||||||
if (this.onTradeUpdate) {
|
const asks = Array.from(askMap.entries())
|
||||||
this.onTradeUpdate(assetId, trade);
|
.map(([price, size]) => ({ price, size }))
|
||||||
}
|
.sort((a, b) => a.price - b.price);
|
||||||
|
|
||||||
|
return { bids, asks };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a simulated order would fill based on current orderbook.
|
* Check if a simulated order would fill based on current orderbook.
|
||||||
* @param {string} assetId - token ID
|
|
||||||
* @param {'buy'|'sell'} side - order side
|
|
||||||
* @param {number} price - limit price
|
|
||||||
* @param {number} size - order size
|
|
||||||
* @returns {{ filled: number, avgPrice: number } | null}
|
|
||||||
*/
|
*/
|
||||||
checkSimFill(assetId, side, price, size) {
|
checkSimFill(assetId, side, price, size) {
|
||||||
const book = this.books.get(assetId);
|
const book = this.getBook(assetId);
|
||||||
if (!book) return null;
|
|
||||||
|
|
||||||
if (side === 'buy') {
|
if (side === 'buy') {
|
||||||
// Buy order fills against asks at or below our price
|
|
||||||
const eligible = book.asks.filter((a) => a.price <= price);
|
const eligible = book.asks.filter((a) => a.price <= price);
|
||||||
if (eligible.length === 0) return null;
|
if (eligible.length === 0) return null;
|
||||||
|
|
||||||
@@ -213,7 +275,6 @@ export class OrderbookWs {
|
|||||||
return { filled: Math.min(filled, size), avgPrice: totalCost / filled };
|
return { filled: Math.min(filled, size), avgPrice: totalCost / filled };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Sell order fills against bids at or above our price
|
|
||||||
const eligible = book.bids.filter((b) => b.price >= price);
|
const eligible = book.bids.filter((b) => b.price >= price);
|
||||||
if (eligible.length === 0) return null;
|
if (eligible.length === 0) return null;
|
||||||
|
|
||||||
@@ -234,14 +295,25 @@ export class OrderbookWs {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBook(assetId) {
|
|
||||||
return this.books.get(assetId) || { bids: [], asks: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
getLastPrice(assetId) {
|
getLastPrice(assetId) {
|
||||||
return this.lastPrice.get(assetId) || 0;
|
return this.lastPrice.get(assetId) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBestBid(assetId) {
|
||||||
|
// Try from price_change data first, fallback to computed from book
|
||||||
|
const cached = this.bestBid.get(assetId);
|
||||||
|
if (cached) return cached;
|
||||||
|
const book = this.getBook(assetId);
|
||||||
|
return book.bids[0]?.price || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBestAsk(assetId) {
|
||||||
|
const cached = this.bestAsk.get(assetId);
|
||||||
|
if (cached) return cached;
|
||||||
|
const book = this.getBook(assetId);
|
||||||
|
return book.asks[0]?.price || 0;
|
||||||
|
}
|
||||||
|
|
||||||
getRecentTrades(assetId, limit = 10) {
|
getRecentTrades(assetId, limit = 10) {
|
||||||
const trades = this.trades.get(assetId) || [];
|
const trades = this.trades.get(assetId) || [];
|
||||||
return trades.slice(-limit);
|
return trades.slice(-limit);
|
||||||
@@ -251,7 +323,7 @@ export class OrderbookWs {
|
|||||||
this.stopPing();
|
this.stopPing();
|
||||||
this.pingTimer = setInterval(() => {
|
this.pingTimer = setInterval(() => {
|
||||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
this.ws.send('ping');
|
this.ws.ping(); // Use proper WebSocket ping frames
|
||||||
}
|
}
|
||||||
}, PING_INTERVAL);
|
}, PING_INTERVAL);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user