tardis-dev 16.4.2 → 16.5.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/consts.d.ts +2 -1
- package/dist/consts.d.ts.map +1 -1
- package/dist/consts.js +14 -2
- package/dist/consts.js.map +1 -1
- package/dist/mappers/bybit.d.ts +2 -2
- package/dist/mappers/bybitspot.d.ts +1 -1
- package/dist/mappers/cryptocom.d.ts +1 -1
- package/dist/mappers/huobi.d.ts +3 -3
- package/dist/mappers/index.d.ts +4 -0
- package/dist/mappers/index.d.ts.map +1 -1
- package/dist/mappers/index.js +7 -3
- package/dist/mappers/index.js.map +1 -1
- package/dist/mappers/polymarket.d.ts +83 -0
- package/dist/mappers/polymarket.d.ts.map +1 -0
- package/dist/mappers/polymarket.js +113 -0
- package/dist/mappers/polymarket.js.map +1 -0
- package/dist/realtimefeeds/index.d.ts.map +1 -1
- package/dist/realtimefeeds/index.js +3 -1
- package/dist/realtimefeeds/index.js.map +1 -1
- package/dist/realtimefeeds/polymarket.d.ts +26 -0
- package/dist/realtimefeeds/polymarket.d.ts.map +1 -0
- package/dist/realtimefeeds/polymarket.js +92 -0
- package/dist/realtimefeeds/polymarket.js.map +1 -0
- package/dist/realtimefeeds/realtimefeed.d.ts +1 -0
- package/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
- package/dist/realtimefeeds/realtimefeed.js +9 -0
- package/dist/realtimefeeds/realtimefeed.js.map +1 -1
- package/package.json +1 -1
- package/src/consts.ts +15 -2
- package/src/mappers/index.ts +7 -3
- package/src/mappers/polymarket.ts +195 -0
- package/src/realtimefeeds/index.ts +3 -1
- package/src/realtimefeeds/polymarket.ts +112 -0
- package/src/realtimefeeds/realtimefeed.ts +10 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { BookChange, BookTicker, Trade } from '../types.ts'
|
|
2
|
+
import { asNonZeroNumberOrUndefined } from '../handy.ts'
|
|
3
|
+
import { Mapper } from './mapper.ts'
|
|
4
|
+
|
|
5
|
+
type PolymarketBookChangeMapperMessage = PolymarketClobBookMessage | PolymarketClobBookMessage[] | PolymarketClobPriceChangeMessage
|
|
6
|
+
export class PolymarketBookChangeMapper implements Mapper<'polymarket', BookChange> {
|
|
7
|
+
canHandle(message: PolymarketNativeMessage): message is PolymarketBookChangeMapperMessage {
|
|
8
|
+
if (Array.isArray(message)) {
|
|
9
|
+
return message.length > 0 && message.every(isPolymarketClobBookMessage)
|
|
10
|
+
}
|
|
11
|
+
return isPolymarketClobPriceChangeMessage(message) || isPolymarketClobBookMessage(message)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getFilters(symbols?: string[]) {
|
|
15
|
+
return [
|
|
16
|
+
{ channel: 'book' as const, symbols },
|
|
17
|
+
{ channel: 'price_change' as const, symbols }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
*map(message: PolymarketNativeMessage, localTimestamp: Date): IterableIterator<BookChange> {
|
|
22
|
+
if (Array.isArray(message)) {
|
|
23
|
+
for (const bookMsg of message) {
|
|
24
|
+
yield this.mapBookSnapshot(bookMsg, localTimestamp)
|
|
25
|
+
}
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (isPolymarketClobPriceChangeMessage(message)) {
|
|
30
|
+
const timestamp = new Date(Number(message.timestamp))
|
|
31
|
+
const changes = message.price_changes
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < changes.length; i++) {
|
|
34
|
+
const change = changes[i]
|
|
35
|
+
const level = this.mapLevel(change)
|
|
36
|
+
|
|
37
|
+
yield {
|
|
38
|
+
type: 'book_change',
|
|
39
|
+
symbol: change.asset_id,
|
|
40
|
+
exchange: 'polymarket',
|
|
41
|
+
isSnapshot: false,
|
|
42
|
+
bids: change.side === 'BUY' ? [level] : [],
|
|
43
|
+
asks: change.side === 'SELL' ? [level] : [],
|
|
44
|
+
timestamp,
|
|
45
|
+
localTimestamp
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isPolymarketClobBookMessage(message)) {
|
|
53
|
+
yield this.mapBookSnapshot(message, localTimestamp)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private mapBookSnapshot(message: PolymarketClobBookMessage, localTimestamp: Date): BookChange {
|
|
59
|
+
return {
|
|
60
|
+
type: 'book_change',
|
|
61
|
+
symbol: message.asset_id,
|
|
62
|
+
exchange: 'polymarket',
|
|
63
|
+
isSnapshot: true,
|
|
64
|
+
bids: message.bids.map(this.mapLevel.bind(this)),
|
|
65
|
+
asks: message.asks.map(this.mapLevel.bind(this)),
|
|
66
|
+
timestamp: new Date(Number(message.timestamp)),
|
|
67
|
+
localTimestamp
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private mapLevel(level: Pick<PolymarketClobBookLevel, 'price' | 'size'>) {
|
|
72
|
+
return {
|
|
73
|
+
price: Number(level.price),
|
|
74
|
+
amount: Number(level.size)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class PolymarketTradesMapper implements Mapper<'polymarket', Trade> {
|
|
80
|
+
canHandle(message: any): message is PolymarketClobLastTradePriceMessage {
|
|
81
|
+
return message.event_type === 'last_trade_price'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getFilters(symbols?: string[]) {
|
|
85
|
+
return [{ channel: 'last_trade_price' as const, symbols }]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
*map(message: PolymarketClobLastTradePriceMessage, localTimestamp: Date): IterableIterator<Trade> {
|
|
89
|
+
yield {
|
|
90
|
+
type: 'trade',
|
|
91
|
+
symbol: message.asset_id,
|
|
92
|
+
exchange: 'polymarket',
|
|
93
|
+
id: message.transaction_hash,
|
|
94
|
+
price: Number(message.price),
|
|
95
|
+
amount: Number(message.size),
|
|
96
|
+
side: message.side.toLowerCase() as Lowercase<PolymarketClobTradeSide>,
|
|
97
|
+
timestamp: new Date(Number(message.timestamp)),
|
|
98
|
+
localTimestamp
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class PolymarketBookTickerMapper implements Mapper<'polymarket', BookTicker> {
|
|
104
|
+
canHandle(message: any): message is PolymarketClobBestBidAskMessage {
|
|
105
|
+
return message.event_type === 'best_bid_ask'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getFilters(symbols?: string[]) {
|
|
109
|
+
return [{ channel: 'best_bid_ask' as const, symbols }]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
*map(message: PolymarketClobBestBidAskMessage, localTimestamp: Date): IterableIterator<BookTicker> {
|
|
113
|
+
yield {
|
|
114
|
+
type: 'book_ticker',
|
|
115
|
+
symbol: message.asset_id,
|
|
116
|
+
exchange: 'polymarket',
|
|
117
|
+
bidPrice: asNonZeroNumberOrUndefined(message.best_bid),
|
|
118
|
+
bidAmount: undefined,
|
|
119
|
+
askPrice: asNonZeroNumberOrUndefined(message.best_ask),
|
|
120
|
+
askAmount: undefined,
|
|
121
|
+
timestamp: new Date(Number(message.timestamp)),
|
|
122
|
+
localTimestamp
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type PolymarketNativeMessage =
|
|
128
|
+
| PolymarketClobBookMessage
|
|
129
|
+
| PolymarketClobBookMessage[]
|
|
130
|
+
| PolymarketClobPriceChangeMessage
|
|
131
|
+
| PolymarketClobLastTradePriceMessage
|
|
132
|
+
| PolymarketClobBestBidAskMessage
|
|
133
|
+
|
|
134
|
+
type PolymarketClobEventType = 'book' | 'price_change' | 'last_trade_price' | 'best_bid_ask'
|
|
135
|
+
|
|
136
|
+
type PolymarketClobMessage<T extends PolymarketClobEventType = PolymarketClobEventType> = {
|
|
137
|
+
event_type: T
|
|
138
|
+
market: string
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isPolymarketClobBookMessage(message: any): message is PolymarketClobBookMessage {
|
|
142
|
+
return message?.event_type === 'book'
|
|
143
|
+
}
|
|
144
|
+
type PolymarketClobBookMessage = PolymarketClobMessage<'book'> & {
|
|
145
|
+
asset_id: string
|
|
146
|
+
timestamp: string
|
|
147
|
+
hash: string
|
|
148
|
+
bids: PolymarketClobBookLevel[]
|
|
149
|
+
asks: PolymarketClobBookLevel[]
|
|
150
|
+
tick_size?: string
|
|
151
|
+
last_trade_price?: string
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type PolymarketClobBookLevel = {
|
|
155
|
+
price: string
|
|
156
|
+
size: string
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isPolymarketClobPriceChangeMessage(message: any): message is PolymarketClobPriceChangeMessage {
|
|
160
|
+
return message?.event_type === 'price_change'
|
|
161
|
+
}
|
|
162
|
+
type PolymarketClobPriceChangeMessage = PolymarketClobMessage<'price_change'> & {
|
|
163
|
+
timestamp: string
|
|
164
|
+
price_changes: PolymarketClobPriceChange[]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
type PolymarketClobPriceChange = {
|
|
168
|
+
asset_id: string
|
|
169
|
+
price: string
|
|
170
|
+
size: string
|
|
171
|
+
side: PolymarketClobTradeSide
|
|
172
|
+
hash: string
|
|
173
|
+
best_bid: string
|
|
174
|
+
best_ask: string
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
type PolymarketClobLastTradePriceMessage = PolymarketClobMessage<'last_trade_price'> & {
|
|
178
|
+
asset_id: string
|
|
179
|
+
fee_rate_bps: string
|
|
180
|
+
price: string
|
|
181
|
+
side: PolymarketClobTradeSide
|
|
182
|
+
size: string
|
|
183
|
+
timestamp: string
|
|
184
|
+
transaction_hash: string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
type PolymarketClobTradeSide = 'BUY' | 'SELL'
|
|
188
|
+
|
|
189
|
+
type PolymarketClobBestBidAskMessage = PolymarketClobMessage<'best_bid_ask'> & {
|
|
190
|
+
asset_id: string
|
|
191
|
+
best_bid: string
|
|
192
|
+
best_ask: string
|
|
193
|
+
spread: string
|
|
194
|
+
timestamp: string
|
|
195
|
+
}
|
|
@@ -54,6 +54,7 @@ import { CoinbaseInternationalRealTimeFeed } from './coinbaseinternational.ts'
|
|
|
54
54
|
import { HyperliquidRealTimeFeed } from './hyperliquid.ts'
|
|
55
55
|
import { LighterRealTimeFeed } from './lighter.ts'
|
|
56
56
|
import { BullishRealTimeFeed } from './bullish.ts'
|
|
57
|
+
import { PolymarketRealTimeFeed } from './polymarket.ts'
|
|
57
58
|
|
|
58
59
|
export * from './realtimefeed.ts'
|
|
59
60
|
|
|
@@ -118,7 +119,8 @@ const realTimeFeedsMap: {
|
|
|
118
119
|
'coinbase-international': CoinbaseInternationalRealTimeFeed,
|
|
119
120
|
hyperliquid: HyperliquidRealTimeFeed,
|
|
120
121
|
lighter: LighterRealTimeFeed,
|
|
121
|
-
bullish: BullishRealTimeFeed
|
|
122
|
+
bullish: BullishRealTimeFeed,
|
|
123
|
+
polymarket: PolymarketRealTimeFeed
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
export function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Filter } from '../types.ts'
|
|
2
|
+
import { MultiConnectionRealTimeFeedBase, RealTimeFeedBase, RealTimeFeedIterable } from './realtimefeed.ts'
|
|
3
|
+
|
|
4
|
+
export class PolymarketRealTimeFeed extends MultiConnectionRealTimeFeedBase {
|
|
5
|
+
private readonly clobChannels = new Set([
|
|
6
|
+
'book',
|
|
7
|
+
'price_change',
|
|
8
|
+
'last_trade_price',
|
|
9
|
+
'best_bid_ask',
|
|
10
|
+
'tick_size_change',
|
|
11
|
+
'new_market',
|
|
12
|
+
'market_resolved'
|
|
13
|
+
])
|
|
14
|
+
private readonly sportsChannel = 'sport_result'
|
|
15
|
+
|
|
16
|
+
protected *_getRealTimeFeeds(
|
|
17
|
+
exchange: string,
|
|
18
|
+
filters: Filter<string>[],
|
|
19
|
+
timeoutIntervalMS?: number,
|
|
20
|
+
onError?: (error: Error) => void
|
|
21
|
+
): IterableIterator<RealTimeFeedIterable> {
|
|
22
|
+
const clobFilters: Filter<string>[] = []
|
|
23
|
+
const sportsFilters: Filter<string>[] = []
|
|
24
|
+
|
|
25
|
+
for (const filter of filters) {
|
|
26
|
+
if (this.clobChannels.has(filter.channel)) {
|
|
27
|
+
clobFilters.push(filter)
|
|
28
|
+
} else if (filter.channel === this.sportsChannel) {
|
|
29
|
+
sportsFilters.push(filter)
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error(`PolymarketRealTimeFeed unsupported channel ${filter.channel}`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (clobFilters.length > 0) {
|
|
36
|
+
if (clobFilters.every((filter) => filter.symbols === undefined || filter.symbols.length === 0)) {
|
|
37
|
+
throw new Error('PolymarketRealTimeFeed requires explicitly specified symbols when subscribing to CLOB live feed')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
yield new PolymarketClobRealTimeFeed(exchange, clobFilters, timeoutIntervalMS, onError)
|
|
41
|
+
}
|
|
42
|
+
if (sportsFilters.length > 0) {
|
|
43
|
+
yield new PolymarketSportsRealTimeFeed(exchange, sportsFilters, timeoutIntervalMS, onError)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class PolymarketClobRealTimeFeed extends RealTimeFeedBase {
|
|
49
|
+
protected wssURL = 'wss://ws-subscriptions-clob.polymarket.com/ws/market'
|
|
50
|
+
private readonly pongMessage = Buffer.from('{"__pong__":true}')
|
|
51
|
+
|
|
52
|
+
protected decompress = (msg: Buffer): Buffer => {
|
|
53
|
+
if (msg.toString() === 'PONG') {
|
|
54
|
+
return this.pongMessage
|
|
55
|
+
}
|
|
56
|
+
return msg
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected messageIsHeartbeat(msg: any) {
|
|
60
|
+
return msg.__pong__ === true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected sendCustomPing = () => {
|
|
64
|
+
this.sendRaw('PING')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
68
|
+
return [
|
|
69
|
+
{
|
|
70
|
+
type: 'market',
|
|
71
|
+
assets_ids: [...new Set(filters.flatMap((f) => f.symbols ?? []))],
|
|
72
|
+
initial_dump: true,
|
|
73
|
+
level: 2,
|
|
74
|
+
custom_feature_enabled: true
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected messageIsError(message: any): boolean {
|
|
80
|
+
return typeof message.error === 'string'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class PolymarketSportsRealTimeFeed extends RealTimeFeedBase {
|
|
85
|
+
protected wssURL = 'wss://sports-api.polymarket.com/ws'
|
|
86
|
+
private readonly serverPingMessage = Buffer.from('{"__server_ping__":true}')
|
|
87
|
+
|
|
88
|
+
protected decompress = (msg: Buffer): Buffer => {
|
|
89
|
+
if (msg.toString() === 'ping') {
|
|
90
|
+
return this.serverPingMessage
|
|
91
|
+
}
|
|
92
|
+
return msg
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected messageIsHeartbeat(msg: any) {
|
|
96
|
+
return msg.__server_ping__ === true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected onMessage(msg: any) {
|
|
100
|
+
if (msg.__server_ping__ === true) {
|
|
101
|
+
this.sendRaw('pong')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected mapToSubscribeMessages(_filters: Filter<string>[]): any[] {
|
|
106
|
+
return []
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
protected messageIsError(message: any): boolean {
|
|
110
|
+
return typeof message.error === 'string'
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -219,6 +219,16 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
|
|
|
219
219
|
this._ws.send(JSON.stringify(msg))
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
protected sendRaw(msg: string | Buffer) {
|
|
223
|
+
if (this._ws === undefined) {
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
if (this._ws.readyState !== WebSocket.OPEN) {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
this._ws.send(msg)
|
|
230
|
+
}
|
|
231
|
+
|
|
222
232
|
protected abstract mapToSubscribeMessages(filters: Filter<string>[]): any[]
|
|
223
233
|
|
|
224
234
|
protected abstract messageIsError(message: any): boolean
|