@nevuamarkets/poly-websockets 0.1.5 → 0.2.1
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 +153 -95
- 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 +157 -95
- 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,58 +99,97 @@ 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
|
-
var _a, _b;
|
|
90
|
-
let events = [];
|
|
116
|
+
var _a, _b, _c;
|
|
91
117
|
try {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
await ((_a = handlers.onError) === null || _a === void 0 ? void 0 : _a.call(handlers, new Error(`Not JSON: ${data.toString()}`)));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
events = lodash_1.default.filter(events, (event) => lodash_1.default.size(event.asset_id) > 0);
|
|
100
|
-
const bookEvents = [];
|
|
101
|
-
const lastTradeEvents = [];
|
|
102
|
-
const tickEvents = [];
|
|
103
|
-
const priceChangeEvents = [];
|
|
104
|
-
for (const event of events) {
|
|
105
|
-
/*
|
|
106
|
-
Skip events for asset ids that are not in the group to ensure that
|
|
107
|
-
we don't get stale events for assets that were removed.
|
|
108
|
-
*/
|
|
109
|
-
if (!group.assetIds.has(event.asset_id)) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if ((0, PolymarketWebSocket_1.isBookEvent)(event)) {
|
|
113
|
-
bookEvents.push(event);
|
|
114
|
-
}
|
|
115
|
-
else if ((0, PolymarketWebSocket_1.isLastTradePriceEvent)(event)) {
|
|
116
|
-
lastTradeEvents.push(event);
|
|
118
|
+
const messageStr = data.toString();
|
|
119
|
+
// Handle PONG messages that might be sent to message handler during handler reattachment
|
|
120
|
+
if (messageStr === 'PONG') {
|
|
121
|
+
return;
|
|
117
122
|
}
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
let events = [];
|
|
124
|
+
try {
|
|
125
|
+
const parsedData = JSON.parse(messageStr);
|
|
126
|
+
events = Array.isArray(parsedData) ? parsedData : [parsedData];
|
|
120
127
|
}
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
catch (err) {
|
|
129
|
+
await ((_a = handlers.onError) === null || _a === void 0 ? void 0 : _a.call(handlers, new Error(`Not JSON: ${messageStr}`)));
|
|
130
|
+
return;
|
|
123
131
|
}
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
// Filter events to ensure validity
|
|
133
|
+
events = lodash_1.default.filter(events, (event) => {
|
|
134
|
+
if (!event) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// For price_change events, check that price_changes array exists
|
|
138
|
+
if ((0, PolymarketWebSocket_1.isPriceChangeEvent)(event)) {
|
|
139
|
+
return event.price_changes && event.price_changes.length > 0;
|
|
140
|
+
}
|
|
141
|
+
// For all other events, check asset_id
|
|
142
|
+
return lodash_1.default.size(event.asset_id) > 0;
|
|
143
|
+
});
|
|
144
|
+
const bookEvents = [];
|
|
145
|
+
const lastTradeEvents = [];
|
|
146
|
+
const tickEvents = [];
|
|
147
|
+
const priceChangeEvents = [];
|
|
148
|
+
for (const event of events) {
|
|
149
|
+
/*
|
|
150
|
+
Skip events for asset ids that are not in the group to ensure that
|
|
151
|
+
we don't get stale events for assets that were removed.
|
|
152
|
+
*/
|
|
153
|
+
if ((0, PolymarketWebSocket_1.isPriceChangeEvent)(event)) {
|
|
154
|
+
// Check if any of the price_changes are for assets in this group
|
|
155
|
+
const relevantChanges = event.price_changes.filter(price_change_item => group.assetIds.has(price_change_item.asset_id));
|
|
156
|
+
if (relevantChanges.length === 0) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Only include relevant changes
|
|
160
|
+
priceChangeEvents.push({
|
|
161
|
+
...event,
|
|
162
|
+
price_changes: relevantChanges
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// For all other events, check asset_id at root
|
|
167
|
+
if (!group.assetIds.has(event.asset_id)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if ((0, PolymarketWebSocket_1.isBookEvent)(event)) {
|
|
171
|
+
bookEvents.push(event);
|
|
172
|
+
}
|
|
173
|
+
else if ((0, PolymarketWebSocket_1.isLastTradePriceEvent)(event)) {
|
|
174
|
+
lastTradeEvents.push(event);
|
|
175
|
+
}
|
|
176
|
+
else if ((0, PolymarketWebSocket_1.isTickSizeChangeEvent)(event)) {
|
|
177
|
+
tickEvents.push(event);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
await ((_b = handlers.onError) === null || _b === void 0 ? void 0 : _b.call(handlers, new Error(`Unknown event: ${JSON.stringify(event)}`)));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
126
183
|
}
|
|
184
|
+
await this.handleBookEvents(bookEvents);
|
|
185
|
+
await this.handleTickEvents(tickEvents);
|
|
186
|
+
await this.handlePriceChangeEvents(priceChangeEvents);
|
|
187
|
+
await this.handleLastTradeEvents(lastTradeEvents);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
// handler-wide error handling
|
|
191
|
+
await ((_c = handlers.onError) === null || _c === void 0 ? void 0 : _c.call(handlers, new Error(`Error handling message: ${err}`)));
|
|
127
192
|
}
|
|
128
|
-
await this.handleBookEvents(bookEvents);
|
|
129
|
-
await this.handleTickEvents(tickEvents);
|
|
130
|
-
await this.handlePriceChangeEvents(priceChangeEvents);
|
|
131
|
-
await this.handleLastTradeEvents(lastTradeEvents);
|
|
132
193
|
};
|
|
133
194
|
const handlePong = () => {
|
|
134
195
|
group.groupId;
|
|
@@ -145,24 +206,18 @@ class GroupSocket {
|
|
|
145
206
|
clearInterval(this.pingInterval);
|
|
146
207
|
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
208
|
};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
group.wsClient.on('close', handleClose);
|
|
157
|
-
}
|
|
209
|
+
// Remove any existing handlers
|
|
210
|
+
currentWebSocket.removeAllListeners();
|
|
211
|
+
// Add the handlers
|
|
212
|
+
currentWebSocket.on('open', handleOpen);
|
|
213
|
+
currentWebSocket.on('message', handleMessage);
|
|
214
|
+
currentWebSocket.on('pong', handlePong);
|
|
215
|
+
currentWebSocket.on('error', handleError);
|
|
216
|
+
currentWebSocket.on('close', handleClose);
|
|
158
217
|
if (group.assetIds.size === 0) {
|
|
159
218
|
group.status = WebSocketSubscriptions_1.WebSocketStatus.CLEANUP;
|
|
160
219
|
return;
|
|
161
220
|
}
|
|
162
|
-
if (!group.wsClient) {
|
|
163
|
-
group.status = WebSocketSubscriptions_1.WebSocketStatus.DEAD;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
221
|
}
|
|
167
222
|
async handleBookEvents(bookEvents) {
|
|
168
223
|
var _a, _b;
|
|
@@ -190,61 +245,64 @@ class GroupSocket {
|
|
|
190
245
|
catch (err) {
|
|
191
246
|
logger_1.logger.debug({
|
|
192
247
|
message: `Skipping derived future price calculation price_change: book not found for asset`,
|
|
193
|
-
asset_id: event.asset_id,
|
|
194
|
-
event: event,
|
|
195
|
-
error: err === null || err === void 0 ? void 0 : err.message
|
|
196
|
-
});
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
let spreadOver10Cents;
|
|
200
|
-
try {
|
|
201
|
-
spreadOver10Cents = this.bookCache.spreadOver(event.asset_id, 0.1);
|
|
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
248
|
event: event,
|
|
208
249
|
error: err === null || err === void 0 ? void 0 : err.message
|
|
209
250
|
});
|
|
210
251
|
continue;
|
|
211
252
|
}
|
|
212
|
-
|
|
213
|
-
|
|
253
|
+
// Handle price updates per asset
|
|
254
|
+
const assetIds = event.price_changes.map(price_change_item => price_change_item.asset_id);
|
|
255
|
+
for (const assetId of assetIds) {
|
|
256
|
+
let spreadOver10Cents;
|
|
214
257
|
try {
|
|
215
|
-
|
|
258
|
+
spreadOver10Cents = this.bookCache.spreadOver(assetId, 0.1);
|
|
216
259
|
}
|
|
217
260
|
catch (err) {
|
|
218
261
|
logger_1.logger.debug({
|
|
219
|
-
message: 'Skipping derived future price calculation for price_change: error calculating
|
|
220
|
-
asset_id:
|
|
262
|
+
message: 'Skipping derived future price calculation for price_change: error calculating spread',
|
|
263
|
+
asset_id: assetId,
|
|
221
264
|
event: event,
|
|
222
265
|
error: err === null || err === void 0 ? void 0 : err.message
|
|
223
266
|
});
|
|
224
267
|
continue;
|
|
225
268
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
269
|
+
if (!spreadOver10Cents) {
|
|
270
|
+
let newPrice;
|
|
271
|
+
try {
|
|
272
|
+
newPrice = this.bookCache.midpoint(assetId);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
logger_1.logger.debug({
|
|
276
|
+
message: 'Skipping derived future price calculation for price_change: error calculating midpoint',
|
|
277
|
+
asset_id: assetId,
|
|
278
|
+
event: event,
|
|
279
|
+
error: err === null || err === void 0 ? void 0 : err.message
|
|
280
|
+
});
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const bookEntry = this.bookCache.getBookEntry(assetId);
|
|
284
|
+
if (!bookEntry) {
|
|
285
|
+
logger_1.logger.debug({
|
|
286
|
+
message: 'Skipping derived future price calculation price_change: book not found for asset',
|
|
287
|
+
asset_id: assetId,
|
|
288
|
+
event: event,
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (newPrice !== bookEntry.price) {
|
|
293
|
+
bookEntry.price = newPrice;
|
|
294
|
+
const priceUpdateEvent = {
|
|
295
|
+
asset_id: assetId,
|
|
296
|
+
event_type: 'price_update',
|
|
297
|
+
triggeringEvent: event,
|
|
298
|
+
timestamp: event.timestamp,
|
|
299
|
+
book: { bids: bookEntry.bids, asks: bookEntry.asks },
|
|
300
|
+
price: newPrice,
|
|
301
|
+
midpoint: bookEntry.midpoint || '',
|
|
302
|
+
spread: bookEntry.spread || '',
|
|
303
|
+
};
|
|
304
|
+
await ((_d = (_c = this.handlers).onPolymarketPriceUpdate) === null || _d === void 0 ? void 0 : _d.call(_c, [priceUpdateEvent]));
|
|
305
|
+
}
|
|
248
306
|
}
|
|
249
307
|
}
|
|
250
308
|
}
|
|
@@ -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,59 +121,99 @@ 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) => {
|
|
109
|
-
let events: PolymarketWSEvent[] = [];
|
|
110
140
|
try {
|
|
111
|
-
const
|
|
112
|
-
events = Array.isArray(parsedData) ? parsedData : [parsedData];
|
|
113
|
-
} catch (err) {
|
|
114
|
-
await handlers.onError?.(new Error(`Not JSON: ${data.toString()}`));
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
events = _.filter(events, (event: PolymarketWSEvent) => _.size(event.asset_id) > 0);
|
|
119
|
-
|
|
120
|
-
const bookEvents: BookEvent[] = [];
|
|
121
|
-
const lastTradeEvents: LastTradePriceEvent[] = [];
|
|
122
|
-
const tickEvents: TickSizeChangeEvent[] = [];
|
|
123
|
-
const priceChangeEvents: PriceChangeEvent[] = [];
|
|
141
|
+
const messageStr = data.toString();
|
|
124
142
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
we don't get stale events for assets that were removed.
|
|
129
|
-
*/
|
|
130
|
-
if (!group.assetIds.has(event.asset_id)) {
|
|
131
|
-
continue;
|
|
143
|
+
// Handle PONG messages that might be sent to message handler during handler reattachment
|
|
144
|
+
if (messageStr === 'PONG') {
|
|
145
|
+
return;
|
|
132
146
|
}
|
|
133
147
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
priceChangeEvents.push(event);
|
|
148
|
+
let events: PolymarketWSEvent[] = [];
|
|
149
|
+
try {
|
|
150
|
+
const parsedData: any = JSON.parse(messageStr);
|
|
151
|
+
events = Array.isArray(parsedData) ? parsedData : [parsedData];
|
|
152
|
+
} catch (err) {
|
|
153
|
+
await handlers.onError?.(new Error(`Not JSON: ${messageStr}`));
|
|
154
|
+
return;
|
|
142
155
|
}
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
|
|
157
|
+
// Filter events to ensure validity
|
|
158
|
+
events = _.filter(events, (event: PolymarketWSEvent) => {
|
|
159
|
+
if (!event) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
// For price_change events, check that price_changes array exists
|
|
163
|
+
if (isPriceChangeEvent(event)) {
|
|
164
|
+
return event.price_changes && event.price_changes.length > 0;
|
|
165
|
+
}
|
|
166
|
+
// For all other events, check asset_id
|
|
167
|
+
return _.size(event.asset_id) > 0;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const bookEvents: BookEvent[] = [];
|
|
171
|
+
const lastTradeEvents: LastTradePriceEvent[] = [];
|
|
172
|
+
const tickEvents: TickSizeChangeEvent[] = [];
|
|
173
|
+
const priceChangeEvents: PriceChangeEvent[] = [];
|
|
174
|
+
|
|
175
|
+
for (const event of events) {
|
|
176
|
+
/*
|
|
177
|
+
Skip events for asset ids that are not in the group to ensure that
|
|
178
|
+
we don't get stale events for assets that were removed.
|
|
179
|
+
*/
|
|
180
|
+
if (isPriceChangeEvent(event)) {
|
|
181
|
+
// Check if any of the price_changes are for assets in this group
|
|
182
|
+
const relevantChanges = event.price_changes.filter(price_change_item => group.assetIds.has(price_change_item.asset_id));
|
|
183
|
+
if (relevantChanges.length === 0) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Only include relevant changes
|
|
187
|
+
priceChangeEvents.push({
|
|
188
|
+
...event,
|
|
189
|
+
price_changes: relevantChanges
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
// For all other events, check asset_id at root
|
|
193
|
+
if (!group.assetIds.has(event.asset_id!)) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (isBookEvent(event)) {
|
|
198
|
+
bookEvents.push(event);
|
|
199
|
+
} else if (isLastTradePriceEvent(event)) {
|
|
200
|
+
lastTradeEvents.push(event);
|
|
201
|
+
} else if (isTickSizeChangeEvent(event)) {
|
|
202
|
+
tickEvents.push(event);
|
|
203
|
+
} else {
|
|
204
|
+
await handlers.onError?.(new Error(`Unknown event: ${JSON.stringify(event)}`));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
145
207
|
}
|
|
146
|
-
}
|
|
147
208
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
209
|
+
await this.handleBookEvents(bookEvents);
|
|
210
|
+
await this.handleTickEvents(tickEvents);
|
|
211
|
+
await this.handlePriceChangeEvents(priceChangeEvents);
|
|
212
|
+
await this.handleLastTradeEvents(lastTradeEvents);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
// handler-wide error handling
|
|
215
|
+
await handlers.onError?.(new Error(`Error handling message: ${err}`));
|
|
216
|
+
}
|
|
152
217
|
};
|
|
153
218
|
|
|
154
219
|
const handlePong = () => {
|
|
@@ -167,27 +232,20 @@ export class GroupSocket {
|
|
|
167
232
|
await handlers.onWSClose?.(group.groupId, code, reason?.toString() || '');
|
|
168
233
|
};
|
|
169
234
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
group.wsClient.removeAllListeners();
|
|
235
|
+
// Remove any existing handlers
|
|
236
|
+
currentWebSocket.removeAllListeners();
|
|
173
237
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
238
|
+
// Add the handlers
|
|
239
|
+
currentWebSocket.on('open', handleOpen);
|
|
240
|
+
currentWebSocket.on('message', handleMessage);
|
|
241
|
+
currentWebSocket.on('pong', handlePong);
|
|
242
|
+
currentWebSocket.on('error', handleError);
|
|
243
|
+
currentWebSocket.on('close', handleClose);
|
|
181
244
|
|
|
182
245
|
if (group.assetIds.size === 0) {
|
|
183
246
|
group.status = WebSocketStatus.CLEANUP;
|
|
184
247
|
return;
|
|
185
248
|
}
|
|
186
|
-
|
|
187
|
-
if (!group.wsClient) {
|
|
188
|
-
group.status = WebSocketStatus.DEAD;
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
249
|
}
|
|
192
250
|
|
|
193
251
|
private async handleBookEvents(bookEvents: BookEvent[]): Promise<void> {
|
|
@@ -215,63 +273,67 @@ export class GroupSocket {
|
|
|
215
273
|
} catch (err: any) {
|
|
216
274
|
logger.debug({
|
|
217
275
|
message: `Skipping derived future price calculation price_change: book not found for asset`,
|
|
218
|
-
asset_id: event.asset_id,
|
|
219
276
|
event: event,
|
|
220
277
|
error: err?.message
|
|
221
278
|
});
|
|
222
279
|
continue;
|
|
223
280
|
}
|
|
224
281
|
|
|
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
|
-
}
|
|
282
|
+
// Handle price updates per asset
|
|
283
|
+
const assetIds: string[] = event.price_changes.map(price_change_item => price_change_item.asset_id);
|
|
237
284
|
|
|
238
|
-
|
|
239
|
-
let
|
|
285
|
+
for (const assetId of assetIds) {
|
|
286
|
+
let spreadOver10Cents: boolean;
|
|
240
287
|
try {
|
|
241
|
-
|
|
288
|
+
spreadOver10Cents = this.bookCache.spreadOver(assetId, 0.1);
|
|
242
289
|
} catch (err: any) {
|
|
243
290
|
logger.debug({
|
|
244
|
-
message: 'Skipping derived future price calculation for price_change: error calculating
|
|
245
|
-
asset_id:
|
|
291
|
+
message: 'Skipping derived future price calculation for price_change: error calculating spread',
|
|
292
|
+
asset_id: assetId,
|
|
246
293
|
event: event,
|
|
247
294
|
error: err?.message
|
|
248
295
|
});
|
|
249
296
|
continue;
|
|
250
297
|
}
|
|
251
298
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
299
|
+
if (!spreadOver10Cents) {
|
|
300
|
+
let newPrice: string;
|
|
301
|
+
try {
|
|
302
|
+
newPrice = this.bookCache.midpoint(assetId);
|
|
303
|
+
} catch (err: any) {
|
|
304
|
+
logger.debug({
|
|
305
|
+
message: 'Skipping derived future price calculation for price_change: error calculating midpoint',
|
|
306
|
+
asset_id: assetId,
|
|
307
|
+
event: event,
|
|
308
|
+
error: err?.message
|
|
309
|
+
});
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const bookEntry: BookEntry | null = this.bookCache.getBookEntry(assetId);
|
|
314
|
+
if (!bookEntry) {
|
|
315
|
+
logger.debug({
|
|
316
|
+
message: 'Skipping derived future price calculation price_change: book not found for asset',
|
|
317
|
+
asset_id: assetId,
|
|
318
|
+
event: event,
|
|
319
|
+
});
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (newPrice !== bookEntry.price) {
|
|
324
|
+
bookEntry.price = newPrice;
|
|
325
|
+
const priceUpdateEvent: PolymarketPriceUpdateEvent = {
|
|
326
|
+
asset_id: assetId,
|
|
327
|
+
event_type: 'price_update',
|
|
328
|
+
triggeringEvent: event,
|
|
329
|
+
timestamp: event.timestamp,
|
|
330
|
+
book: { bids: bookEntry.bids, asks: bookEntry.asks },
|
|
331
|
+
price: newPrice,
|
|
332
|
+
midpoint: bookEntry.midpoint || '',
|
|
333
|
+
spread: bookEntry.spread || '',
|
|
334
|
+
};
|
|
335
|
+
await this.handlers.onPolymarketPriceUpdate?.([priceUpdateEvent]);
|
|
336
|
+
}
|
|
275
337
|
}
|
|
276
338
|
}
|
|
277
339
|
}
|
|
@@ -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
|
+
}
|