tardis-dev 13.5.0 → 13.6.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.
Files changed (48) hide show
  1. package/dist/consts.d.ts +2 -1
  2. package/dist/consts.d.ts.map +1 -1
  3. package/dist/consts.js +5 -2
  4. package/dist/consts.js.map +1 -1
  5. package/dist/handy.d.ts +1 -0
  6. package/dist/handy.d.ts.map +1 -1
  7. package/dist/handy.js +5 -1
  8. package/dist/handy.js.map +1 -1
  9. package/dist/mappers/bybit.d.ts +1 -1
  10. package/dist/mappers/bybitspot.d.ts +1 -1
  11. package/dist/mappers/cryptocom.d.ts +1 -1
  12. package/dist/mappers/huobi.d.ts +3 -3
  13. package/dist/mappers/index.d.ts +3 -3
  14. package/dist/mappers/index.d.ts.map +1 -1
  15. package/dist/mappers/index.js +7 -3
  16. package/dist/mappers/index.js.map +1 -1
  17. package/dist/mappers/kucoin.d.ts +109 -0
  18. package/dist/mappers/kucoin.d.ts.map +1 -0
  19. package/dist/mappers/kucoin.js +216 -0
  20. package/dist/mappers/kucoin.js.map +1 -0
  21. package/dist/realtimefeeds/binance.js +1 -1
  22. package/dist/realtimefeeds/binance.js.map +1 -1
  23. package/dist/realtimefeeds/ftx.js +1 -1
  24. package/dist/realtimefeeds/ftx.js.map +1 -1
  25. package/dist/realtimefeeds/huobi.js +2 -2
  26. package/dist/realtimefeeds/huobi.js.map +1 -1
  27. package/dist/realtimefeeds/index.d.ts.map +1 -1
  28. package/dist/realtimefeeds/index.js +3 -1
  29. package/dist/realtimefeeds/index.js.map +1 -1
  30. package/dist/realtimefeeds/kucoin.d.ts +13 -0
  31. package/dist/realtimefeeds/kucoin.d.ts.map +1 -0
  32. package/dist/realtimefeeds/kucoin.js +70 -0
  33. package/dist/realtimefeeds/kucoin.js.map +1 -0
  34. package/dist/realtimefeeds/realtimefeed.d.ts +1 -0
  35. package/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
  36. package/dist/realtimefeeds/realtimefeed.js +6 -2
  37. package/dist/realtimefeeds/realtimefeed.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/consts.ts +6 -2
  40. package/src/handy.ts +4 -0
  41. package/src/mappers/index.ts +7 -3
  42. package/src/mappers/kucoin.ts +311 -0
  43. package/src/realtimefeeds/binance.ts +1 -1
  44. package/src/realtimefeeds/ftx.ts +1 -1
  45. package/src/realtimefeeds/huobi.ts +2 -2
  46. package/src/realtimefeeds/index.ts +3 -1
  47. package/src/realtimefeeds/kucoin.ts +77 -0
  48. package/src/realtimefeeds/realtimefeed.ts +8 -3
package/src/consts.ts CHANGED
@@ -44,7 +44,8 @@ export const EXCHANGES = [
44
44
  'huobi-dm-options',
45
45
  'star-atlas',
46
46
  'crypto-com',
47
- 'crypto-com-derivatives'
47
+ 'crypto-com-derivatives',
48
+ 'kucoin'
48
49
  ] as const
49
50
 
50
51
  const BINANCE_CHANNELS = ['trade', 'aggTrade', 'ticker', 'depth', 'depthSnapshot', 'bookTicker', 'recentTrades', 'borrowInterest'] as const
@@ -380,6 +381,8 @@ const CRYPTO_COM_CHANNELS = ['trade', 'book', 'ticker']
380
381
 
381
382
  const CRYPTO_COM_DERIVATIVES = ['trade', 'book', 'ticker', 'settlement', 'index', 'mark', 'funding']
382
383
 
384
+ const KUCOIN_CHANNELS = ['market/ticker', 'market/snapshot', 'market/level2', 'market/match', 'market/level2Snapshot']
385
+
383
386
  export const EXCHANGE_CHANNELS_INFO = {
384
387
  bitmex: BITMEX_CHANNELS,
385
388
  coinbase: COINBASE_CHANNELS,
@@ -426,5 +429,6 @@ export const EXCHANGE_CHANNELS_INFO = {
426
429
  'huobi-dm-options': HUOBI_DM_OPTIONS_CHANNELS,
427
430
  mango: MANGO_CHANNELS,
428
431
  'crypto-com': CRYPTO_COM_CHANNELS,
429
- 'crypto-com-derivatives': CRYPTO_COM_DERIVATIVES
432
+ 'crypto-com-derivatives': CRYPTO_COM_DERIVATIVES,
433
+ kucoin: KUCOIN_CHANNELS
430
434
  }
package/src/handy.ts CHANGED
@@ -23,6 +23,10 @@ export function wait(delayMS: number) {
23
23
  })
24
24
  }
25
25
 
26
+ export function getRandomString() {
27
+ return crypto.randomBytes(24).toString('hex')
28
+ }
29
+
26
30
  export function formatDateToPath(date: Date) {
27
31
  const year = date.getUTCFullYear()
28
32
  const month = doubleDigit(date.getUTCMonth() + 1)
@@ -69,6 +69,7 @@ import {
69
69
  HuobiTradesMapper
70
70
  } from './huobi'
71
71
  import { krakenBookChangeMapper, krakenBookTickerMapper, krakenTradesMapper } from './kraken'
72
+ import { KucoinBookChangeMapper, KucoinBookTickerMapper, KucoinTradesMapper } from './kucoin'
72
73
  import { Mapper } from './mapper'
73
74
  import {
74
75
  OkexBookChangeMapper,
@@ -171,7 +172,8 @@ const tradesMappers = {
171
172
  mango: () => new SerumTradesMapper('mango'),
172
173
  'bybit-spot': () => new BybitSpotTradesMapper('bybit-spot'),
173
174
  'crypto-com': () => new CryptoComTradesMapper('crypto-com'),
174
- 'crypto-com-derivatives': () => new CryptoComTradesMapper('crypto-com-derivatives')
175
+ 'crypto-com-derivatives': () => new CryptoComTradesMapper('crypto-com-derivatives'),
176
+ kucoin: () => new KucoinTradesMapper('kucoin')
175
177
  }
176
178
 
177
179
  const bookChangeMappers = {
@@ -241,7 +243,8 @@ const bookChangeMappers = {
241
243
  'star-atlas': () => new SerumBookChangeMapper('star-atlas'),
242
244
  mango: () => new SerumBookChangeMapper('mango'),
243
245
  'crypto-com': () => new CryptoComBookChangeMapper('crypto-com'),
244
- 'crypto-com-derivatives': () => new CryptoComBookChangeMapper('crypto-com-derivatives')
246
+ 'crypto-com-derivatives': () => new CryptoComBookChangeMapper('crypto-com-derivatives'),
247
+ kucoin: () => new KucoinBookChangeMapper('kucoin')
245
248
  }
246
249
 
247
250
  const derivativeTickersMappers = {
@@ -349,7 +352,8 @@ const bookTickersMappers = {
349
352
  'gate-io-futures': () => new GateIOFuturesBookTickerMapper('gate-io-futures'),
350
353
  'bybit-spot': () => new BybitSpotBookTickerMapper('bybit-spot'),
351
354
  'crypto-com': () => new CryptoComBookTickerMapper('crypto-com'),
352
- 'crypto-com-derivatives': () => new CryptoComBookTickerMapper('crypto-com-derivatives')
355
+ 'crypto-com-derivatives': () => new CryptoComBookTickerMapper('crypto-com-derivatives'),
356
+ kucoin: () => new KucoinBookTickerMapper('kucoin')
353
357
  }
354
358
 
355
359
  export const normalizeTrades = <T extends keyof typeof tradesMappers>(exchange: T, localTimestamp: Date): Mapper<T, Trade> => {
@@ -0,0 +1,311 @@
1
+ import { BookPriceLevel } from '..'
2
+ import { CircularBuffer, upperCaseSymbols } from '../handy'
3
+ import { BookChange, Exchange, BookTicker, Trade } from '../types'
4
+ import { Mapper } from './mapper'
5
+
6
+ export class KucoinTradesMapper implements Mapper<'kucoin', Trade> {
7
+ constructor(private readonly _exchange: Exchange) {}
8
+ canHandle(message: KucoinTradeMessage) {
9
+ return message.type === 'message' && message.topic.startsWith('/market/match')
10
+ }
11
+
12
+ getFilters(symbols?: string[]) {
13
+ symbols = upperCaseSymbols(symbols)
14
+
15
+ return [
16
+ {
17
+ channel: 'market/match',
18
+ symbols
19
+ } as const
20
+ ]
21
+ }
22
+
23
+ *map(message: KucoinTradeMessage, localTimestamp: Date): IterableIterator<Trade> {
24
+ const kucoinTrade = message.data
25
+
26
+ const timestamp = new Date(Number(kucoinTrade.time.slice(0, 13)))
27
+
28
+ timestamp.μs = Number(kucoinTrade.time.slice(13, 16))
29
+
30
+ yield {
31
+ type: 'trade',
32
+ symbol: kucoinTrade.symbol,
33
+ exchange: this._exchange,
34
+ id: kucoinTrade.tradeId,
35
+ price: Number(kucoinTrade.price),
36
+ amount: Number(kucoinTrade.size),
37
+ side: kucoinTrade.side === 'sell' ? 'sell' : 'buy',
38
+ timestamp,
39
+ localTimestamp
40
+ }
41
+ }
42
+ }
43
+
44
+ export class KucoinBookChangeMapper implements Mapper<'kucoin', BookChange> {
45
+ protected readonly symbolToDepthInfoMapping: {
46
+ [key: string]: LocalDepthInfo
47
+ } = {}
48
+
49
+ constructor(protected readonly _exchange: Exchange) {}
50
+
51
+ canHandle(message: KucoinLevel2SnapshotMessage | KucoinLevel2UpdateMessage) {
52
+ return message.type === 'message' && message.topic.startsWith('/market/level2')
53
+ }
54
+
55
+ getFilters(symbols?: string[]) {
56
+ symbols = upperCaseSymbols(symbols)
57
+ return [
58
+ {
59
+ channel: 'market/level2',
60
+ symbols
61
+ } as const,
62
+ {
63
+ channel: 'market/level2Snapshot',
64
+ symbols
65
+ } as const
66
+ ]
67
+ }
68
+
69
+ *map(message: KucoinLevel2SnapshotMessage | KucoinLevel2UpdateMessage, localTimestamp: Date) {
70
+ const symbol = message.topic.split(':')[1]
71
+
72
+ if (this.symbolToDepthInfoMapping[symbol] === undefined) {
73
+ this.symbolToDepthInfoMapping[symbol] = {
74
+ bufferedUpdates: new CircularBuffer<KucoinLevel2UpdateMessage>(2000)
75
+ }
76
+ }
77
+
78
+ const symbolDepthInfo = this.symbolToDepthInfoMapping[symbol]
79
+ const snapshotAlreadyProcessed = symbolDepthInfo.snapshotProcessed
80
+
81
+ // first check if received message is snapshot and process it as such if it is
82
+ if (message.subject === 'trade.l2Snapshot') {
83
+ // if we've already received 'manual' snapshot, ignore if there is another one
84
+ if (snapshotAlreadyProcessed) {
85
+ return
86
+ }
87
+ // produce snapshot book_change
88
+ const kucoinSnapshotData = message.data
89
+
90
+ // mark given symbol depth info that has snapshot processed
91
+ symbolDepthInfo.lastUpdateId = Number(kucoinSnapshotData.sequence)
92
+ symbolDepthInfo.snapshotProcessed = true
93
+
94
+ // if there were any depth updates buffered, let's proccess those by adding to or updating the initial snapshot
95
+ for (const update of symbolDepthInfo.bufferedUpdates.items()) {
96
+ const bookChange = this.mapBookDepthUpdate(update, localTimestamp)
97
+ if (bookChange !== undefined) {
98
+ for (const bid of update.data.changes.bids) {
99
+ if (bid[0] == '0') {
100
+ continue
101
+ }
102
+ const matchingBid = kucoinSnapshotData.bids.find((b) => b[0] === bid[0])
103
+ if (matchingBid !== undefined) {
104
+ matchingBid[1] = bid[1]
105
+ } else {
106
+ kucoinSnapshotData.bids.push([bid[0], bid[1]])
107
+ }
108
+ }
109
+
110
+ for (const ask of update.data.changes.asks) {
111
+ if (ask[0] == '0') {
112
+ continue
113
+ }
114
+
115
+ const matchingAsk = kucoinSnapshotData.asks.find((a) => a[0] === ask[0])
116
+ if (matchingAsk !== undefined) {
117
+ matchingAsk[1] = ask[1]
118
+ } else {
119
+ kucoinSnapshotData.asks.push([ask[0], ask[1]])
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ // remove all buffered updates
126
+ symbolDepthInfo.bufferedUpdates.clear()
127
+
128
+ const bookChange: BookChange = {
129
+ type: 'book_change',
130
+ symbol,
131
+ exchange: this._exchange,
132
+ isSnapshot: true,
133
+ bids: kucoinSnapshotData.bids.map(this.mapBookLevel),
134
+ asks: kucoinSnapshotData.asks.map(this.mapBookLevel),
135
+ timestamp: localTimestamp,
136
+ localTimestamp
137
+ }
138
+
139
+ yield bookChange
140
+ } else if (snapshotAlreadyProcessed) {
141
+ // snapshot was already processed let's map the message as normal book_change
142
+ const bookChange = this.mapBookDepthUpdate(message, localTimestamp)
143
+ if (bookChange !== undefined) {
144
+ yield bookChange
145
+ }
146
+ } else {
147
+ symbolDepthInfo.bufferedUpdates.append(message)
148
+ }
149
+ }
150
+
151
+ protected mapBookDepthUpdate(l2UpdateMessage: KucoinLevel2UpdateMessage, localTimestamp: Date): BookChange | undefined {
152
+ // we can safely assume here that depthContext and lastUpdateId aren't null here as this is method only works
153
+ // when we've already processed the snapshot
154
+ const depthContext = this.symbolToDepthInfoMapping[l2UpdateMessage.data.symbol]!
155
+ const lastUpdateId = depthContext.lastUpdateId!
156
+
157
+ // Drop any event where sequenceEnd is <= lastUpdateId in the snapshot
158
+ if (l2UpdateMessage.data.sequenceEnd <= lastUpdateId) {
159
+ return
160
+ }
161
+
162
+ // The first processed event should have sequenceStart <= lastUpdateId+1 AND sequenceEnd >= lastUpdateId+1.
163
+ if (!depthContext.validatedFirstUpdate) {
164
+ // if there is new instrument added it can have empty book at first and that's normal
165
+ const bookSnapshotIsEmpty = lastUpdateId == -1 || lastUpdateId == 0
166
+
167
+ if (
168
+ (l2UpdateMessage.data.sequenceStart <= lastUpdateId + 1 && l2UpdateMessage.data.sequenceEnd >= lastUpdateId + 1) ||
169
+ bookSnapshotIsEmpty
170
+ ) {
171
+ depthContext.validatedFirstUpdate = true
172
+ } else {
173
+ const message = `Book depth snapshot has no overlap with first update, update ${JSON.stringify(
174
+ l2UpdateMessage
175
+ )}, lastUpdateId: ${lastUpdateId}, exchange ${this._exchange}`
176
+
177
+ throw new Error(message)
178
+ }
179
+ }
180
+ const bids = l2UpdateMessage.data.changes.bids.map(this.mapBookLevel).filter(this.nonZeroLevels)
181
+ const asks = l2UpdateMessage.data.changes.asks.map(this.mapBookLevel).filter(this.nonZeroLevels)
182
+
183
+ if (bids.length === 0 && asks.length === 0) {
184
+ return
185
+ }
186
+
187
+ return {
188
+ type: 'book_change',
189
+ symbol: l2UpdateMessage.data.symbol,
190
+ exchange: this._exchange,
191
+ isSnapshot: false,
192
+ bids,
193
+ asks,
194
+ timestamp: localTimestamp,
195
+ localTimestamp: localTimestamp
196
+ }
197
+ }
198
+
199
+ private mapBookLevel(level: [string, string, string?]) {
200
+ const price = Number(level[0])
201
+ const amount = Number(level[1])
202
+ return { price, amount }
203
+ }
204
+
205
+ private nonZeroLevels(level: BookPriceLevel) {
206
+ return level.price > 0
207
+ }
208
+ }
209
+
210
+ export class KucoinBookTickerMapper implements Mapper<'kucoin', BookTicker> {
211
+ constructor(protected readonly _exchange: Exchange) {}
212
+
213
+ canHandle(message: KucoinTickerMessage) {
214
+ return message.type === 'message' && message.topic.startsWith('/market/ticker')
215
+ }
216
+
217
+ getFilters(symbols?: string[]) {
218
+ symbols = upperCaseSymbols(symbols)
219
+ return [
220
+ {
221
+ channel: 'market/ticker',
222
+ symbols
223
+ } as const
224
+ ]
225
+ }
226
+
227
+ *map(message: KucoinTickerMessage, localTimestamp: Date) {
228
+ const symbol = message.topic.split(':')[1]
229
+
230
+ const bookTicker: BookTicker = {
231
+ type: 'book_ticker',
232
+ symbol,
233
+ exchange: this._exchange,
234
+ askAmount: message.data.bestAskSize !== undefined && message.data.bestAskSize !== null ? Number(message.data.bestAskSize) : undefined,
235
+ askPrice: message.data.bestAsk !== undefined && message.data.bestAsk !== null ? Number(message.data.bestAsk) : undefined,
236
+ bidPrice: message.data.bestBid !== undefined && message.data.bestBid !== null ? Number(message.data.bestBid) : undefined,
237
+ bidAmount: message.data.bestBidSize !== undefined && message.data.bestBidSize !== null ? Number(message.data.bestBidSize) : undefined,
238
+ timestamp: new Date(message.data.time),
239
+ localTimestamp: localTimestamp
240
+ }
241
+
242
+ yield bookTicker
243
+ }
244
+ }
245
+
246
+ type KucoinTickerMessage = {
247
+ type: 'message'
248
+ topic: '/market/ticker:ADA-USDT'
249
+ subject: 'trade.ticker'
250
+ data: {
251
+ bestAsk: '0.549931'
252
+ bestAskSize: '966.4756'
253
+ bestBid: '0.549824'
254
+ bestBidSize: '1050'
255
+ price: '0.549825'
256
+ sequence: '1623526404099'
257
+ size: '1'
258
+ time: 1660608019871
259
+ }
260
+ }
261
+
262
+ type KucoinTradeMessage = {
263
+ type: 'message'
264
+ topic: '/market/match:BTC-USDT'
265
+ subject: 'trade.l3match'
266
+ data: {
267
+ symbol: 'BTC-USDT'
268
+ side: 'sell'
269
+ type: 'match'
270
+ makerOrderId: '62fadde41add68000167fb58'
271
+ sequence: '1636276321894'
272
+ size: '0.00001255'
273
+ price: '24093.9'
274
+ takerOrderId: '62faddfff0476c0001c86c71'
275
+ time: '1660608000026914990'
276
+ tradeId: '62fade002e113d292303a18b'
277
+ }
278
+ }
279
+
280
+ type LocalDepthInfo = {
281
+ bufferedUpdates: CircularBuffer<KucoinLevel2UpdateMessage>
282
+ snapshotProcessed?: boolean
283
+ lastUpdateId?: number
284
+ validatedFirstUpdate?: boolean
285
+ }
286
+
287
+ type KucoinLevel2SnapshotMessage = {
288
+ type: 'message'
289
+ generated: true
290
+ topic: '/market/level2Snapshot:BTC-USDT'
291
+ subject: 'trade.l2Snapshot'
292
+ code: '200000'
293
+ data: {
294
+ time: 1660608003710
295
+ sequence: '1636276324355'
296
+ bids: [string, string][]
297
+ asks: [string, string][]
298
+ }
299
+ }
300
+
301
+ type KucoinLevel2UpdateMessage = {
302
+ type: 'message'
303
+ topic: '/market/level2:BTC-USDT'
304
+ subject: 'trade.l2update'
305
+ data: {
306
+ sequenceStart: 1636276324710
307
+ symbol: 'BTC-USDT'
308
+ changes: { asks: [string, string, string][]; bids: [string, string, string][] }
309
+ sequenceEnd: 1636276324710
310
+ }
311
+ }
@@ -39,7 +39,7 @@ class BinanceFuturesOpenInterestClient extends PoolingClientBase {
39
39
 
40
40
  protected async poolDataToStream(outputStream: Writable) {
41
41
  for (const instruments of batch(this._instruments, 10)) {
42
- await Promise.all(
42
+ await Promise.allSettled(
43
43
  instruments.map(async (instrument) => {
44
44
  if (outputStream.destroyed) {
45
45
  return
@@ -85,7 +85,7 @@ class FTXInstrumentInfoClient extends PoolingClientBase {
85
85
 
86
86
  protected async poolDataToStream(outputStream: Writable) {
87
87
  for (const instruments of batch(this._instruments, 10)) {
88
- await Promise.all(
88
+ await Promise.allSettled(
89
89
  instruments.map(async (instrument) => {
90
90
  if (outputStream.destroyed) {
91
91
  return
@@ -241,7 +241,7 @@ class HuobiOpenInterestClient extends PoolingClientBase {
241
241
 
242
242
  protected async poolDataToStream(outputStream: Writable) {
243
243
  for (const instruments of batch(this._instruments, 10)) {
244
- await Promise.all(
244
+ await Promise.allSettled(
245
245
  instruments.map(async (instrument) => {
246
246
  if (outputStream.destroyed) {
247
247
  return
@@ -311,7 +311,7 @@ class HuobiOptionsIndexClient extends PoolingClientBase {
311
311
 
312
312
  protected async poolDataToStream(outputStream: Writable) {
313
313
  for (const instruments of batch(this._instruments, 10)) {
314
- await Promise.all(
314
+ await Promise.allSettled(
315
315
  instruments.map(async (instrument) => {
316
316
  if (outputStream.destroyed) {
317
317
  return
@@ -44,6 +44,7 @@ import { MangoRealTimeFeed } from './mango'
44
44
  import { BybitSpotRealTimeFeed } from './bybitspot'
45
45
  import { CryptoComRealTimeFeed } from './cryptocom'
46
46
  import { CryptoComDerivativesRealTimeFeed } from './cryptocomderivatives'
47
+ import { KucoinRealTimeFeed } from './kucoin'
47
48
 
48
49
  export * from './realtimefeed'
49
50
 
@@ -95,7 +96,8 @@ const realTimeFeedsMap: {
95
96
  mango: MangoRealTimeFeed,
96
97
  'bybit-spot': BybitSpotRealTimeFeed,
97
98
  'crypto-com': CryptoComRealTimeFeed,
98
- 'crypto-com-derivatives': CryptoComDerivativesRealTimeFeed
99
+ 'crypto-com-derivatives': CryptoComDerivativesRealTimeFeed,
100
+ kucoin: KucoinRealTimeFeed
99
101
  }
100
102
 
101
103
  export function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {
@@ -0,0 +1,77 @@
1
+ import { httpClient, getRandomString, wait } from '../handy'
2
+ import { Filter } from '../types'
3
+ import { RealTimeFeedBase } from './realtimefeed'
4
+
5
+ export class KucoinRealTimeFeed extends RealTimeFeedBase {
6
+ protected wssURL = ''
7
+ private _httpURL = 'https://api.kucoin.com/api'
8
+
9
+ protected async getWebSocketUrl() {
10
+ const response = (await httpClient.post(`${this._httpURL}/v1/bullet-public`, { retry: 3, timeout: 10000 }).json()) as any
11
+
12
+ return `${response.data.instanceServers[0].endpoint}?token=${response.data.token}&connectId=${getRandomString()}`
13
+ }
14
+
15
+ protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
16
+ return filters
17
+ .filter((f) => f.channel !== 'market/level2Snapshot')
18
+ .map((filter) => {
19
+ if (!filter.symbols || filter.symbols.length === 0) {
20
+ throw new Error('KucoinRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
21
+ }
22
+
23
+ return {
24
+ id: getRandomString(),
25
+ type: 'subscribe',
26
+ topic: `/${filter.channel}:${filter.symbols.join(',')}`,
27
+ privateChannel: false,
28
+ response: true
29
+ }
30
+ })
31
+ }
32
+
33
+ protected async provideManualSnapshots(filters: Filter<string>[], shouldCancel: () => boolean) {
34
+ const depthSnapshotFilter = filters.find((f) => f.channel === 'market/level2Snapshot')
35
+ if (!depthSnapshotFilter) {
36
+ return
37
+ }
38
+
39
+ this.debug('requesting manual snapshots for: %s', depthSnapshotFilter.symbols)
40
+ for (let symbol of depthSnapshotFilter.symbols!) {
41
+ if (shouldCancel()) {
42
+ return
43
+ }
44
+
45
+ const depthSnapshotResponse = (await httpClient
46
+ .get(`${this._httpURL}/v1/market/orderbook/level2_100?symbol=${symbol}`, { timeout: 10000 })
47
+ .json()) as any
48
+
49
+ const snapshot = {
50
+ type: 'message',
51
+ generated: true,
52
+ topic: `/market/level2Snapshot:${symbol}`,
53
+ subject: 'trade.l2Snapshot',
54
+ ...depthSnapshotResponse
55
+ }
56
+
57
+ this.manualSnapshotsBuffer.push(snapshot)
58
+ }
59
+
60
+ this.debug('requested manual snapshots successfully for: %s ', depthSnapshotFilter.symbols)
61
+ }
62
+
63
+ protected messageIsError(message: any): boolean {
64
+ return message.type === 'error'
65
+ }
66
+
67
+ protected sendCustomPing = () => {
68
+ this.send({
69
+ id: new Date().valueOf().toString(),
70
+ type: 'ping'
71
+ })
72
+ }
73
+
74
+ protected messageIsHeartbeat(msg: any) {
75
+ return msg.type === 'pong'
76
+ }
77
+ }
@@ -58,6 +58,13 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
58
58
  }
59
59
  }
60
60
 
61
+ protected async getWebSocketUrl() {
62
+ const wssUrlOverride = process.env[`WSS_URL_${this._exchange.toUpperCase()}`]
63
+ const finalWssUrl = wssUrlOverride !== undefined ? wssUrlOverride : this.wssURL
64
+
65
+ return finalWssUrl
66
+ }
67
+
61
68
  private async *_stream() {
62
69
  let staleConnectionTimerId
63
70
  let pingTimerId
@@ -66,9 +73,7 @@ export abstract class RealTimeFeedBase implements RealTimeFeedIterable {
66
73
  while (true) {
67
74
  try {
68
75
  const subscribeMessages = this.mapToSubscribeMessages(this._filters)
69
-
70
- const wssUrlOverride = process.env[`WSS_URL_${this._exchange.toUpperCase()}`]
71
- const finalWssUrl = wssUrlOverride !== undefined ? wssUrlOverride : this.wssURL
76
+ const finalWssUrl = await this.getWebSocketUrl()
72
77
 
73
78
  this.debug('(connection id: %d) estabilishing connection to %s', this._connectionId, finalWssUrl)
74
79