@nevuamarkets/poly-websockets 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/WSSubscriptionManager.js +27 -7
- package/dist/modules/GroupSocket.js +126 -77
- package/dist/modules/OrderBookCache.js +7 -6
- package/dist/types/PolymarketWebSocket.d.ts +45 -26
- package/package.json +1 -1
- package/src/WSSubscriptionManager.ts +31 -9
- package/src/modules/GroupSocket.ts +132 -78
- package/src/modules/OrderBookCache.ts +7 -6
- package/src/types/PolymarketWebSocket.ts +47 -23
|
@@ -7,6 +7,7 @@ exports.WSSubscriptionManager = void 0;
|
|
|
7
7
|
const ms_1 = __importDefault(require("ms"));
|
|
8
8
|
const lodash_1 = __importDefault(require("lodash"));
|
|
9
9
|
const bottleneck_1 = __importDefault(require("bottleneck"));
|
|
10
|
+
const PolymarketWebSocket_1 = require("./types/PolymarketWebSocket");
|
|
10
11
|
const GroupRegistry_1 = require("./modules/GroupRegistry");
|
|
11
12
|
const OrderBookCache_1 = require("./modules/OrderBookCache");
|
|
12
13
|
const GroupSocket_1 = require("./modules/GroupSocket");
|
|
@@ -86,15 +87,34 @@ class WSSubscriptionManager {
|
|
|
86
87
|
async actOnSubscribedEvents(events, action) {
|
|
87
88
|
// Filter out events that are not subscribed to by any groups
|
|
88
89
|
events = lodash_1.default.filter(events, (event) => {
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
// Handle PriceChangeEvent which doesn't have asset_id at root
|
|
91
|
+
if ((0, PolymarketWebSocket_1.isPriceChangeEvent)(event)) {
|
|
92
|
+
// Check if any of the price_changes are subscribed
|
|
93
|
+
return event.price_changes.some(price_change_item => {
|
|
94
|
+
const groupIndices = this.groupRegistry.getGroupIndicesForAsset(price_change_item.asset_id);
|
|
95
|
+
if (groupIndices.length > 1) {
|
|
96
|
+
logger_1.logger.warn({
|
|
97
|
+
message: 'Found multiple groups for asset',
|
|
98
|
+
asset_id: price_change_item.asset_id,
|
|
99
|
+
group_indices: groupIndices
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return groupIndices.length > 0;
|
|
95
103
|
});
|
|
96
104
|
}
|
|
97
|
-
|
|
105
|
+
// For all other events, check asset_id at root
|
|
106
|
+
if ('asset_id' in event) {
|
|
107
|
+
const groupIndices = this.groupRegistry.getGroupIndicesForAsset(event.asset_id);
|
|
108
|
+
if (groupIndices.length > 1) {
|
|
109
|
+
logger_1.logger.warn({
|
|
110
|
+
message: 'Found multiple groups for asset',
|
|
111
|
+
asset_id: event.asset_id,
|
|
112
|
+
group_indices: groupIndices
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return groupIndices.length > 0;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
98
118
|
});
|
|
99
119
|
await (action === null || action === void 0 ? void 0 : action(events));
|
|
100
120
|
}
|
|
@@ -59,6 +59,11 @@ class GroupSocket {
|
|
|
59
59
|
setupEventHandlers() {
|
|
60
60
|
const group = this.group;
|
|
61
61
|
const handlers = this.handlers;
|
|
62
|
+
// Capture the current WebSocket instance to avoid race conditions
|
|
63
|
+
const currentWebSocket = group.wsClient;
|
|
64
|
+
if (!currentWebSocket) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
62
67
|
/*
|
|
63
68
|
Define handlers within this scope to capture 'this' context
|
|
64
69
|
*/
|
|
@@ -68,8 +73,25 @@ class GroupSocket {
|
|
|
68
73
|
group.status = WebSocketSubscriptions_1.WebSocketStatus.CLEANUP;
|
|
69
74
|
return;
|
|
70
75
|
}
|
|
76
|
+
// Verify this handler is for the current WebSocket instance
|
|
77
|
+
if (currentWebSocket !== group.wsClient) {
|
|
78
|
+
logger_1.logger.warn({
|
|
79
|
+
message: 'handleOpen called for stale WebSocket instance',
|
|
80
|
+
groupId: group.groupId,
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Additional safety check for readyState
|
|
85
|
+
if (currentWebSocket.readyState !== ws_1.default.OPEN) {
|
|
86
|
+
logger_1.logger.warn({
|
|
87
|
+
message: 'handleOpen called but WebSocket is not in OPEN state',
|
|
88
|
+
groupId: group.groupId,
|
|
89
|
+
readyState: currentWebSocket.readyState,
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
71
93
|
group.status = WebSocketSubscriptions_1.WebSocketStatus.ALIVE;
|
|
72
|
-
|
|
94
|
+
currentWebSocket.send(JSON.stringify({ assets_ids: Array.from(group.assetIds), type: 'market' }));
|
|
73
95
|
await ((_a = handlers.onWSOpen) === null || _a === void 0 ? void 0 : _a.call(handlers, group.groupId, Array.from(group.assetIds)));
|
|
74
96
|
this.pingInterval = setInterval(() => {
|
|
75
97
|
if (group.assetIds.size === 0) {
|
|
@@ -77,26 +99,44 @@ class GroupSocket {
|
|
|
77
99
|
group.status = WebSocketSubscriptions_1.WebSocketStatus.CLEANUP;
|
|
78
100
|
return;
|
|
79
101
|
}
|
|
80
|
-
|
|
102
|
+
// Verify we're still using the same WebSocket
|
|
103
|
+
if (currentWebSocket !== group.wsClient) {
|
|
104
|
+
clearInterval(this.pingInterval);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!currentWebSocket || currentWebSocket.readyState !== ws_1.default.OPEN) {
|
|
81
108
|
clearInterval(this.pingInterval);
|
|
82
109
|
group.status = WebSocketSubscriptions_1.WebSocketStatus.DEAD;
|
|
83
110
|
return;
|
|
84
111
|
}
|
|
85
|
-
|
|
112
|
+
currentWebSocket.ping();
|
|
86
113
|
}, (0, crypto_1.randomInt)((0, ms_1.default)('15s'), (0, ms_1.default)('25s')));
|
|
87
114
|
};
|
|
88
115
|
const handleMessage = async (data) => {
|
|
89
116
|
var _a, _b;
|
|
117
|
+
const messageStr = data.toString();
|
|
118
|
+
// Handle PONG messages that might be sent to message handler during handler reattachment
|
|
119
|
+
if (messageStr === 'PONG') {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
90
122
|
let events = [];
|
|
91
123
|
try {
|
|
92
|
-
const parsedData = JSON.parse(
|
|
124
|
+
const parsedData = JSON.parse(messageStr);
|
|
93
125
|
events = Array.isArray(parsedData) ? parsedData : [parsedData];
|
|
94
126
|
}
|
|
95
127
|
catch (err) {
|
|
96
|
-
await ((_a = handlers.onError) === null || _a === void 0 ? void 0 : _a.call(handlers, new Error(`Not JSON: ${
|
|
128
|
+
await ((_a = handlers.onError) === null || _a === void 0 ? void 0 : _a.call(handlers, new Error(`Not JSON: ${messageStr}`)));
|
|
97
129
|
return;
|
|
98
130
|
}
|
|
99
|
-
|
|
131
|
+
// Filter events to ensure validity
|
|
132
|
+
events = lodash_1.default.filter(events, (event) => {
|
|
133
|
+
// For price_change events, check that price_changes array exists
|
|
134
|
+
if ((0, PolymarketWebSocket_1.isPriceChangeEvent)(event)) {
|
|
135
|
+
return event.price_changes && event.price_changes.length > 0;
|
|
136
|
+
}
|
|
137
|
+
// For all other events, check asset_id
|
|
138
|
+
return lodash_1.default.size(event.asset_id) > 0;
|
|
139
|
+
});
|
|
100
140
|
const bookEvents = [];
|
|
101
141
|
const lastTradeEvents = [];
|
|
102
142
|
const tickEvents = [];
|
|
@@ -106,23 +146,35 @@ class GroupSocket {
|
|
|
106
146
|
Skip events for asset ids that are not in the group to ensure that
|
|
107
147
|
we don't get stale events for assets that were removed.
|
|
108
148
|
*/
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
else if ((0, PolymarketWebSocket_1.isPriceChangeEvent)(event)) {
|
|
122
|
-
priceChangeEvents.push(event);
|
|
149
|
+
if ((0, PolymarketWebSocket_1.isPriceChangeEvent)(event)) {
|
|
150
|
+
// Check if any of the price_changes are for assets in this group
|
|
151
|
+
const relevantChanges = event.price_changes.filter(price_change_item => group.assetIds.has(price_change_item.asset_id));
|
|
152
|
+
if (relevantChanges.length === 0) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// Only include relevant changes
|
|
156
|
+
priceChangeEvents.push({
|
|
157
|
+
...event,
|
|
158
|
+
price_changes: relevantChanges
|
|
159
|
+
});
|
|
123
160
|
}
|
|
124
161
|
else {
|
|
125
|
-
|
|
162
|
+
// For all other events, check asset_id at root
|
|
163
|
+
if (!group.assetIds.has(event.asset_id)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if ((0, PolymarketWebSocket_1.isBookEvent)(event)) {
|
|
167
|
+
bookEvents.push(event);
|
|
168
|
+
}
|
|
169
|
+
else if ((0, PolymarketWebSocket_1.isLastTradePriceEvent)(event)) {
|
|
170
|
+
lastTradeEvents.push(event);
|
|
171
|
+
}
|
|
172
|
+
else if ((0, PolymarketWebSocket_1.isTickSizeChangeEvent)(event)) {
|
|
173
|
+
tickEvents.push(event);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
await ((_b = handlers.onError) === null || _b === void 0 ? void 0 : _b.call(handlers, new Error(`Unknown event: ${JSON.stringify(event)}`)));
|
|
177
|
+
}
|
|
126
178
|
}
|
|
127
179
|
}
|
|
128
180
|
await this.handleBookEvents(bookEvents);
|
|
@@ -143,26 +195,20 @@ class GroupSocket {
|
|
|
143
195
|
var _a;
|
|
144
196
|
group.status = WebSocketSubscriptions_1.WebSocketStatus.DEAD;
|
|
145
197
|
clearInterval(this.pingInterval);
|
|
146
|
-
await ((_a = handlers.onWSClose) === null || _a === void 0 ? void 0 : _a.call(handlers, group.groupId, code, reason.toString()));
|
|
198
|
+
await ((_a = handlers.onWSClose) === null || _a === void 0 ? void 0 : _a.call(handlers, group.groupId, code, (reason === null || reason === void 0 ? void 0 : reason.toString()) || ''));
|
|
147
199
|
};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
group.wsClient.on('close', handleClose);
|
|
157
|
-
}
|
|
200
|
+
// Remove any existing handlers
|
|
201
|
+
currentWebSocket.removeAllListeners();
|
|
202
|
+
// Add the handlers
|
|
203
|
+
currentWebSocket.on('open', handleOpen);
|
|
204
|
+
currentWebSocket.on('message', handleMessage);
|
|
205
|
+
currentWebSocket.on('pong', handlePong);
|
|
206
|
+
currentWebSocket.on('error', handleError);
|
|
207
|
+
currentWebSocket.on('close', handleClose);
|
|
158
208
|
if (group.assetIds.size === 0) {
|
|
159
209
|
group.status = WebSocketSubscriptions_1.WebSocketStatus.CLEANUP;
|
|
160
210
|
return;
|
|
161
211
|
}
|
|
162
|
-
if (!group.wsClient) {
|
|
163
|
-
group.status = WebSocketSubscriptions_1.WebSocketStatus.DEAD;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
212
|
}
|
|
167
213
|
async handleBookEvents(bookEvents) {
|
|
168
214
|
var _a, _b;
|
|
@@ -190,61 +236,64 @@ class GroupSocket {
|
|
|
190
236
|
catch (err) {
|
|
191
237
|
logger_1.logger.debug({
|
|
192
238
|
message: `Skipping derived future price calculation price_change: book not found for asset`,
|
|
193
|
-
asset_id: event.asset_id,
|
|
194
239
|
event: event,
|
|
195
240
|
error: err === null || err === void 0 ? void 0 : err.message
|
|
196
241
|
});
|
|
197
242
|
continue;
|
|
198
243
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
catch (err) {
|
|
204
|
-
logger_1.logger.debug({
|
|
205
|
-
message: 'Skipping derived future price calculation for price_change: error calculating spread',
|
|
206
|
-
asset_id: event.asset_id,
|
|
207
|
-
event: event,
|
|
208
|
-
error: err === null || err === void 0 ? void 0 : err.message
|
|
209
|
-
});
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (!spreadOver10Cents) {
|
|
213
|
-
let newPrice;
|
|
244
|
+
// Handle price updates per asset
|
|
245
|
+
const assetIds = event.price_changes.map(price_change_item => price_change_item.asset_id);
|
|
246
|
+
for (const assetId of assetIds) {
|
|
247
|
+
let spreadOver10Cents;
|
|
214
248
|
try {
|
|
215
|
-
|
|
249
|
+
spreadOver10Cents = this.bookCache.spreadOver(assetId, 0.1);
|
|
216
250
|
}
|
|
217
251
|
catch (err) {
|
|
218
252
|
logger_1.logger.debug({
|
|
219
|
-
message: 'Skipping derived future price calculation for price_change: error calculating
|
|
220
|
-
asset_id:
|
|
253
|
+
message: 'Skipping derived future price calculation for price_change: error calculating spread',
|
|
254
|
+
asset_id: assetId,
|
|
221
255
|
event: event,
|
|
222
256
|
error: err === null || err === void 0 ? void 0 : err.message
|
|
223
257
|
});
|
|
224
258
|
continue;
|
|
225
259
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
260
|
+
if (!spreadOver10Cents) {
|
|
261
|
+
let newPrice;
|
|
262
|
+
try {
|
|
263
|
+
newPrice = this.bookCache.midpoint(assetId);
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
logger_1.logger.debug({
|
|
267
|
+
message: 'Skipping derived future price calculation for price_change: error calculating midpoint',
|
|
268
|
+
asset_id: assetId,
|
|
269
|
+
event: event,
|
|
270
|
+
error: err === null || err === void 0 ? void 0 : err.message
|
|
271
|
+
});
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const bookEntry = this.bookCache.getBookEntry(assetId);
|
|
275
|
+
if (!bookEntry) {
|
|
276
|
+
logger_1.logger.debug({
|
|
277
|
+
message: 'Skipping derived future price calculation price_change: book not found for asset',
|
|
278
|
+
asset_id: assetId,
|
|
279
|
+
event: event,
|
|
280
|
+
});
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (newPrice !== bookEntry.price) {
|
|
284
|
+
bookEntry.price = newPrice;
|
|
285
|
+
const priceUpdateEvent = {
|
|
286
|
+
asset_id: assetId,
|
|
287
|
+
event_type: 'price_update',
|
|
288
|
+
triggeringEvent: event,
|
|
289
|
+
timestamp: event.timestamp,
|
|
290
|
+
book: { bids: bookEntry.bids, asks: bookEntry.asks },
|
|
291
|
+
price: newPrice,
|
|
292
|
+
midpoint: bookEntry.midpoint || '',
|
|
293
|
+
spread: bookEntry.spread || '',
|
|
294
|
+
};
|
|
295
|
+
await ((_d = (_c = this.handlers).onPolymarketPriceUpdate) === null || _d === void 0 ? void 0 : _d.call(_c, [priceUpdateEvent]));
|
|
296
|
+
}
|
|
248
297
|
}
|
|
249
298
|
}
|
|
250
299
|
}
|
|
@@ -47,12 +47,13 @@ class OrderBookCache {
|
|
|
47
47
|
* Throws if the book is not found.
|
|
48
48
|
*/
|
|
49
49
|
upsertPriceChange(event) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
// Iterate through price_changes array
|
|
51
|
+
for (const priceChange of event.price_changes) {
|
|
52
|
+
const book = this.bookCache[priceChange.asset_id];
|
|
53
|
+
if (!book) {
|
|
54
|
+
throw new Error(`Book not found for asset ${priceChange.asset_id}`);
|
|
55
|
+
}
|
|
56
|
+
const { price, size, side } = priceChange;
|
|
56
57
|
if (side === 'BUY') {
|
|
57
58
|
const i = book.bids.findIndex(bid => bid.price === price);
|
|
58
59
|
if (i !== -1) {
|
|
@@ -7,31 +7,44 @@ export type PriceLevel = {
|
|
|
7
7
|
price: string;
|
|
8
8
|
size: string;
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Represents a single price change item
|
|
12
|
+
*/
|
|
13
|
+
export type PriceChangeItem = {
|
|
14
|
+
asset_id: string;
|
|
15
|
+
price: string;
|
|
16
|
+
size: string;
|
|
17
|
+
side: 'BUY' | 'SELL';
|
|
18
|
+
hash: string;
|
|
19
|
+
best_bid: string;
|
|
20
|
+
best_ask: string;
|
|
21
|
+
};
|
|
10
22
|
/**
|
|
11
23
|
* Represents a price_change event from Polymarket WebSocket
|
|
12
|
-
*
|
|
24
|
+
*
|
|
25
|
+
* Schema example:
|
|
13
26
|
* {
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* {
|
|
27
|
+
* market: "0x5f65177b394277fd294cd75650044e32ba009a95022d88a0c1d565897d72f8f1",
|
|
28
|
+
* price_changes: [
|
|
29
|
+
* {
|
|
30
|
+
* asset_id: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
|
|
31
|
+
* price: "0.5",
|
|
32
|
+
* size: "200",
|
|
33
|
+
* side: "BUY",
|
|
34
|
+
* hash: "56621a121a47ed9333273e21c83b660cff37ae50",
|
|
35
|
+
* best_bid: "0.5",
|
|
36
|
+
* best_ask: "1"
|
|
37
|
+
* }
|
|
17
38
|
* ],
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* market: "0x5412ae25e97078f814157de948459d59c6221b4c4c495fdd57b536543ad36729",
|
|
21
|
-
* timestamp: "1749371014925"
|
|
39
|
+
* timestamp: "1757908892351",
|
|
40
|
+
* event_type: "price_change"
|
|
22
41
|
* }
|
|
23
42
|
*/
|
|
24
43
|
export type PriceChangeEvent = {
|
|
25
|
-
asset_id: string;
|
|
26
|
-
changes: {
|
|
27
|
-
price: string;
|
|
28
|
-
side: string;
|
|
29
|
-
size: string;
|
|
30
|
-
}[];
|
|
31
44
|
event_type: 'price_change';
|
|
32
|
-
hash: string;
|
|
33
45
|
market: string;
|
|
34
46
|
timestamp: string;
|
|
47
|
+
price_changes: PriceChangeItem[];
|
|
35
48
|
};
|
|
36
49
|
/**
|
|
37
50
|
* Represents a Polymarket book
|
|
@@ -149,14 +162,20 @@ export type TickSizeChangeEvent = {
|
|
|
149
162
|
*
|
|
150
163
|
* @example PriceChangeEvent
|
|
151
164
|
* {
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* {
|
|
165
|
+
* market: "0x5f65177b394277fd294cd75650044e32ba009a95022d88a0c1d565897d72f8f1",
|
|
166
|
+
* price_changes: [
|
|
167
|
+
* {
|
|
168
|
+
* asset_id: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
|
|
169
|
+
* price: "0.5",
|
|
170
|
+
* size: "200",
|
|
171
|
+
* side: "BUY",
|
|
172
|
+
* hash: "56621a121a47ed9333273e21c83b660cff37ae50",
|
|
173
|
+
* best_bid: "0.5",
|
|
174
|
+
* best_ask: "1"
|
|
175
|
+
* }
|
|
155
176
|
* ],
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* market: "0x5412ae25e97078f814157de948459d59c6221b4c4c495fdd57b536543ad36729",
|
|
159
|
-
* timestamp: "1749371014925"
|
|
177
|
+
* timestamp: "1757908892351",
|
|
178
|
+
* event_type: "price_change"
|
|
160
179
|
* }
|
|
161
180
|
*
|
|
162
181
|
* @example TickSizeChangeEvent
|
|
@@ -212,7 +231,7 @@ export type WebSocketHandlers = {
|
|
|
212
231
|
* console.log(event.bids);
|
|
213
232
|
* }
|
|
214
233
|
*/
|
|
215
|
-
export declare function isBookEvent(event: PolymarketWSEvent): event is BookEvent;
|
|
234
|
+
export declare function isBookEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is BookEvent;
|
|
216
235
|
/**
|
|
217
236
|
* Type guard to check if an event is a LastTradePriceEvent
|
|
218
237
|
* @example
|
|
@@ -221,7 +240,7 @@ export declare function isBookEvent(event: PolymarketWSEvent): event is BookEven
|
|
|
221
240
|
* console.log(event.side);
|
|
222
241
|
* }
|
|
223
242
|
*/
|
|
224
|
-
export declare function isLastTradePriceEvent(event: PolymarketWSEvent): event is LastTradePriceEvent;
|
|
243
|
+
export declare function isLastTradePriceEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is LastTradePriceEvent;
|
|
225
244
|
/**
|
|
226
245
|
* Type guard to check if an event is a PriceChangeEvent
|
|
227
246
|
* @example
|
|
@@ -230,7 +249,7 @@ export declare function isLastTradePriceEvent(event: PolymarketWSEvent): event i
|
|
|
230
249
|
* console.log(event.changes);
|
|
231
250
|
* }
|
|
232
251
|
*/
|
|
233
|
-
export declare function isPriceChangeEvent(event: PolymarketWSEvent): event is PriceChangeEvent;
|
|
252
|
+
export declare function isPriceChangeEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is PriceChangeEvent;
|
|
234
253
|
/**
|
|
235
254
|
* Type guard to check if an event is a TickSizeChangeEvent
|
|
236
255
|
* @example
|
|
@@ -239,4 +258,4 @@ export declare function isPriceChangeEvent(event: PolymarketWSEvent): event is P
|
|
|
239
258
|
* console.log(event.old_tick_size);
|
|
240
259
|
* }
|
|
241
260
|
*/
|
|
242
|
-
export declare function isTickSizeChangeEvent(event: PolymarketWSEvent): event is TickSizeChangeEvent;
|
|
261
|
+
export declare function isTickSizeChangeEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is TickSizeChangeEvent;
|
package/package.json
CHANGED
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
LastTradePriceEvent,
|
|
9
9
|
TickSizeChangeEvent,
|
|
10
10
|
PolymarketWSEvent,
|
|
11
|
-
PolymarketPriceUpdateEvent
|
|
11
|
+
PolymarketPriceUpdateEvent,
|
|
12
|
+
isPriceChangeEvent
|
|
12
13
|
} from './types/PolymarketWebSocket';
|
|
13
14
|
import { SubscriptionManagerOptions } from './types/WebSocketSubscriptions';
|
|
14
15
|
|
|
@@ -111,16 +112,37 @@ class WSSubscriptionManager {
|
|
|
111
112
|
|
|
112
113
|
// Filter out events that are not subscribed to by any groups
|
|
113
114
|
events = _.filter(events, (event: T) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
// Handle PriceChangeEvent which doesn't have asset_id at root
|
|
116
|
+
if (isPriceChangeEvent(event)) {
|
|
117
|
+
// Check if any of the price_changes are subscribed
|
|
118
|
+
return event.price_changes.some(price_change_item => {
|
|
119
|
+
const groupIndices = this.groupRegistry.getGroupIndicesForAsset(price_change_item.asset_id);
|
|
120
|
+
if (groupIndices.length > 1) {
|
|
121
|
+
logger.warn({
|
|
122
|
+
message: 'Found multiple groups for asset',
|
|
123
|
+
asset_id: price_change_item.asset_id,
|
|
124
|
+
group_indices: groupIndices
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return groupIndices.length > 0;
|
|
121
128
|
});
|
|
122
129
|
}
|
|
123
|
-
|
|
130
|
+
|
|
131
|
+
// For all other events, check asset_id at root
|
|
132
|
+
if ('asset_id' in event) {
|
|
133
|
+
const groupIndices = this.groupRegistry.getGroupIndicesForAsset(event.asset_id);
|
|
134
|
+
|
|
135
|
+
if (groupIndices.length > 1) {
|
|
136
|
+
logger.warn({
|
|
137
|
+
message: 'Found multiple groups for asset',
|
|
138
|
+
asset_id: event.asset_id,
|
|
139
|
+
group_indices: groupIndices
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return groupIndices.length > 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
124
146
|
});
|
|
125
147
|
|
|
126
148
|
await action?.(events);
|
|
@@ -74,6 +74,12 @@ export class GroupSocket {
|
|
|
74
74
|
private setupEventHandlers() {
|
|
75
75
|
const group = this.group;
|
|
76
76
|
const handlers = this.handlers;
|
|
77
|
+
|
|
78
|
+
// Capture the current WebSocket instance to avoid race conditions
|
|
79
|
+
const currentWebSocket = group.wsClient;
|
|
80
|
+
if (!currentWebSocket) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
77
83
|
|
|
78
84
|
/*
|
|
79
85
|
Define handlers within this scope to capture 'this' context
|
|
@@ -84,9 +90,28 @@ export class GroupSocket {
|
|
|
84
90
|
return;
|
|
85
91
|
}
|
|
86
92
|
|
|
93
|
+
// Verify this handler is for the current WebSocket instance
|
|
94
|
+
if (currentWebSocket !== group.wsClient) {
|
|
95
|
+
logger.warn({
|
|
96
|
+
message: 'handleOpen called for stale WebSocket instance',
|
|
97
|
+
groupId: group.groupId,
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Additional safety check for readyState
|
|
103
|
+
if (currentWebSocket.readyState !== WebSocket.OPEN) {
|
|
104
|
+
logger.warn({
|
|
105
|
+
message: 'handleOpen called but WebSocket is not in OPEN state',
|
|
106
|
+
groupId: group.groupId,
|
|
107
|
+
readyState: currentWebSocket.readyState,
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
87
112
|
group.status = WebSocketStatus.ALIVE;
|
|
88
113
|
|
|
89
|
-
|
|
114
|
+
currentWebSocket.send(JSON.stringify({ assets_ids: Array.from(group.assetIds), type: 'market' }));
|
|
90
115
|
await handlers.onWSOpen?.(group.groupId, Array.from(group.assetIds));
|
|
91
116
|
|
|
92
117
|
this.pingInterval = setInterval(() => {
|
|
@@ -96,26 +121,47 @@ export class GroupSocket {
|
|
|
96
121
|
return;
|
|
97
122
|
}
|
|
98
123
|
|
|
99
|
-
|
|
124
|
+
// Verify we're still using the same WebSocket
|
|
125
|
+
if (currentWebSocket !== group.wsClient) {
|
|
126
|
+
clearInterval(this.pingInterval);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!currentWebSocket || currentWebSocket.readyState !== WebSocket.OPEN) {
|
|
100
131
|
clearInterval(this.pingInterval);
|
|
101
132
|
group.status = WebSocketStatus.DEAD;
|
|
102
133
|
return;
|
|
103
134
|
}
|
|
104
|
-
|
|
135
|
+
currentWebSocket.ping();
|
|
105
136
|
}, randomInt(ms('15s'), ms('25s')));
|
|
106
137
|
};
|
|
107
138
|
|
|
108
139
|
const handleMessage = async (data: Buffer) => {
|
|
140
|
+
const messageStr = data.toString();
|
|
141
|
+
|
|
142
|
+
// Handle PONG messages that might be sent to message handler during handler reattachment
|
|
143
|
+
if (messageStr === 'PONG') {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
109
147
|
let events: PolymarketWSEvent[] = [];
|
|
110
148
|
try {
|
|
111
|
-
const parsedData: any = JSON.parse(
|
|
149
|
+
const parsedData: any = JSON.parse(messageStr);
|
|
112
150
|
events = Array.isArray(parsedData) ? parsedData : [parsedData];
|
|
113
151
|
} catch (err) {
|
|
114
|
-
await handlers.onError?.(new Error(`Not JSON: ${
|
|
152
|
+
await handlers.onError?.(new Error(`Not JSON: ${messageStr}`));
|
|
115
153
|
return;
|
|
116
154
|
}
|
|
117
155
|
|
|
118
|
-
|
|
156
|
+
// Filter events to ensure validity
|
|
157
|
+
events = _.filter(events, (event: PolymarketWSEvent) => {
|
|
158
|
+
// For price_change events, check that price_changes array exists
|
|
159
|
+
if (isPriceChangeEvent(event)) {
|
|
160
|
+
return event.price_changes && event.price_changes.length > 0;
|
|
161
|
+
}
|
|
162
|
+
// For all other events, check asset_id
|
|
163
|
+
return _.size(event.asset_id) > 0;
|
|
164
|
+
});
|
|
119
165
|
|
|
120
166
|
const bookEvents: BookEvent[] = [];
|
|
121
167
|
const lastTradeEvents: LastTradePriceEvent[] = [];
|
|
@@ -127,21 +173,32 @@ export class GroupSocket {
|
|
|
127
173
|
Skip events for asset ids that are not in the group to ensure that
|
|
128
174
|
we don't get stale events for assets that were removed.
|
|
129
175
|
*/
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
176
|
+
if (isPriceChangeEvent(event)) {
|
|
177
|
+
// Check if any of the price_changes are for assets in this group
|
|
178
|
+
const relevantChanges = event.price_changes.filter(price_change_item => group.assetIds.has(price_change_item.asset_id));
|
|
179
|
+
if (relevantChanges.length === 0) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
// Only include relevant changes
|
|
183
|
+
priceChangeEvents.push({
|
|
184
|
+
...event,
|
|
185
|
+
price_changes: relevantChanges
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
// For all other events, check asset_id at root
|
|
189
|
+
if (!group.assetIds.has(event.asset_id!)) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
133
192
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
else {
|
|
144
|
-
await handlers.onError?.(new Error(`Unknown event: ${JSON.stringify(event)}`));
|
|
193
|
+
if (isBookEvent(event)) {
|
|
194
|
+
bookEvents.push(event);
|
|
195
|
+
} else if (isLastTradePriceEvent(event)) {
|
|
196
|
+
lastTradeEvents.push(event);
|
|
197
|
+
} else if (isTickSizeChangeEvent(event)) {
|
|
198
|
+
tickEvents.push(event);
|
|
199
|
+
} else {
|
|
200
|
+
await handlers.onError?.(new Error(`Unknown event: ${JSON.stringify(event)}`));
|
|
201
|
+
}
|
|
145
202
|
}
|
|
146
203
|
}
|
|
147
204
|
|
|
@@ -161,33 +218,26 @@ export class GroupSocket {
|
|
|
161
218
|
handlers.onError?.(new Error(`WebSocket error for group ${group.groupId}: ${err.message}`));
|
|
162
219
|
};
|
|
163
220
|
|
|
164
|
-
const handleClose = async (code: number, reason
|
|
221
|
+
const handleClose = async (code: number, reason?: Buffer) => {
|
|
165
222
|
group.status = WebSocketStatus.DEAD;
|
|
166
223
|
clearInterval(this.pingInterval);
|
|
167
|
-
await handlers.onWSClose?.(group.groupId, code, reason
|
|
224
|
+
await handlers.onWSClose?.(group.groupId, code, reason?.toString() || '');
|
|
168
225
|
};
|
|
169
226
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
group.wsClient.removeAllListeners();
|
|
227
|
+
// Remove any existing handlers
|
|
228
|
+
currentWebSocket.removeAllListeners();
|
|
173
229
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
230
|
+
// Add the handlers
|
|
231
|
+
currentWebSocket.on('open', handleOpen);
|
|
232
|
+
currentWebSocket.on('message', handleMessage);
|
|
233
|
+
currentWebSocket.on('pong', handlePong);
|
|
234
|
+
currentWebSocket.on('error', handleError);
|
|
235
|
+
currentWebSocket.on('close', handleClose);
|
|
181
236
|
|
|
182
237
|
if (group.assetIds.size === 0) {
|
|
183
238
|
group.status = WebSocketStatus.CLEANUP;
|
|
184
239
|
return;
|
|
185
240
|
}
|
|
186
|
-
|
|
187
|
-
if (!group.wsClient) {
|
|
188
|
-
group.status = WebSocketStatus.DEAD;
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
241
|
}
|
|
192
242
|
|
|
193
243
|
private async handleBookEvents(bookEvents: BookEvent[]): Promise<void> {
|
|
@@ -215,63 +265,67 @@ export class GroupSocket {
|
|
|
215
265
|
} catch (err: any) {
|
|
216
266
|
logger.debug({
|
|
217
267
|
message: `Skipping derived future price calculation price_change: book not found for asset`,
|
|
218
|
-
asset_id: event.asset_id,
|
|
219
268
|
event: event,
|
|
220
269
|
error: err?.message
|
|
221
270
|
});
|
|
222
271
|
continue;
|
|
223
272
|
}
|
|
224
273
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
spreadOver10Cents = this.bookCache.spreadOver(event.asset_id, 0.1);
|
|
228
|
-
} catch (err: any) {
|
|
229
|
-
logger.debug({
|
|
230
|
-
message: 'Skipping derived future price calculation for price_change: error calculating spread',
|
|
231
|
-
asset_id: event.asset_id,
|
|
232
|
-
event: event,
|
|
233
|
-
error: err?.message
|
|
234
|
-
});
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
274
|
+
// Handle price updates per asset
|
|
275
|
+
const assetIds: string[] = event.price_changes.map(price_change_item => price_change_item.asset_id);
|
|
237
276
|
|
|
238
|
-
|
|
239
|
-
let
|
|
277
|
+
for (const assetId of assetIds) {
|
|
278
|
+
let spreadOver10Cents: boolean;
|
|
240
279
|
try {
|
|
241
|
-
|
|
280
|
+
spreadOver10Cents = this.bookCache.spreadOver(assetId, 0.1);
|
|
242
281
|
} catch (err: any) {
|
|
243
282
|
logger.debug({
|
|
244
|
-
message: 'Skipping derived future price calculation for price_change: error calculating
|
|
245
|
-
asset_id:
|
|
283
|
+
message: 'Skipping derived future price calculation for price_change: error calculating spread',
|
|
284
|
+
asset_id: assetId,
|
|
246
285
|
event: event,
|
|
247
286
|
error: err?.message
|
|
248
287
|
});
|
|
249
288
|
continue;
|
|
250
289
|
}
|
|
251
290
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
if (!spreadOver10Cents) {
|
|
292
|
+
let newPrice: string;
|
|
293
|
+
try {
|
|
294
|
+
newPrice = this.bookCache.midpoint(assetId);
|
|
295
|
+
} catch (err: any) {
|
|
296
|
+
logger.debug({
|
|
297
|
+
message: 'Skipping derived future price calculation for price_change: error calculating midpoint',
|
|
298
|
+
asset_id: assetId,
|
|
299
|
+
event: event,
|
|
300
|
+
error: err?.message
|
|
301
|
+
});
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const bookEntry: BookEntry | null = this.bookCache.getBookEntry(assetId);
|
|
306
|
+
if (!bookEntry) {
|
|
307
|
+
logger.debug({
|
|
308
|
+
message: 'Skipping derived future price calculation price_change: book not found for asset',
|
|
309
|
+
asset_id: assetId,
|
|
310
|
+
event: event,
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (newPrice !== bookEntry.price) {
|
|
316
|
+
bookEntry.price = newPrice;
|
|
317
|
+
const priceUpdateEvent: PolymarketPriceUpdateEvent = {
|
|
318
|
+
asset_id: assetId,
|
|
319
|
+
event_type: 'price_update',
|
|
320
|
+
triggeringEvent: event,
|
|
321
|
+
timestamp: event.timestamp,
|
|
322
|
+
book: { bids: bookEntry.bids, asks: bookEntry.asks },
|
|
323
|
+
price: newPrice,
|
|
324
|
+
midpoint: bookEntry.midpoint || '',
|
|
325
|
+
spread: bookEntry.spread || '',
|
|
326
|
+
};
|
|
327
|
+
await this.handlers.onPolymarketPriceUpdate?.([priceUpdateEvent]);
|
|
328
|
+
}
|
|
275
329
|
}
|
|
276
330
|
}
|
|
277
331
|
}
|
|
@@ -72,13 +72,14 @@ export class OrderBookCache {
|
|
|
72
72
|
* Throws if the book is not found.
|
|
73
73
|
*/
|
|
74
74
|
public upsertPriceChange(event: PriceChangeEvent): void {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
// Iterate through price_changes array
|
|
76
|
+
for (const priceChange of event.price_changes) {
|
|
77
|
+
const book = this.bookCache[priceChange.asset_id];
|
|
78
|
+
if (!book) {
|
|
79
|
+
throw new Error(`Book not found for asset ${priceChange.asset_id}`);
|
|
80
|
+
}
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
const { price, size, side } = change;
|
|
82
|
+
const { price, size, side } = priceChange;
|
|
82
83
|
if (side === 'BUY') {
|
|
83
84
|
const i = book.bids.findIndex(bid => bid.price === price);
|
|
84
85
|
if (i !== -1) {
|
|
@@ -8,27 +8,45 @@ export type PriceLevel = {
|
|
|
8
8
|
size: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Represents a single price change item
|
|
13
|
+
*/
|
|
14
|
+
export type PriceChangeItem = {
|
|
15
|
+
asset_id: string;
|
|
16
|
+
price: string;
|
|
17
|
+
size: string;
|
|
18
|
+
side: 'BUY' | 'SELL';
|
|
19
|
+
hash: string;
|
|
20
|
+
best_bid: string;
|
|
21
|
+
best_ask: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
11
24
|
/**
|
|
12
25
|
* Represents a price_change event from Polymarket WebSocket
|
|
13
|
-
*
|
|
26
|
+
*
|
|
27
|
+
* Schema example:
|
|
14
28
|
* {
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* {
|
|
29
|
+
* market: "0x5f65177b394277fd294cd75650044e32ba009a95022d88a0c1d565897d72f8f1",
|
|
30
|
+
* price_changes: [
|
|
31
|
+
* {
|
|
32
|
+
* asset_id: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
|
|
33
|
+
* price: "0.5",
|
|
34
|
+
* size: "200",
|
|
35
|
+
* side: "BUY",
|
|
36
|
+
* hash: "56621a121a47ed9333273e21c83b660cff37ae50",
|
|
37
|
+
* best_bid: "0.5",
|
|
38
|
+
* best_ask: "1"
|
|
39
|
+
* }
|
|
18
40
|
* ],
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* market: "0x5412ae25e97078f814157de948459d59c6221b4c4c495fdd57b536543ad36729",
|
|
22
|
-
* timestamp: "1749371014925"
|
|
41
|
+
* timestamp: "1757908892351",
|
|
42
|
+
* event_type: "price_change"
|
|
23
43
|
* }
|
|
24
44
|
*/
|
|
25
45
|
export type PriceChangeEvent = {
|
|
26
|
-
asset_id: string;
|
|
27
|
-
changes: { price: string; side: string; size: string }[];
|
|
28
46
|
event_type: 'price_change';
|
|
29
|
-
hash: string;
|
|
30
47
|
market: string;
|
|
31
48
|
timestamp: string;
|
|
49
|
+
price_changes: PriceChangeItem[];
|
|
32
50
|
};
|
|
33
51
|
|
|
34
52
|
/**
|
|
@@ -151,14 +169,20 @@ export type TickSizeChangeEvent = {
|
|
|
151
169
|
*
|
|
152
170
|
* @example PriceChangeEvent
|
|
153
171
|
* {
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* {
|
|
172
|
+
* market: "0x5f65177b394277fd294cd75650044e32ba009a95022d88a0c1d565897d72f8f1",
|
|
173
|
+
* price_changes: [
|
|
174
|
+
* {
|
|
175
|
+
* asset_id: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
|
|
176
|
+
* price: "0.5",
|
|
177
|
+
* size: "200",
|
|
178
|
+
* side: "BUY",
|
|
179
|
+
* hash: "56621a121a47ed9333273e21c83b660cff37ae50",
|
|
180
|
+
* best_bid: "0.5",
|
|
181
|
+
* best_ask: "1"
|
|
182
|
+
* }
|
|
157
183
|
* ],
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* market: "0x5412ae25e97078f814157de948459d59c6221b4c4c495fdd57b536543ad36729",
|
|
161
|
-
* timestamp: "1749371014925"
|
|
184
|
+
* timestamp: "1757908892351",
|
|
185
|
+
* event_type: "price_change"
|
|
162
186
|
* }
|
|
163
187
|
*
|
|
164
188
|
* @example TickSizeChangeEvent
|
|
@@ -239,7 +263,7 @@ export type WebSocketHandlers = {
|
|
|
239
263
|
* console.log(event.bids);
|
|
240
264
|
* }
|
|
241
265
|
*/
|
|
242
|
-
export function isBookEvent(event: PolymarketWSEvent): event is BookEvent {
|
|
266
|
+
export function isBookEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is BookEvent {
|
|
243
267
|
return event?.event_type === 'book';
|
|
244
268
|
}
|
|
245
269
|
|
|
@@ -251,7 +275,7 @@ export function isBookEvent(event: PolymarketWSEvent): event is BookEvent {
|
|
|
251
275
|
* console.log(event.side);
|
|
252
276
|
* }
|
|
253
277
|
*/
|
|
254
|
-
export function isLastTradePriceEvent(event: PolymarketWSEvent): event is LastTradePriceEvent {
|
|
278
|
+
export function isLastTradePriceEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is LastTradePriceEvent {
|
|
255
279
|
return event?.event_type === 'last_trade_price';
|
|
256
280
|
}
|
|
257
281
|
|
|
@@ -263,7 +287,7 @@ export function isLastTradePriceEvent(event: PolymarketWSEvent): event is LastTr
|
|
|
263
287
|
* console.log(event.changes);
|
|
264
288
|
* }
|
|
265
289
|
*/
|
|
266
|
-
export function isPriceChangeEvent(event: PolymarketWSEvent): event is PriceChangeEvent {
|
|
290
|
+
export function isPriceChangeEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is PriceChangeEvent {
|
|
267
291
|
return event?.event_type === 'price_change';
|
|
268
292
|
}
|
|
269
293
|
|
|
@@ -275,6 +299,6 @@ export function isPriceChangeEvent(event: PolymarketWSEvent): event is PriceChan
|
|
|
275
299
|
* console.log(event.old_tick_size);
|
|
276
300
|
* }
|
|
277
301
|
*/
|
|
278
|
-
export function isTickSizeChangeEvent(event: PolymarketWSEvent): event is TickSizeChangeEvent {
|
|
302
|
+
export function isTickSizeChangeEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is TickSizeChangeEvent {
|
|
279
303
|
return event?.event_type === 'tick_size_change';
|
|
280
|
-
}
|
|
304
|
+
}
|