Skip to content

Commit 714aa97

Browse files
authored
Polymarket integration (#66)
* feat(polymarket-integration): added some types * feat(polymarket-integration): added more types * feat(polymarket-integration): added more types * feat(polymarket-integration): added PolymarketBookChangeMapper mapper * feat(polymarket-integration): added PolymarketTradesMapper mapper * feat(polymarket-integration): registered mappers * feat(polymarket-integration): added next channel * feat(polymarket-integration): added PolymarketBookTickerMapper mapper * feat(polymarket-integration): added real feed streaming * feat(polymarket-integration): improved naming * feat(polymarket-integration): fixed prettier formatting * feat(polymarket-integration): migration after back-merge * feat(polymarket-integration): added new_market and market_resolved channels * feat(polymarket-integration): improved condition orders * feat(polymarket-integration): cleanup * feat(polymarket-integration): SIMPLIFIED * feat(polymarket-integration): cleanup * feat(polymarket-integration): cleanup * feat(polymarket-integration): optimizations * feat(polymarket-integration): forget about tests
1 parent 9771699 commit 714aa97

10 files changed

Lines changed: 1017 additions & 7 deletions

File tree

src/consts.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ export const EXCHANGES = [
5757
'bitget-futures',
5858
'hyperliquid',
5959
'lighter',
60-
'bullish'
60+
'bullish',
61+
'polymarket'
6162
] as const
6263

6364
const BINANCE_CHANNELS = ['trade', 'aggTrade', 'ticker', 'depth', 'depthSnapshot', 'bookTicker', 'recentTrades', 'borrowInterest'] as const
@@ -525,6 +526,17 @@ const LIGHTER_CHANNELS = ['order_book', 'trade', 'ticker', 'market_stats', 'spot
525526

526527
const BULLISH_CHANNELS = ['V1TALevel2', 'V1TALevel1', 'V1TAAnonymousTradeUpdate', 'V1TATickerResponse', 'V1TAIndexPrice'] as const
527528

529+
const POLYMARKET_CHANNELS = [
530+
'book',
531+
'price_change',
532+
'last_trade_price',
533+
'best_bid_ask',
534+
'tick_size_change',
535+
'sport_result',
536+
'new_market',
537+
'market_resolved'
538+
] as const
539+
528540
export const EXCHANGE_CHANNELS_INFO = {
529541
bitmex: BITMEX_CHANNELS,
530542
coinbase: COINBASE_CHANNELS,
@@ -584,5 +596,6 @@ export const EXCHANGE_CHANNELS_INFO = {
584596
'bitget-futures': BITGET_FUTURES_CHANNELS,
585597
hyperliquid: HYPERLIQUID_CHANNELS,
586598
lighter: LIGHTER_CHANNELS,
587-
bullish: BULLISH_CHANNELS
599+
bullish: BULLISH_CHANNELS,
600+
polymarket: POLYMARKET_CHANNELS
588601
}

src/mappers/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ import { PoloniexBookChangeMapper, PoloniexTradesMapper, PoloniexV2BookChangeMap
169169
import { SerumBookChangeMapper, SerumBookTickerMapper, SerumTradesMapper } from './serum.ts'
170170
import { UpbitBookChangeMapper, UpbitTradesMapper } from './upbit.ts'
171171
import { WooxBookChangeMapper, WooxBookTickerMapper, WooxDerivativeTickerMapper, wooxTradesMapper } from './woox.ts'
172+
import { PolymarketBookChangeMapper, PolymarketBookTickerMapper, PolymarketTradesMapper } from './polymarket.ts'
172173

173174
export * from './mapper.ts'
174175

@@ -352,7 +353,8 @@ const tradesMappers = {
352353
'coinbase-international': () => coinbaseInternationalTradesMapper,
353354
hyperliquid: () => new HyperliquidTradesMapper(),
354355
lighter: () => new LighterTradesMapper(),
355-
bullish: () => new BullishTradesMapper()
356+
bullish: () => new BullishTradesMapper(),
357+
polymarket: () => new PolymarketTradesMapper()
356358
}
357359

358360
const bookChangeMappers = {
@@ -453,7 +455,8 @@ const bookChangeMappers = {
453455
'coinbase-international': () => new CoinbaseInternationalBookChangMapper(),
454456
hyperliquid: () => new HyperliquidBookChangeMapper(),
455457
lighter: () => new LighterBookChangeMapper(),
456-
bullish: () => new BullishBookChangeMapper()
458+
bullish: () => new BullishBookChangeMapper(),
459+
polymarket: () => new PolymarketBookChangeMapper()
457460
}
458461

459462
const derivativeTickersMappers = {
@@ -603,7 +606,8 @@ const bookTickersMappers = {
603606
hyperliquid: () => new HyperliquidBookTickerMapper(),
604607
lighter: () => new LighterBookTickerMapper(),
605608
bullish: () => new BullishBookTickerMapper(),
606-
'binance-european-options': () => new BinanceEuropeanOptionsBookTickerMapper()
609+
'binance-european-options': () => new BinanceEuropeanOptionsBookTickerMapper(),
610+
polymarket: () => new PolymarketBookTickerMapper()
607611
}
608612

609613
export const normalizeTrades = <T extends keyof typeof tradesMappers>(exchange: T, localTimestamp: Date): Mapper<T, Trade> => {

src/mappers/polymarket.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

src/realtimefeeds/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { CoinbaseInternationalRealTimeFeed } from './coinbaseinternational.ts'
5454
import { HyperliquidRealTimeFeed } from './hyperliquid.ts'
5555
import { LighterRealTimeFeed } from './lighter.ts'
5656
import { BullishRealTimeFeed } from './bullish.ts'
57+
import { PolymarketRealTimeFeed } from './polymarket.ts'
5758

5859
export * from './realtimefeed.ts'
5960

@@ -118,7 +119,8 @@ const realTimeFeedsMap: {
118119
'coinbase-international': CoinbaseInternationalRealTimeFeed,
119120
hyperliquid: HyperliquidRealTimeFeed,
120121
lighter: LighterRealTimeFeed,
121-
bullish: BullishRealTimeFeed
122+
bullish: BullishRealTimeFeed,
123+
polymarket: PolymarketRealTimeFeed
122124
}
123125

124126
export function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {

0 commit comments

Comments
 (0)