@nevuamarkets/poly-websockets 0.0.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/LICENSE +661 -0
- package/README.md +251 -0
- package/dist/WSSubscriptionManager.d.ts +18 -0
- package/dist/WSSubscriptionManager.js +174 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +21 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +34 -0
- package/dist/modules/GroupRegistry.d.ts +85 -0
- package/dist/modules/GroupRegistry.js +261 -0
- package/dist/modules/GroupSocket.d.ts +22 -0
- package/dist/modules/GroupSocket.js +311 -0
- package/dist/modules/OrderBookCache.d.ts +49 -0
- package/dist/modules/OrderBookCache.js +173 -0
- package/dist/types/PolymarketWebSocket.d.ts +242 -0
- package/dist/types/PolymarketWebSocket.js +50 -0
- package/dist/types/WebSocketSubscriptions.d.ts +19 -0
- package/dist/types/WebSocketSubscriptions.js +10 -0
- package/package.json +49 -0
- package/src/WSSubscriptionManager.ts +201 -0
- package/src/index.ts +3 -0
- package/src/logger.ts +37 -0
- package/src/modules/GroupRegistry.ts +274 -0
- package/src/modules/GroupSocket.ts +338 -0
- package/src/modules/OrderBookCache.ts +208 -0
- package/src/types/PolymarketWebSocket.ts +280 -0
- package/src/types/WebSocketSubscriptions.ts +26 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a single price level in the order book
|
|
3
|
+
* @example
|
|
4
|
+
* { price: "0.01", size: "510000" }
|
|
5
|
+
*/
|
|
6
|
+
export type PriceLevel = {
|
|
7
|
+
price: string;
|
|
8
|
+
size: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents a price_change event from Polymarket WebSocket
|
|
13
|
+
* @example
|
|
14
|
+
* {
|
|
15
|
+
* asset_id: "39327269875426915204597944387916069897800289788920336317845465327697809453999",
|
|
16
|
+
* changes: [
|
|
17
|
+
* { price: "0.044", side: "SELL", size: "611" }
|
|
18
|
+
* ],
|
|
19
|
+
* event_type: "price_change",
|
|
20
|
+
* hash: "a0b7cadf869fc288dbbf65704996fe818cc97d6a",
|
|
21
|
+
* market: "0x5412ae25e97078f814157de948459d59c6221b4c4c495fdd57b536543ad36729",
|
|
22
|
+
* timestamp: "1749371014925"
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
export type PriceChangeEvent = {
|
|
26
|
+
asset_id: string;
|
|
27
|
+
changes: { price: string; side: string; size: string }[];
|
|
28
|
+
event_type: 'price_change';
|
|
29
|
+
hash: string;
|
|
30
|
+
market: string;
|
|
31
|
+
timestamp: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Represents a Polymarket book
|
|
36
|
+
* @example
|
|
37
|
+
* {
|
|
38
|
+
* bids: [
|
|
39
|
+
* { price: "0.01", size: "510000" },
|
|
40
|
+
* { price: "0.02", size: "3100" }
|
|
41
|
+
* ],
|
|
42
|
+
* asks: [
|
|
43
|
+
* { price: "0.99", size: "58.07" },
|
|
44
|
+
* { price: "0.97", size: "178.73" }
|
|
45
|
+
* }
|
|
46
|
+
*/
|
|
47
|
+
export type Book = {
|
|
48
|
+
bids: PriceLevel[];
|
|
49
|
+
asks: PriceLevel[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Represents a book event from Polymarket WebSocket
|
|
54
|
+
* @example
|
|
55
|
+
* {
|
|
56
|
+
* market: "0xf83fb46dd70a4459fcc441a8511701c463374c5c3c250f585d74fda85ddfb7c9",
|
|
57
|
+
* asset_id: "101007741586870489619361069512452187353898396425142157315847015703471254508752",
|
|
58
|
+
* timestamp: "1740759191594",
|
|
59
|
+
* hash: "c0e51b1cfdbcb1b2aec58feaf7b01004019a89c6",
|
|
60
|
+
* bids: [
|
|
61
|
+
* { price: "0.01", size: "510000" },
|
|
62
|
+
* { price: "0.02", size: "3100" }
|
|
63
|
+
* ],
|
|
64
|
+
* asks: [
|
|
65
|
+
* { price: "0.99", size: "58.07" },
|
|
66
|
+
* { price: "0.97", size: "178.73" }
|
|
67
|
+
* ],
|
|
68
|
+
* event_type: "book"
|
|
69
|
+
* }
|
|
70
|
+
*/
|
|
71
|
+
export type BookEvent = {
|
|
72
|
+
market: string;
|
|
73
|
+
asset_id: string;
|
|
74
|
+
timestamp: string;
|
|
75
|
+
hash: string;
|
|
76
|
+
bids: PriceLevel[];
|
|
77
|
+
asks: PriceLevel[];
|
|
78
|
+
event_type: 'book';
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Represents a last trade price event from Polymarket WebSocket
|
|
83
|
+
* @example
|
|
84
|
+
* {
|
|
85
|
+
* asset_id: "101007741586870489619361069512452187353898396425142157315847015703471254508752",
|
|
86
|
+
* event_type: "last_trade_price",
|
|
87
|
+
* fee_rate_bps: "0",
|
|
88
|
+
* market: "0xf83fb46dd70a4459fcc441a8511701c463374c5c3c250f585d74fda85ddfb7c9",
|
|
89
|
+
* price: "0.12",
|
|
90
|
+
* side: "BUY",
|
|
91
|
+
* size: "8.333332",
|
|
92
|
+
* timestamp: "1740760245471"
|
|
93
|
+
* }
|
|
94
|
+
*/
|
|
95
|
+
export type LastTradePriceEvent = {
|
|
96
|
+
asset_id: string;
|
|
97
|
+
event_type: 'last_trade_price';
|
|
98
|
+
fee_rate_bps: string;
|
|
99
|
+
market: string;
|
|
100
|
+
price: string;
|
|
101
|
+
side: 'BUY' | 'SELL';
|
|
102
|
+
size: string;
|
|
103
|
+
timestamp: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Represents a tick size change event from Polymarket WebSocket
|
|
108
|
+
* @example
|
|
109
|
+
* {
|
|
110
|
+
* event_type: "tick_size_change",
|
|
111
|
+
* asset_id: "65818619657568813474341868652308942079804919287380422192892211131408793125422",
|
|
112
|
+
* market: "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af",
|
|
113
|
+
* old_tick_size: "0.01",
|
|
114
|
+
* new_tick_size: "0.001",
|
|
115
|
+
* timestamp: "100000000"
|
|
116
|
+
* }
|
|
117
|
+
*/
|
|
118
|
+
export type TickSizeChangeEvent = {
|
|
119
|
+
asset_id: string;
|
|
120
|
+
event_type: 'tick_size_change';
|
|
121
|
+
market: string;
|
|
122
|
+
old_tick_size: string;
|
|
123
|
+
new_tick_size: string;
|
|
124
|
+
timestamp: string;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Union type representing all possible event types from Polymarket WebSocket
|
|
129
|
+
* @example BookEvent
|
|
130
|
+
* {
|
|
131
|
+
* market: "0xf83fb46dd70a4459fcc441a8511701c463374c5c3c250f585d74fda85ddfb7c9",
|
|
132
|
+
* asset_id: "101007741586870489619361069512452187353898396425142157315847015703471254508752",
|
|
133
|
+
* timestamp: "1740759191594",
|
|
134
|
+
* hash: "c0e51b1cfdbcb1b2aec58feaf7b01004019a89c6",
|
|
135
|
+
* bids: [{ price: "0.01", size: "510000" }],
|
|
136
|
+
* asks: [{ price: "0.99", size: "58.07" }],
|
|
137
|
+
* event_type: "book"
|
|
138
|
+
* }
|
|
139
|
+
*
|
|
140
|
+
* @example LastTradePriceEvent
|
|
141
|
+
* {
|
|
142
|
+
* asset_id: "101007741586870489619361069512452187353898396425142157315847015703471254508752",
|
|
143
|
+
* event_type: "last_trade_price",
|
|
144
|
+
* fee_rate_bps: "0",
|
|
145
|
+
* market: "0xf83fb46dd70a4459fcc441a8511701c463374c5c3c250f585d74fda85ddfb7c9",
|
|
146
|
+
* price: "0.12",
|
|
147
|
+
* side: "BUY",
|
|
148
|
+
* size: "8.333332",
|
|
149
|
+
* timestamp: "1740760245471"
|
|
150
|
+
* }
|
|
151
|
+
*
|
|
152
|
+
* @example PriceChangeEvent
|
|
153
|
+
* {
|
|
154
|
+
* asset_id: "39327269875426915204597944387916069897800289788920336317845465327697809453999",
|
|
155
|
+
* changes: [
|
|
156
|
+
* { price: "0.044", side: "SELL", size: "611" }
|
|
157
|
+
* ],
|
|
158
|
+
* event_type: "price_change",
|
|
159
|
+
* hash: "a0b7cadf869fc288dbbf65704996fe818cc97d6a",
|
|
160
|
+
* market: "0x5412ae25e97078f814157de948459d59c6221b4c4c495fdd57b536543ad36729",
|
|
161
|
+
* timestamp: "1749371014925"
|
|
162
|
+
* }
|
|
163
|
+
*
|
|
164
|
+
* @example TickSizeChangeEvent
|
|
165
|
+
* {
|
|
166
|
+
* event_type: "tick_size_change",
|
|
167
|
+
* asset_id: "65818619657568813474341868652308942079804919287380422192892211131408793125422",
|
|
168
|
+
* market: "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af",
|
|
169
|
+
* old_tick_size: "0.01",
|
|
170
|
+
* new_tick_size: "0.001",
|
|
171
|
+
* timestamp: "100000000"
|
|
172
|
+
* }
|
|
173
|
+
*/
|
|
174
|
+
export type PolymarketWSEvent = BookEvent | LastTradePriceEvent | PriceChangeEvent | TickSizeChangeEvent;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Represents a price update event
|
|
178
|
+
*
|
|
179
|
+
* This is an event that is emitted to faciliate price update events. It is
|
|
180
|
+
* not emitted by the Polymarket WebSocket directly.
|
|
181
|
+
*
|
|
182
|
+
* See https://docs.polymarket.com/polymarket-learn/trading/how-are-prices-calculated
|
|
183
|
+
*
|
|
184
|
+
* TLDR: The prices displayed on Polymarket are the midpoint of the bid-ask spread in the orderbook,
|
|
185
|
+
* UNLESS that spread is over $0.10, in which case the **last traded price** is used.
|
|
186
|
+
*/
|
|
187
|
+
export interface PolymarketPriceUpdateEvent {
|
|
188
|
+
event_type: 'price_update';
|
|
189
|
+
asset_id: string;
|
|
190
|
+
timestamp: string;
|
|
191
|
+
triggeringEvent: LastTradePriceEvent | PriceChangeEvent;
|
|
192
|
+
book: Book;
|
|
193
|
+
price: string;
|
|
194
|
+
midpoint: string;
|
|
195
|
+
spread: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Represents the handlers for the Polymarket WebSocket
|
|
200
|
+
*/
|
|
201
|
+
export type WebSocketHandlers = {
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
Polymarket WebSocket event handlers
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
// https://docs.polymarket.com/developers/CLOB/websocket/market-channel#book-message
|
|
208
|
+
onBook?: (events: BookEvent[]) => Promise<void>;
|
|
209
|
+
|
|
210
|
+
// Currently undocumented, but is emitted when a trade occurs
|
|
211
|
+
onLastTradePrice?: (events: LastTradePriceEvent[]) => Promise<void>;
|
|
212
|
+
|
|
213
|
+
// https://docs.polymarket.com/developers/CLOB/websocket/market-channel#tick-size-change-message
|
|
214
|
+
onTickSizeChange?: (events: TickSizeChangeEvent[]) => Promise<void>;
|
|
215
|
+
|
|
216
|
+
// https://docs.polymarket.com/developers/CLOB/websocket/market-channel#price-change-message
|
|
217
|
+
onPriceChange?: (events: PriceChangeEvent[]) => Promise<void>;
|
|
218
|
+
|
|
219
|
+
/*
|
|
220
|
+
Also mentioned as 'Future Price', this is the price that is displayed on the Polymarket UI
|
|
221
|
+
and denotes the probability of an event happening. Read more about it here:
|
|
222
|
+
https://docs.polymarket.com/polymarket-learn/trading/how-are-prices-calculated#future-price
|
|
223
|
+
|
|
224
|
+
This is an aggregate event that is not emmited by the Polymarket WebSocket directly.
|
|
225
|
+
*/
|
|
226
|
+
onPolymarketPriceUpdate?: (events: PolymarketPriceUpdateEvent[]) => Promise<void>;
|
|
227
|
+
|
|
228
|
+
// Error handling
|
|
229
|
+
onError?: (error: Error) => Promise<void>;
|
|
230
|
+
onWSClose?: (groupId: string, code: number, reason: string) => Promise<void>;
|
|
231
|
+
onWSOpen?: (groupId: string, assetIds: string[]) => Promise<void>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Type guard to check if an event is a BookEvent
|
|
236
|
+
* @example
|
|
237
|
+
* if (isBookEvent(event)) {
|
|
238
|
+
* // event is now typed as BookEvent
|
|
239
|
+
* console.log(event.bids);
|
|
240
|
+
* }
|
|
241
|
+
*/
|
|
242
|
+
export function isBookEvent(event: PolymarketWSEvent): event is BookEvent {
|
|
243
|
+
return event?.event_type === 'book';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Type guard to check if an event is a LastTradePriceEvent
|
|
248
|
+
* @example
|
|
249
|
+
* if (isLastTradePriceEvent(event)) {
|
|
250
|
+
* // event is now typed as LastTradePriceEvent
|
|
251
|
+
* console.log(event.side);
|
|
252
|
+
* }
|
|
253
|
+
*/
|
|
254
|
+
export function isLastTradePriceEvent(event: PolymarketWSEvent): event is LastTradePriceEvent {
|
|
255
|
+
return event?.event_type === 'last_trade_price';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Type guard to check if an event is a PriceChangeEvent
|
|
260
|
+
* @example
|
|
261
|
+
* if (isPriceChangeEvent(event)) {
|
|
262
|
+
* // event is now typed as PriceChangeEvent
|
|
263
|
+
* console.log(event.changes);
|
|
264
|
+
* }
|
|
265
|
+
*/
|
|
266
|
+
export function isPriceChangeEvent(event: PolymarketWSEvent): event is PriceChangeEvent {
|
|
267
|
+
return event?.event_type === 'price_change';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Type guard to check if an event is a TickSizeChangeEvent
|
|
272
|
+
* @example
|
|
273
|
+
* if (isTickSizeChangeEvent(event)) {
|
|
274
|
+
* // event is now typed as TickSizeChangeEvent
|
|
275
|
+
* console.log(event.old_tick_size);
|
|
276
|
+
* }
|
|
277
|
+
*/
|
|
278
|
+
export function isTickSizeChangeEvent(event: PolymarketWSEvent): event is TickSizeChangeEvent {
|
|
279
|
+
return event?.event_type === 'tick_size_change';
|
|
280
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Bottleneck from 'bottleneck';
|
|
2
|
+
import WebSocket from 'ws';
|
|
3
|
+
|
|
4
|
+
export enum WebSocketStatus {
|
|
5
|
+
PENDING = 'pending', // New group that is pending connection
|
|
6
|
+
ALIVE = 'alive', // Group is connected and receiving events
|
|
7
|
+
DEAD = 'dead', // Group is disconnected
|
|
8
|
+
CLEANUP = 'cleanup' // Group is marked for cleanup
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type WebSocketGroup = {
|
|
12
|
+
groupId: string;
|
|
13
|
+
assetIds: Set<string>;
|
|
14
|
+
wsClient: WebSocket | null;
|
|
15
|
+
status: WebSocketStatus;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type SubscriptionManagerOptions = {
|
|
19
|
+
burstLimiter?: Bottleneck;
|
|
20
|
+
|
|
21
|
+
// How often to check for groups to reconnect and cleanup
|
|
22
|
+
reconnectAndCleanupIntervalMs?: number;
|
|
23
|
+
|
|
24
|
+
// How many assets to allow per WebSocket
|
|
25
|
+
maxMarketsPerWS?: number;
|
|
26
|
+
}
|