tardis-dev 13.4.12 → 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 (66) hide show
  1. package/dist/consts.d.ts +4 -1
  2. package/dist/consts.d.ts.map +1 -1
  3. package/dist/consts.js +11 -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 +257 -0
  12. package/dist/mappers/cryptocom.d.ts.map +1 -0
  13. package/dist/mappers/cryptocom.js +193 -0
  14. package/dist/mappers/cryptocom.js.map +1 -0
  15. package/dist/mappers/huobi.d.ts +3 -3
  16. package/dist/mappers/index.d.ts +4 -4
  17. package/dist/mappers/index.d.ts.map +1 -1
  18. package/dist/mappers/index.js +16 -4
  19. package/dist/mappers/index.js.map +1 -1
  20. package/dist/mappers/kucoin.d.ts +109 -0
  21. package/dist/mappers/kucoin.d.ts.map +1 -0
  22. package/dist/mappers/kucoin.js +216 -0
  23. package/dist/mappers/kucoin.js.map +1 -0
  24. package/dist/realtimefeeds/binance.js +1 -1
  25. package/dist/realtimefeeds/binance.js.map +1 -1
  26. package/dist/realtimefeeds/cryptocom.d.ts +10 -0
  27. package/dist/realtimefeeds/cryptocom.d.ts.map +1 -0
  28. package/dist/realtimefeeds/cryptocom.js +49 -0
  29. package/dist/realtimefeeds/cryptocom.js.map +1 -0
  30. package/dist/realtimefeeds/cryptocomderivatives.d.ts +10 -0
  31. package/dist/realtimefeeds/cryptocomderivatives.d.ts.map +1 -0
  32. package/dist/realtimefeeds/cryptocomderivatives.js +51 -0
  33. package/dist/realtimefeeds/cryptocomderivatives.js.map +1 -0
  34. package/dist/realtimefeeds/ftx.js +1 -1
  35. package/dist/realtimefeeds/ftx.js.map +1 -1
  36. package/dist/realtimefeeds/huobi.js +2 -2
  37. package/dist/realtimefeeds/huobi.js.map +1 -1
  38. package/dist/realtimefeeds/index.d.ts.map +1 -1
  39. package/dist/realtimefeeds/index.js +7 -1
  40. package/dist/realtimefeeds/index.js.map +1 -1
  41. package/dist/realtimefeeds/kucoin.d.ts +13 -0
  42. package/dist/realtimefeeds/kucoin.d.ts.map +1 -0
  43. package/dist/realtimefeeds/kucoin.js +70 -0
  44. package/dist/realtimefeeds/kucoin.js.map +1 -0
  45. package/dist/realtimefeeds/realtimefeed.d.ts +1 -0
  46. package/dist/realtimefeeds/realtimefeed.d.ts.map +1 -1
  47. package/dist/realtimefeeds/realtimefeed.js +6 -2
  48. package/dist/realtimefeeds/realtimefeed.js.map +1 -1
  49. package/dist/replay.d.ts.map +1 -1
  50. package/dist/replay.js +11 -0
  51. package/dist/replay.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/consts.ts +14 -2
  54. package/src/handy.ts +4 -0
  55. package/src/mappers/cryptocom.ts +395 -0
  56. package/src/mappers/index.ts +16 -4
  57. package/src/mappers/kucoin.ts +311 -0
  58. package/src/realtimefeeds/binance.ts +1 -1
  59. package/src/realtimefeeds/cryptocom.ts +48 -0
  60. package/src/realtimefeeds/cryptocomderivatives.ts +50 -0
  61. package/src/realtimefeeds/ftx.ts +1 -1
  62. package/src/realtimefeeds/huobi.ts +2 -2
  63. package/src/realtimefeeds/index.ts +7 -1
  64. package/src/realtimefeeds/kucoin.ts +77 -0
  65. package/src/realtimefeeds/realtimefeed.ts +8 -3
  66. package/src/replay.ts +13 -1
@@ -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
@@ -0,0 +1,48 @@
1
+ import { Filter } from '../types'
2
+ import { RealTimeFeedBase } from './realtimefeed'
3
+
4
+ export class CryptoComRealTimeFeed extends RealTimeFeedBase {
5
+ protected wssURL = 'wss://stream.crypto.com/v2/market'
6
+
7
+ protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
8
+ const channels = filters
9
+ .map((filter) => {
10
+ if (!filter.symbols || filter.symbols.length === 0) {
11
+ throw new Error('CryptoComRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
12
+ }
13
+
14
+ return filter.symbols.map((symbol) => {
15
+ const suffix = filter.channel === 'book' ? '.150' : ''
16
+ return `${filter.channel}.${symbol}${suffix}`
17
+ })
18
+ })
19
+ .flatMap((s) => s)
20
+
21
+ return [
22
+ {
23
+ id: 1,
24
+ method: 'subscribe',
25
+ nonce: new Date().valueOf(),
26
+ params: {
27
+ channels: channels
28
+ }
29
+ }
30
+ ]
31
+ }
32
+
33
+ protected messageIsError(message: any): boolean {
34
+ return message.code !== undefined && message.code !== 0
35
+ }
36
+
37
+ protected onMessage(msg: any) {
38
+ if (msg.method === 'public/heartbeat') {
39
+ this.send({
40
+ id: msg.id,
41
+ method: 'public/respond-heartbeat'
42
+ })
43
+ }
44
+ }
45
+ protected messageIsHeartbeat(msg: any) {
46
+ return msg.method === 'public/heartbeat'
47
+ }
48
+ }
@@ -0,0 +1,50 @@
1
+ import { Filter } from '../types'
2
+ import { RealTimeFeedBase } from './realtimefeed'
3
+
4
+ export class CryptoComDerivativesRealTimeFeed extends RealTimeFeedBase {
5
+ protected wssURL = 'wss://deriv-stream.crypto.com/v1/market'
6
+
7
+ protected mapToSubscribeMessages(filters: Filter<string>[]): any[] {
8
+ const channels = filters
9
+ .map((filter) => {
10
+ if (!filter.symbols || filter.symbols.length === 0) {
11
+ throw new Error('CryptoComDerivativesRealTimeFeed requires explicitly specified symbols when subscribing to live feed')
12
+ }
13
+
14
+ return filter.symbols.map((symbol) => {
15
+ const suffix = filter.channel === 'book' ? '.50' : ''
16
+ return `${filter.channel}.${symbol}${suffix}`
17
+ })
18
+ })
19
+ .flatMap((s) => s)
20
+
21
+ return [
22
+ {
23
+ id: 1,
24
+ method: 'subscribe',
25
+ nonce: new Date().valueOf(),
26
+ params: {
27
+ channels: channels,
28
+ book_subscription_type: 'SNAPSHOT_AND_UPDATE',
29
+ book_update_frequency: 5
30
+ }
31
+ }
32
+ ]
33
+ }
34
+
35
+ protected messageIsError(message: any): boolean {
36
+ return message.code !== undefined && message.code !== 0 && message.code !== 40003
37
+ }
38
+
39
+ protected onMessage(msg: any) {
40
+ if (msg.method === 'public/heartbeat') {
41
+ this.send({
42
+ id: msg.id,
43
+ method: 'public/respond-heartbeat'
44
+ })
45
+ }
46
+ }
47
+ protected messageIsHeartbeat(msg: any) {
48
+ return msg.method === 'public/heartbeat'
49
+ }
50
+ }
@@ -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
@@ -42,6 +42,9 @@ import { SerumRealTimeFeed } from './serum'
42
42
  import { StarAtlasRealTimeFeed } from './staratlas'
43
43
  import { MangoRealTimeFeed } from './mango'
44
44
  import { BybitSpotRealTimeFeed } from './bybitspot'
45
+ import { CryptoComRealTimeFeed } from './cryptocom'
46
+ import { CryptoComDerivativesRealTimeFeed } from './cryptocomderivatives'
47
+ import { KucoinRealTimeFeed } from './kucoin'
45
48
 
46
49
  export * from './realtimefeed'
47
50
 
@@ -91,7 +94,10 @@ const realTimeFeedsMap: {
91
94
  'star-atlas': StarAtlasRealTimeFeed,
92
95
  'huobi-dm-options': HuobiDMOptionsRealTimeFeed,
93
96
  mango: MangoRealTimeFeed,
94
- 'bybit-spot': BybitSpotRealTimeFeed
97
+ 'bybit-spot': BybitSpotRealTimeFeed,
98
+ 'crypto-com': CryptoComRealTimeFeed,
99
+ 'crypto-com-derivatives': CryptoComDerivativesRealTimeFeed,
100
+ kucoin: KucoinRealTimeFeed
95
101
  }
96
102
 
97
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
 
package/src/replay.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createReadStream } from 'fs-extra'
1
+ import { createReadStream, remove } from 'fs-extra'
2
2
  import path from 'path'
3
3
  import { EventEmitter } from 'stream'
4
4
  import { Worker } from 'worker_threads'
@@ -166,6 +166,10 @@ export async function* replay<T extends Exchange, U extends boolean = false, Z e
166
166
 
167
167
  // remove slice key from the map as it's already processed
168
168
  cachedSlicePaths.delete(sliceKey)
169
+
170
+ if (autoCleanup) {
171
+ await cleanupSlice(cachedSlicePath)
172
+ }
169
173
  // move one minute forward
170
174
  currentSliceDate.setUTCMinutes(currentSliceDate.getUTCMinutes() + 1)
171
175
  }
@@ -209,6 +213,14 @@ export async function* replay<T extends Exchange, U extends boolean = false, Z e
209
213
  }
210
214
  }
211
215
 
216
+ async function cleanupSlice(slicePath: string) {
217
+ try {
218
+ await remove(slicePath)
219
+ } catch (e) {
220
+ debug('cleanupSlice error %s %o', slicePath, e)
221
+ }
222
+ }
223
+
212
224
  // gracefully terminate worker
213
225
  async function terminateWorker(worker: Worker, waitTimeout: number) {
214
226
  let cancelWait = () => {}