tardis-dev 16.3.1 → 16.4.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 +5 -2
- package/dist/consts.js.map +1 -1
- package/dist/handy.d.ts +9 -0
- package/dist/handy.d.ts.map +1 -1
- package/dist/handy.js +15 -0
- package/dist/handy.js.map +1 -1
- package/dist/mappers/bullish.d.ts +161 -0
- package/dist/mappers/bullish.d.ts.map +1 -0
- package/dist/mappers/bullish.js +218 -0
- package/dist/mappers/bullish.js.map +1 -0
- 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 +6 -0
- package/dist/mappers/index.d.ts.map +1 -1
- package/dist/mappers/index.js +10 -4
- package/dist/mappers/index.js.map +1 -1
- package/dist/realtimefeeds/bullish.d.ts +18 -0
- package/dist/realtimefeeds/bullish.d.ts.map +1 -0
- package/dist/realtimefeeds/bullish.js +113 -0
- package/dist/realtimefeeds/bullish.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/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +52 -43
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/src/consts.ts +6 -2
- package/src/handy.ts +17 -0
- package/src/mappers/bullish.ts +374 -0
- package/src/mappers/index.ts +16 -4
- package/src/realtimefeeds/bullish.ts +140 -0
- package/src/realtimefeeds/index.ts +3 -1
- package/src/types.ts +2 -1
- package/src/worker.ts +82 -54
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { asNumberIfValid, asNumberOrUndefined } from '../handy.ts'
|
|
2
|
+
import { BookChange, BookPriceLevel, BookTicker, DerivativeTicker, OptionSummary, Trade } from '../types.ts'
|
|
3
|
+
import { Mapper, PendingTickerInfoHelper } from './mapper.ts'
|
|
4
|
+
|
|
5
|
+
export class BullishTradesMapper implements Mapper<'bullish', Trade> {
|
|
6
|
+
canHandle(message: BullishMessage): message is BullishAnonymousTradeUpdateMessage {
|
|
7
|
+
return message.dataType === 'V1TAAnonymousTradeUpdate' && message.type === 'update'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
getFilters(symbols?: string[]) {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
channel: 'V1TAAnonymousTradeUpdate' as const,
|
|
14
|
+
symbols
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
*map(message: BullishAnonymousTradeUpdateMessage, localTimestamp: Date): IterableIterator<Trade> {
|
|
20
|
+
for (const trade of message.data.trades) {
|
|
21
|
+
yield {
|
|
22
|
+
type: 'trade',
|
|
23
|
+
symbol: trade.symbol,
|
|
24
|
+
exchange: 'bullish',
|
|
25
|
+
id: trade.tradeId,
|
|
26
|
+
price: Number(trade.price),
|
|
27
|
+
amount: Number(trade.quantity),
|
|
28
|
+
side: this.mapBullishTradeSide(trade.side, trade.isTaker),
|
|
29
|
+
timestamp: new Date(trade.createdAtDatetime),
|
|
30
|
+
localTimestamp
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private mapBullishTradeSide(side: BullishTradeSide, isTaker: boolean): 'buy' | 'sell' {
|
|
36
|
+
if (isTaker) {
|
|
37
|
+
// Bullish side already represents the taker/aggressor side.
|
|
38
|
+
return side === 'BUY' ? 'buy' : 'sell'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Bullish side represents the maker/resting order, so the taker was on the opposite side.
|
|
42
|
+
return side === 'BUY' ? 'sell' : 'buy'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class BullishBookChangeMapper implements Mapper<'bullish', BookChange> {
|
|
47
|
+
canHandle(message: BullishMessage): message is BullishLevel2Message {
|
|
48
|
+
return message.dataType === 'V1TALevel2'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getFilters(symbols?: string[]) {
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
channel: 'V1TALevel2' as const,
|
|
55
|
+
symbols
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
*map(message: BullishLevel2Message, localTimestamp: Date): IterableIterator<BookChange> {
|
|
61
|
+
yield {
|
|
62
|
+
type: 'book_change',
|
|
63
|
+
symbol: message.data.symbol,
|
|
64
|
+
exchange: 'bullish',
|
|
65
|
+
isSnapshot: message.type === 'snapshot',
|
|
66
|
+
bids: this.mapLevels(message.data.bids),
|
|
67
|
+
asks: this.mapLevels(message.data.asks),
|
|
68
|
+
timestamp: new Date(message.data.datetime),
|
|
69
|
+
localTimestamp
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private mapLevels(levels: string[]): BookPriceLevel[] {
|
|
74
|
+
const result = new Array<BookPriceLevel>(levels.length / 2)
|
|
75
|
+
for (let index = 0, resultIndex = 0; index < levels.length; index += 2, resultIndex++) {
|
|
76
|
+
result[resultIndex] = {
|
|
77
|
+
price: Number(levels[index]),
|
|
78
|
+
amount: Number(levels[index + 1])
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class BullishBookTickerMapper implements Mapper<'bullish', BookTicker> {
|
|
87
|
+
canHandle(message: BullishMessage): message is BullishLevel1Message {
|
|
88
|
+
return message.dataType === 'V1TALevel1' && (message.type === 'snapshot' || message.type === 'update')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getFilters(symbols?: string[]) {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
channel: 'V1TALevel1' as const,
|
|
95
|
+
symbols
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
*map(message: BullishLevel1Message, localTimestamp: Date): IterableIterator<BookTicker> {
|
|
101
|
+
yield {
|
|
102
|
+
type: 'book_ticker',
|
|
103
|
+
symbol: message.data.symbol,
|
|
104
|
+
exchange: 'bullish',
|
|
105
|
+
bidPrice: asNumberIfValid(message.data.bid[0]),
|
|
106
|
+
bidAmount: asNumberIfValid(message.data.bid[1]),
|
|
107
|
+
askPrice: asNumberIfValid(message.data.ask[0]),
|
|
108
|
+
askAmount: asNumberIfValid(message.data.ask[1]),
|
|
109
|
+
timestamp: new Date(message.data.datetime),
|
|
110
|
+
localTimestamp
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class BullishDerivativeTickerMapper implements Mapper<'bullish', DerivativeTicker> {
|
|
116
|
+
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
|
|
117
|
+
private readonly indexPrices = new Map<string, { price: number; timestamp: Date }>()
|
|
118
|
+
|
|
119
|
+
canHandle(message: BullishMessage): message is BullishDerivativeTickerMessage | BullishIndexPriceMessage {
|
|
120
|
+
if (message.dataType === 'V1TAIndexPrice' && (message.type === 'snapshot' || message.type === 'update')) {
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (message.dataType === 'V1TATickerResponse' && (message.type === 'snapshot' || message.type === 'update')) {
|
|
125
|
+
const tickerMessage = message as BullishTickerMessage
|
|
126
|
+
|
|
127
|
+
return tickerMessage.data.symbol.endsWith('-PERP') || /-\d{8}$/.test(tickerMessage.data.symbol)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getFilters(symbols?: string[]) {
|
|
134
|
+
return [
|
|
135
|
+
{
|
|
136
|
+
channel: 'V1TATickerResponse' as const,
|
|
137
|
+
symbols
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
channel: 'V1TAIndexPrice' as const,
|
|
141
|
+
symbols: symbols === undefined ? undefined : [...new Set(symbols.map((symbol) => symbol.split('-')[0]))]
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
*map(message: BullishDerivativeTickerMessage | BullishIndexPriceMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> {
|
|
147
|
+
if (message.dataType === 'V1TAIndexPrice') {
|
|
148
|
+
const price = asNumberIfValid(message.data.price)
|
|
149
|
+
if (price !== undefined) {
|
|
150
|
+
this.indexPrices.set(message.data.assetSymbol, {
|
|
151
|
+
price,
|
|
152
|
+
timestamp: new Date(message.data.updatedAtDatetime)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(message.data.symbol, 'bullish')
|
|
160
|
+
const indexAsset = message.data.symbol.split('-')[0]
|
|
161
|
+
const indexPrice = this.indexPrices.get(indexAsset)
|
|
162
|
+
|
|
163
|
+
pendingTickerInfo.updateLastPrice(asNumberIfValid(message.data.last))
|
|
164
|
+
pendingTickerInfo.updateMarkPrice(asNumberIfValid(message.data.markPrice))
|
|
165
|
+
pendingTickerInfo.updateFundingRate(asNumberOrUndefined(message.data.fundingRate))
|
|
166
|
+
pendingTickerInfo.updateOpenInterest(asNumberOrUndefined(message.data.openInterest))
|
|
167
|
+
pendingTickerInfo.updateIndexPrice(indexPrice?.price)
|
|
168
|
+
|
|
169
|
+
if (pendingTickerInfo.hasChanged()) {
|
|
170
|
+
pendingTickerInfo.updateTimestamp(new Date(message.data.createdAtDatetime))
|
|
171
|
+
yield pendingTickerInfo.getSnapshot(localTimestamp)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class BullishOptionSummaryMapper implements Mapper<'bullish', OptionSummary> {
|
|
177
|
+
private readonly indexPrices = new Map<string, { price: number; timestamp: Date }>()
|
|
178
|
+
|
|
179
|
+
canHandle(message: BullishMessage): message is BullishOptionTickerMessage | BullishIndexPriceMessage {
|
|
180
|
+
if (message.dataType === 'V1TAIndexPrice' && (message.type === 'snapshot' || message.type === 'update')) {
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (message.dataType === 'V1TATickerResponse' && (message.type === 'snapshot' || message.type === 'update')) {
|
|
185
|
+
const tickerMessage = message as BullishTickerMessage
|
|
186
|
+
|
|
187
|
+
return tickerMessage.data.symbol.endsWith('-C') || tickerMessage.data.symbol.endsWith('-P')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
getFilters(symbols?: string[]) {
|
|
194
|
+
return [
|
|
195
|
+
{
|
|
196
|
+
channel: 'V1TATickerResponse' as const,
|
|
197
|
+
symbols
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
channel: 'V1TAIndexPrice' as const,
|
|
201
|
+
symbols: symbols === undefined ? undefined : [...new Set(symbols.map((symbol) => symbol.split('-')[0]))]
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
*map(message: BullishOptionTickerMessage | BullishIndexPriceMessage, localTimestamp: Date): IterableIterator<OptionSummary> {
|
|
207
|
+
if (message.dataType === 'V1TAIndexPrice') {
|
|
208
|
+
const price = asNumberIfValid(message.data.price)
|
|
209
|
+
if (price !== undefined) {
|
|
210
|
+
this.indexPrices.set(message.data.assetSymbol, {
|
|
211
|
+
price,
|
|
212
|
+
timestamp: new Date(message.data.updatedAtDatetime)
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const [indexAsset, , dateText, strikePriceText, optionType] = message.data.symbol.split('-')
|
|
220
|
+
const indexPrice = this.indexPrices.get(indexAsset)
|
|
221
|
+
|
|
222
|
+
const expirationDate = new Date(`${dateText.slice(0, 4)}-${dateText.slice(4, 6)}-${dateText.slice(6, 8)}Z`)
|
|
223
|
+
expirationDate.setUTCHours(8)
|
|
224
|
+
|
|
225
|
+
yield {
|
|
226
|
+
type: 'option_summary',
|
|
227
|
+
symbol: message.data.symbol,
|
|
228
|
+
exchange: 'bullish',
|
|
229
|
+
optionType: optionType === 'P' ? 'put' : 'call',
|
|
230
|
+
strikePrice: Number(strikePriceText),
|
|
231
|
+
expirationDate,
|
|
232
|
+
bestBidPrice: asNumberIfValid(message.data.bestBid),
|
|
233
|
+
bestBidAmount: asNumberIfValid(message.data.bidVolume),
|
|
234
|
+
bestBidIV: undefined,
|
|
235
|
+
bestAskPrice: asNumberIfValid(message.data.bestAsk),
|
|
236
|
+
bestAskAmount: asNumberIfValid(message.data.askVolume),
|
|
237
|
+
bestAskIV: undefined,
|
|
238
|
+
lastPrice: asNumberIfValid(message.data.last),
|
|
239
|
+
openInterest: asNumberOrUndefined(message.data.openInterest),
|
|
240
|
+
markPrice: asNumberOrUndefined(message.data.markPrice),
|
|
241
|
+
markIV: asNumberIfValid(message.data.impliedVolatility),
|
|
242
|
+
delta: asNumberOrUndefined(message.data.delta),
|
|
243
|
+
gamma: asNumberOrUndefined(message.data.gamma),
|
|
244
|
+
vega: asNumberOrUndefined(message.data.vega),
|
|
245
|
+
theta: asNumberOrUndefined(message.data.theta),
|
|
246
|
+
rho: undefined,
|
|
247
|
+
underlyingPrice: indexPrice?.price,
|
|
248
|
+
underlyingIndex: indexAsset,
|
|
249
|
+
timestamp: new Date(message.data.createdAtDatetime),
|
|
250
|
+
localTimestamp
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
type BullishMessage = BullishDataMessage<string, unknown>
|
|
256
|
+
type BullishDataMessage<TDataType extends string, TData> = {
|
|
257
|
+
type: BullishMessageRole
|
|
258
|
+
dataType: TDataType
|
|
259
|
+
data: TData
|
|
260
|
+
}
|
|
261
|
+
type BullishMessageRole = 'snapshot' | 'update'
|
|
262
|
+
|
|
263
|
+
type BullishAnonymousTradeUpdateMessage = BullishDataMessage<'V1TAAnonymousTradeUpdate', BullishAnonymousTradeUpdateData>
|
|
264
|
+
type BullishLevel2Message = BullishDataMessage<'V1TALevel2', BullishLevel2Data>
|
|
265
|
+
type BullishLevel1Message = BullishDataMessage<'V1TALevel1', BullishLevel1Data>
|
|
266
|
+
type BullishTickerMessage = BullishDataMessage<'V1TATickerResponse', BullishTickerData>
|
|
267
|
+
type BullishDerivativeTickerMessage = BullishDataMessage<'V1TATickerResponse', BullishDerivativeTickerData>
|
|
268
|
+
type BullishOptionTickerMessage = BullishDataMessage<'V1TATickerResponse', BullishOptionTickerData>
|
|
269
|
+
type BullishIndexPriceMessage = BullishDataMessage<'V1TAIndexPrice', BullishIndexPriceData>
|
|
270
|
+
|
|
271
|
+
type BullishAnonymousTradeUpdateData = {
|
|
272
|
+
symbol: string
|
|
273
|
+
createdAtTimestamp: string
|
|
274
|
+
publishedAtTimestamp: string
|
|
275
|
+
trades: BullishAnonymousTrade[]
|
|
276
|
+
}
|
|
277
|
+
type BullishAnonymousTrade = {
|
|
278
|
+
symbol: string
|
|
279
|
+
tradeId: string
|
|
280
|
+
price: string
|
|
281
|
+
quantity: string
|
|
282
|
+
side: BullishTradeSide
|
|
283
|
+
isTaker: boolean
|
|
284
|
+
createdAtTimestamp: string
|
|
285
|
+
publishedAtTimestamp: string
|
|
286
|
+
lastUpdatedTimestamp: string
|
|
287
|
+
createdAtDatetime: string
|
|
288
|
+
}
|
|
289
|
+
type BullishTradeSide = 'BUY' | 'SELL'
|
|
290
|
+
|
|
291
|
+
type BullishLevel2Data = {
|
|
292
|
+
timestamp: string
|
|
293
|
+
bids: string[]
|
|
294
|
+
asks: string[]
|
|
295
|
+
publishedAtTimestamp: string
|
|
296
|
+
datetime: string
|
|
297
|
+
sequenceNumberRange: [number, number]
|
|
298
|
+
symbol: string
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
type BullishLevel1Data = {
|
|
302
|
+
timestamp: string
|
|
303
|
+
bid: [string, string]
|
|
304
|
+
ask: [string, string]
|
|
305
|
+
publishedAtTimestamp: string
|
|
306
|
+
datetime: string
|
|
307
|
+
sequenceNumber: string
|
|
308
|
+
symbol: string
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
type BullishIndexPriceData = {
|
|
312
|
+
price: string
|
|
313
|
+
assetSymbol: string
|
|
314
|
+
updatedAtDatetime: string
|
|
315
|
+
updatedAtTimestamp: string
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
type BullishTickerData = BullishSpotTickerData | BullishDerivativeTickerData | BullishOptionTickerData
|
|
319
|
+
|
|
320
|
+
type BullishSpotTickerData = BullishTickerDataBase
|
|
321
|
+
|
|
322
|
+
type BullishDerivativeTickerData = BullishTickerDataBase & {
|
|
323
|
+
markPrice: string | null
|
|
324
|
+
fundingRate?: string | null
|
|
325
|
+
openInterest: string | null
|
|
326
|
+
openInterestUSD: string | null
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
type BullishOptionTickerData = BullishTickerDataBase & {
|
|
330
|
+
markPrice: string | null
|
|
331
|
+
openInterest: string | null
|
|
332
|
+
openInterestUSD: string | null
|
|
333
|
+
delta: string | null
|
|
334
|
+
gamma: string | null
|
|
335
|
+
theta: string | null
|
|
336
|
+
vega: string | null
|
|
337
|
+
impliedVolatility: string | null
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
type BullishTickerDataBase = {
|
|
341
|
+
askVolume: string | null
|
|
342
|
+
average: string | null
|
|
343
|
+
baseVolume: string
|
|
344
|
+
bestAsk?: string | null
|
|
345
|
+
bestBid?: string | null
|
|
346
|
+
bidVolume?: string | null
|
|
347
|
+
change: string
|
|
348
|
+
close: string | null
|
|
349
|
+
createdAtTimestamp: string
|
|
350
|
+
publishedAtTimestamp: string
|
|
351
|
+
high: string | null
|
|
352
|
+
last: string | null
|
|
353
|
+
lastTradeDatetime: string | null
|
|
354
|
+
lastTradeSize: string
|
|
355
|
+
low: string | null
|
|
356
|
+
open: string | null
|
|
357
|
+
percentage: string
|
|
358
|
+
quoteVolume: string
|
|
359
|
+
symbol: string
|
|
360
|
+
type: 'ticker'
|
|
361
|
+
vwap: string | null
|
|
362
|
+
currentPrice: string | null
|
|
363
|
+
ammData: BullishTickerAmmData[] | null
|
|
364
|
+
createdAtDatetime: string
|
|
365
|
+
otcBaseVolume: string
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
type BullishTickerAmmData = {
|
|
369
|
+
feeTierId: string
|
|
370
|
+
tierPrice: string
|
|
371
|
+
currentPrice: string
|
|
372
|
+
bidSpreadFee: string
|
|
373
|
+
askSpreadFee: string
|
|
374
|
+
}
|
package/src/mappers/index.ts
CHANGED
|
@@ -48,6 +48,13 @@ import {
|
|
|
48
48
|
import { BitnomialBookChangMapper, bitnomialTradesMapper } from './bitnomial.ts'
|
|
49
49
|
import { BitstampBookChangeMapper, bitstampTradesMapper } from './bitstamp.ts'
|
|
50
50
|
import { BlockchainComBookChangeMapper, BlockchainComTradesMapper } from './blockchaincom.ts'
|
|
51
|
+
import {
|
|
52
|
+
BullishBookChangeMapper,
|
|
53
|
+
BullishBookTickerMapper,
|
|
54
|
+
BullishDerivativeTickerMapper,
|
|
55
|
+
BullishOptionSummaryMapper,
|
|
56
|
+
BullishTradesMapper
|
|
57
|
+
} from './bullish.ts'
|
|
51
58
|
import {
|
|
52
59
|
BybitBookChangeMapper,
|
|
53
60
|
BybitDerivativeTickerMapper,
|
|
@@ -342,7 +349,8 @@ const tradesMappers = {
|
|
|
342
349
|
shouldUseBitgetV3Mappers(localTimestamp) ? new BitgetV3TradesMapper('bitget-futures') : new BitgetTradesMapper('bitget-futures'),
|
|
343
350
|
'coinbase-international': () => coinbaseInternationalTradesMapper,
|
|
344
351
|
hyperliquid: () => new HyperliquidTradesMapper(),
|
|
345
|
-
lighter: () => new LighterTradesMapper()
|
|
352
|
+
lighter: () => new LighterTradesMapper(),
|
|
353
|
+
bullish: () => new BullishTradesMapper()
|
|
346
354
|
}
|
|
347
355
|
|
|
348
356
|
const bookChangeMappers = {
|
|
@@ -442,7 +450,8 @@ const bookChangeMappers = {
|
|
|
442
450
|
: new BitgetBookChangeMapper('bitget-futures'),
|
|
443
451
|
'coinbase-international': () => new CoinbaseInternationalBookChangMapper(),
|
|
444
452
|
hyperliquid: () => new HyperliquidBookChangeMapper(),
|
|
445
|
-
lighter: () => new LighterBookChangeMapper()
|
|
453
|
+
lighter: () => new LighterBookChangeMapper(),
|
|
454
|
+
bullish: () => new BullishBookChangeMapper()
|
|
446
455
|
}
|
|
447
456
|
|
|
448
457
|
const derivativeTickersMappers = {
|
|
@@ -480,7 +489,8 @@ const derivativeTickersMappers = {
|
|
|
480
489
|
shouldUseBitgetV3Mappers(localTimestamp) ? new BitgetV3DerivativeTickerMapper() : new BitgetDerivativeTickerMapper(),
|
|
481
490
|
'coinbase-international': () => new CoinbaseInternationalDerivativeTickerMapper(),
|
|
482
491
|
hyperliquid: () => new HyperliquidDerivativeTickerMapper(),
|
|
483
|
-
lighter: () => new LighterDerivativeTickerMapper()
|
|
492
|
+
lighter: () => new LighterDerivativeTickerMapper(),
|
|
493
|
+
bullish: () => new BullishDerivativeTickerMapper()
|
|
484
494
|
}
|
|
485
495
|
|
|
486
496
|
const optionsSummaryMappers = {
|
|
@@ -492,7 +502,8 @@ const optionsSummaryMappers = {
|
|
|
492
502
|
'binance-european-options': (localTimestamp: Date) =>
|
|
493
503
|
shouldUseBinanceEuropeanOptionsV2Mappers(localTimestamp)
|
|
494
504
|
? new BinanceEuropeanOptionSummaryMapperV2()
|
|
495
|
-
: new BinanceEuropeanOptionSummaryMapper()
|
|
505
|
+
: new BinanceEuropeanOptionSummaryMapper(),
|
|
506
|
+
bullish: () => new BullishOptionSummaryMapper()
|
|
496
507
|
}
|
|
497
508
|
|
|
498
509
|
const liquidationsMappers = {
|
|
@@ -589,6 +600,7 @@ const bookTickersMappers = {
|
|
|
589
600
|
'coinbase-international': () => coinbaseInternationalBookTickerMapper,
|
|
590
601
|
hyperliquid: () => new HyperliquidBookTickerMapper(),
|
|
591
602
|
lighter: () => new LighterBookTickerMapper(),
|
|
603
|
+
bullish: () => new BullishBookTickerMapper(),
|
|
592
604
|
'binance-european-options': () => new BinanceEuropeanOptionsBookTickerMapper()
|
|
593
605
|
}
|
|
594
606
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Filter } from '../types.ts'
|
|
2
|
+
import { MultiConnectionRealTimeFeedBase, RealTimeFeedBase } from './realtimefeed.ts'
|
|
3
|
+
|
|
4
|
+
type BullishSubscriptionTarget = {
|
|
5
|
+
readonly topic: string
|
|
6
|
+
readonly path: string
|
|
7
|
+
readonly symbolParam?: 'symbol' | 'assetSymbol'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const BULLISH_SUBSCRIPTION_TARGETS: Record<string, BullishSubscriptionTarget> = {
|
|
11
|
+
V1TALevel2: {
|
|
12
|
+
topic: 'l2Orderbook',
|
|
13
|
+
path: '/trading-api/v1/market-data/orderbook',
|
|
14
|
+
symbolParam: 'symbol'
|
|
15
|
+
},
|
|
16
|
+
V1TALevel1: {
|
|
17
|
+
topic: 'l1Orderbook',
|
|
18
|
+
path: '/trading-api/v1/market-data/orderbook',
|
|
19
|
+
symbolParam: 'symbol'
|
|
20
|
+
},
|
|
21
|
+
V1TAAnonymousTradeUpdate: {
|
|
22
|
+
topic: 'anonymousTrades',
|
|
23
|
+
path: '/trading-api/v1/market-data/trades',
|
|
24
|
+
symbolParam: 'symbol'
|
|
25
|
+
},
|
|
26
|
+
V1TATickerResponse: {
|
|
27
|
+
topic: 'tick',
|
|
28
|
+
path: '/trading-api/v1/market-data/tick',
|
|
29
|
+
symbolParam: 'symbol'
|
|
30
|
+
},
|
|
31
|
+
V1TAIndexPrice: {
|
|
32
|
+
topic: 'indexPrice',
|
|
33
|
+
path: '/trading-api/v1/index-data',
|
|
34
|
+
symbolParam: 'assetSymbol'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getBullishSubscriptionTarget(channel: string) {
|
|
39
|
+
const target = BULLISH_SUBSCRIPTION_TARGETS[channel]
|
|
40
|
+
if (target === undefined) {
|
|
41
|
+
throw new Error(`BullishRealTimeFeed unsupported channel ${channel}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return target
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class BullishRealTimeFeed extends MultiConnectionRealTimeFeedBase {
|
|
48
|
+
protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[], timeoutIntervalMS?: number, onError?: (error: Error) => void) {
|
|
49
|
+
for (const [path, pathFilters] of this.groupByPath(filters)) {
|
|
50
|
+
yield new BullishSingleConnectionRealTimeFeed(exchange, path, pathFilters, timeoutIntervalMS, onError)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private groupByPath(filters: Filter<string>[]) {
|
|
55
|
+
const filtersByPath = new Map<string, Filter<string>[]>()
|
|
56
|
+
|
|
57
|
+
for (const filter of filters) {
|
|
58
|
+
const target = getBullishSubscriptionTarget(filter.channel)
|
|
59
|
+
const pathFilters = filtersByPath.get(target.path) ?? []
|
|
60
|
+
pathFilters.push(filter)
|
|
61
|
+
filtersByPath.set(target.path, pathFilters)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return filtersByPath
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class BullishSingleConnectionRealTimeFeed extends RealTimeFeedBase {
|
|
69
|
+
protected wssURL = 'wss://api.exchange.bullish.com'
|
|
70
|
+
private nextMessageId = 1
|
|
71
|
+
|
|
72
|
+
constructor(
|
|
73
|
+
exchange: string,
|
|
74
|
+
private readonly path: string,
|
|
75
|
+
filters: Filter<string>[],
|
|
76
|
+
timeoutIntervalMS: number | undefined,
|
|
77
|
+
onError?: (error: Error) => void
|
|
78
|
+
) {
|
|
79
|
+
super(exchange, filters, timeoutIntervalMS, onError)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected async getWebSocketUrl() {
|
|
83
|
+
const baseURL = await super.getWebSocketUrl()
|
|
84
|
+
return `${baseURL.replace(/\/$/, '')}${this.path}`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
|
|
88
|
+
return filters.flatMap((filter) => {
|
|
89
|
+
const target = getBullishSubscriptionTarget(filter.channel)
|
|
90
|
+
|
|
91
|
+
if (target.symbolParam == null) {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
jsonrpc: '2.0',
|
|
95
|
+
type: 'command',
|
|
96
|
+
method: 'subscribe',
|
|
97
|
+
id: (this.nextMessageId++).toString(),
|
|
98
|
+
params: {
|
|
99
|
+
topic: target.topic
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!filter.symbols || filter.symbols.length === 0) {
|
|
106
|
+
throw new Error('BullishRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return filter.symbols.map((symbol) => {
|
|
110
|
+
return {
|
|
111
|
+
jsonrpc: '2.0',
|
|
112
|
+
type: 'command',
|
|
113
|
+
method: 'subscribe',
|
|
114
|
+
id: (this.nextMessageId++).toString(),
|
|
115
|
+
params: {
|
|
116
|
+
topic: target.topic,
|
|
117
|
+
[target.symbolParam!]: symbol
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected messageIsError(message: any): boolean {
|
|
125
|
+
return message.error !== undefined && message.error !== null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected sendCustomPing = () => {
|
|
129
|
+
this.send({
|
|
130
|
+
jsonrpc: '2.0',
|
|
131
|
+
type: 'command',
|
|
132
|
+
method: 'keepalivePing',
|
|
133
|
+
id: (this.nextMessageId++).toString()
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
protected messageIsHeartbeat(message: any) {
|
|
138
|
+
return message.dataType === 'V1TAHeartbeat' || message.result?.message === 'Keep alive pong'
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -53,6 +53,7 @@ import { BitgetFuturesRealTimeFeed, BitgetRealTimeFeed } from './bitget.ts'
|
|
|
53
53
|
import { CoinbaseInternationalRealTimeFeed } from './coinbaseinternational.ts'
|
|
54
54
|
import { HyperliquidRealTimeFeed } from './hyperliquid.ts'
|
|
55
55
|
import { LighterRealTimeFeed } from './lighter.ts'
|
|
56
|
+
import { BullishRealTimeFeed } from './bullish.ts'
|
|
56
57
|
|
|
57
58
|
export * from './realtimefeed.ts'
|
|
58
59
|
|
|
@@ -116,7 +117,8 @@ const realTimeFeedsMap: {
|
|
|
116
117
|
'bitget-futures': BitgetFuturesRealTimeFeed,
|
|
117
118
|
'coinbase-international': CoinbaseInternationalRealTimeFeed,
|
|
118
119
|
hyperliquid: HyperliquidRealTimeFeed,
|
|
119
|
-
lighter: LighterRealTimeFeed
|
|
120
|
+
lighter: LighterRealTimeFeed,
|
|
121
|
+
bullish: BullishRealTimeFeed
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
export function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {
|
package/src/types.ts
CHANGED
|
@@ -27,7 +27,8 @@ export type Trade = {
|
|
|
27
27
|
readonly id: string | undefined
|
|
28
28
|
readonly price: number
|
|
29
29
|
readonly amount: number
|
|
30
|
-
|
|
30
|
+
/** liquidity taker side (aggressor), not the resting maker order side */
|
|
31
|
+
readonly side: 'buy' | 'sell' | 'unknown'
|
|
31
32
|
readonly timestamp: Date
|
|
32
33
|
readonly localTimestamp: Date
|
|
33
34
|
}
|