tardis-dev 13.4.12 → 13.4.13

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.
@@ -0,0 +1,395 @@
1
+ import { upperCaseSymbols } from '../handy'
2
+ import { BookChange, Exchange, BookTicker, Trade, DerivativeTicker } from '../types'
3
+ import { Mapper, PendingTickerInfoHelper } from './mapper'
4
+
5
+ export class CryptoComTradesMapper implements Mapper<'crypto-com' | 'crypto-com-derivatives', Trade> {
6
+ constructor(private readonly _exchange: Exchange) {}
7
+ canHandle(message: CryptoComTradeMessage) {
8
+ return message.result !== undefined && message.result.channel === 'trade'
9
+ }
10
+
11
+ getFilters(symbols?: string[]) {
12
+ symbols = upperCaseSymbols(symbols)
13
+
14
+ return [
15
+ {
16
+ channel: 'trade',
17
+ symbols
18
+ } as const
19
+ ]
20
+ }
21
+
22
+ *map(message: CryptoComTradeMessage, localTimestamp: Date): IterableIterator<Trade> {
23
+ message.result.data.reverse()
24
+
25
+ for (const item of message.result.data) {
26
+ const trade: Trade = {
27
+ type: 'trade',
28
+ symbol: message.result.instrument_name,
29
+ exchange: this._exchange,
30
+ id: item.d.toString(),
31
+ price: Number(item.p),
32
+ amount: Number(item.q),
33
+ side: item.s === 'BUY' ? 'buy' : 'sell',
34
+ timestamp: new Date(item.t),
35
+ localTimestamp
36
+ }
37
+
38
+ yield trade
39
+ }
40
+ }
41
+ }
42
+
43
+ export class CryptoComBookChangeMapper implements Mapper<'crypto-com' | 'crypto-com-derivatives', BookChange> {
44
+ constructor(protected readonly _exchange: Exchange) {}
45
+
46
+ canHandle(message: CryptoComBookMessage) {
47
+ return message.result !== undefined && message.result.channel.startsWith('book')
48
+ }
49
+
50
+ getFilters(symbols?: string[]) {
51
+ symbols = upperCaseSymbols(symbols)
52
+ return [
53
+ {
54
+ channel: 'book',
55
+ symbols
56
+ } as const
57
+ ]
58
+ }
59
+
60
+ *map(message: CryptoComBookMessage, localTimestamp: Date) {
61
+ if (message.result.data === undefined || message.result.data[0] === undefined) {
62
+ return
63
+ }
64
+
65
+ const bids = (message.result.channel === 'book' ? message.result.data[0].bids : message.result.data[0].update.bids) || []
66
+ const asks = (message.result.channel === 'book' ? message.result.data[0].asks : message.result.data[0].update.asks) || []
67
+
68
+ yield {
69
+ type: 'book_change',
70
+ symbol: message.result.instrument_name,
71
+ exchange: this._exchange,
72
+ isSnapshot: message.result.channel === 'book',
73
+ bids: bids.map(this._mapBookLevel),
74
+ asks: asks.map(this._mapBookLevel),
75
+ timestamp: new Date(message.result.data[0].t),
76
+ localTimestamp
77
+ } as const
78
+ }
79
+
80
+ private _mapBookLevel(level: [number | string, number | string]) {
81
+ return { price: Number(level[0]), amount: Number(level[1]) }
82
+ }
83
+ }
84
+
85
+ export class CryptoComBookTickerMapper implements Mapper<'crypto-com' | 'crypto-com-derivatives', BookTicker> {
86
+ constructor(protected readonly _exchange: Exchange) {}
87
+
88
+ canHandle(message: CryptoComTickerMessage) {
89
+ return message.result !== undefined && message.result.channel === 'ticker'
90
+ }
91
+
92
+ getFilters(symbols?: string[]) {
93
+ symbols = upperCaseSymbols(symbols)
94
+ return [
95
+ {
96
+ channel: 'ticker',
97
+ symbols
98
+ } as const
99
+ ]
100
+ }
101
+
102
+ *map(message: CryptoComTickerMessage, localTimestamp: Date) {
103
+ for (const item of message.result.data) {
104
+ const bookTicker: BookTicker = {
105
+ type: 'book_ticker',
106
+ symbol: message.result.instrument_name,
107
+ exchange: this._exchange,
108
+
109
+ askAmount: undefined,
110
+ askPrice: item.k !== undefined && item.k !== null ? Number(item.k) : undefined,
111
+ bidPrice: item.b !== undefined && item.b !== null ? Number(item.b) : undefined,
112
+ bidAmount: undefined,
113
+ timestamp: new Date(item.t),
114
+ localTimestamp: localTimestamp
115
+ }
116
+
117
+ yield bookTicker
118
+ }
119
+ }
120
+ }
121
+
122
+ export class CryptoComDerivativeTickerMapper implements Mapper<'crypto-com-derivatives', DerivativeTicker> {
123
+ private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper()
124
+ private readonly _indexPrices = new Map<string, number>()
125
+
126
+ constructor(protected readonly exchange: Exchange) {}
127
+
128
+ canHandle(message: CryptoComDerivativesTickerMessage | CryptoComIndexMessage | CryptoComMarkPriceMessage | CryptoComFundingMessage) {
129
+ if (message.result === undefined) {
130
+ return false
131
+ }
132
+
133
+ return (
134
+ message.result.channel === 'ticker' ||
135
+ message.result.channel === 'index' ||
136
+ message.result.channel === 'mark' ||
137
+ message.result.channel === 'funding'
138
+ )
139
+ }
140
+
141
+ getFilters(symbols?: string[]) {
142
+ symbols = upperCaseSymbols(symbols)
143
+
144
+ let indexes: string[] = []
145
+ if (symbols !== undefined) {
146
+ indexes = [...new Set(symbols.map((s) => `${s.split('-')[0]}-INDEX`))]
147
+ }
148
+ const filters = [
149
+ {
150
+ channel: 'ticker',
151
+ symbols
152
+ } as const,
153
+ {
154
+ channel: 'index',
155
+ symbols: indexes
156
+ } as const,
157
+ {
158
+ channel: 'mark',
159
+ symbols
160
+ } as const,
161
+ {
162
+ channel: 'funding',
163
+ symbols
164
+ } as const
165
+ ]
166
+
167
+ return filters
168
+ }
169
+
170
+ *map(
171
+ message: CryptoComDerivativesTickerMessage | CryptoComIndexMessage | CryptoComMarkPriceMessage | CryptoComFundingMessage,
172
+ localTimestamp: Date
173
+ ): IterableIterator<DerivativeTicker> {
174
+ if (message.result.channel === 'index') {
175
+ this._indexPrices.set(message.result.instrument_name.split('-')[0], Number(message.result.data[0].v))
176
+ return
177
+ }
178
+
179
+ const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(message.result.instrument_name, this.exchange)
180
+
181
+ const lastIndexPrice = this._indexPrices.get(message.result.instrument_name.split('-')[0])
182
+ if (lastIndexPrice !== undefined) {
183
+ pendingTickerInfo.updateIndexPrice(lastIndexPrice)
184
+ }
185
+
186
+ if (message.result.channel === 'ticker') {
187
+ if (message.result.data[0].a !== null && message.result.data[0].a !== undefined) {
188
+ pendingTickerInfo.updateLastPrice(Number(message.result.data[0].a))
189
+ }
190
+ if (message.result.data[0].oi !== null && message.result.data[0].oi !== undefined) {
191
+ pendingTickerInfo.updateOpenInterest(Number(message.result.data[0].oi))
192
+ }
193
+ }
194
+
195
+ if (message.result.channel === 'mark') {
196
+ if (message.result.data[0].v !== null && message.result.data[0].v !== undefined) {
197
+ pendingTickerInfo.updateMarkPrice(Number(message.result.data[0].v))
198
+ }
199
+ }
200
+
201
+ if (message.result.channel === 'funding') {
202
+ if (message.result.data[0].v !== null && message.result.data[0].v !== undefined) {
203
+ pendingTickerInfo.updateFundingRate(Number(message.result.data[0].v))
204
+ const nextFundingTimestamp = new Date(message.result.data[0].t)
205
+ nextFundingTimestamp.setUTCHours(nextFundingTimestamp.getUTCHours() + 1)
206
+ nextFundingTimestamp.setUTCMinutes(0, 0, 0)
207
+ pendingTickerInfo.updateFundingTimestamp(nextFundingTimestamp)
208
+ }
209
+ }
210
+
211
+ pendingTickerInfo.updateTimestamp(new Date(message.result.data[0].t))
212
+
213
+ if (pendingTickerInfo.hasChanged()) {
214
+ yield pendingTickerInfo.getSnapshot(localTimestamp)
215
+ }
216
+ }
217
+ }
218
+
219
+ type CryptoComTradeMessage =
220
+ | {
221
+ method: 'subscribe'
222
+ result: {
223
+ instrument_name: 'ETH_CRO' // instrument_name
224
+ subscription: 'trade.ETH_CRO'
225
+ channel: 'trade'
226
+ data: [
227
+ {
228
+ p: 162.12 // price
229
+ q: 11.085 // quantity
230
+ s: 'BUY' // side
231
+ d: 1210447366 // trade id
232
+ t: 1587523078844 // trade time
233
+ dataTime: 0 // please ignore this field
234
+ }
235
+ ]
236
+ }
237
+ }
238
+ | {
239
+ id: -1
240
+ code: 0
241
+ method: 'subscribe'
242
+ result: {
243
+ channel: 'trade'
244
+ subscription: 'trade.BTCUSD-PERP'
245
+ instrument_name: 'BTCUSD-PERP'
246
+ data: [{ d: '4611686018439397540'; t: 1653992578435; p: '31603.5'; q: '0.1000'; s: 'BUY'; i: 'BTCUSD-PERP' }]
247
+ }
248
+ }
249
+
250
+ type CryptoComBookMessage =
251
+ | {
252
+ code: 0
253
+ method: 'subscribe'
254
+ result: {
255
+ instrument_name: 'ETH_CRO'
256
+ subscription: 'book.ETH_CRO.150'
257
+ channel: 'book'
258
+ depth: 150
259
+ data: [
260
+ {
261
+ bids: [number, number][]
262
+ asks: [number, number][]
263
+ t: 1659311999933
264
+ s: 788293808
265
+ }
266
+ ]
267
+ }
268
+ }
269
+ | {
270
+ code: 0
271
+ method: 'subscribe'
272
+ result: {
273
+ instrument_name: 'DOT_USDT'
274
+ subscription: 'book.DOT_USDT.150'
275
+ channel: 'book.update'
276
+ depth: 150
277
+ data: [
278
+ {
279
+ update: { bids: [number, number][]; asks: [number, number][] }
280
+ t: 1659312000046
281
+ s: 763793123
282
+ }
283
+ ]
284
+ }
285
+ }
286
+ | {
287
+ id: -1
288
+ code: 0
289
+ method: 'subscribe'
290
+ result: {
291
+ channel: 'book.update'
292
+ subscription: 'book.BTCUSD-PERP.50'
293
+ instrument_name: 'BTCUSD-PERP'
294
+ depth: 50
295
+ data: [
296
+ {
297
+ update: { asks: [string, string][]; bids: [string, string][] }
298
+ t: 1653992578436
299
+ tt: 1653992578428
300
+ u: 72560693920
301
+ pu: 72560688000
302
+ cs: 380529173
303
+ }
304
+ ]
305
+ }
306
+ }
307
+
308
+ type CryptoComTickerMessage =
309
+ | {
310
+ code: 0
311
+ method: 'subscribe'
312
+ result: {
313
+ instrument_name: 'GODS_USDT'
314
+ subscription: 'ticker.GODS_USDT'
315
+ channel: 'ticker'
316
+ data: [
317
+ {
318
+ i: 'GODS_USDT'
319
+ b: 0.4262
320
+ k: 0.4272
321
+ a: 0.4272
322
+ t: 1659311999946
323
+ v: 100623.01
324
+ vv: 42986.1541
325
+ h: 0.4624
326
+ l: 0.4229
327
+ c: -0.0062
328
+ pc: -1.4302
329
+ }
330
+ ]
331
+ }
332
+ }
333
+ | CryptoComDerivativesTickerMessage
334
+
335
+ type CryptoComDerivativesTickerMessage = {
336
+ id: -1
337
+ code: 0
338
+ method: 'subscribe'
339
+ result: {
340
+ channel: 'ticker'
341
+ instrument_name: 'BTCUSD-PERP'
342
+ subscription: 'ticker.BTCUSD-PERP'
343
+ data: [
344
+ {
345
+ h: '32222.5'
346
+ l: '30240.0'
347
+ a: '31611.0'
348
+ c: '0.0320'
349
+ b: '31613.0'
350
+ k: '31613.5'
351
+ i: 'BTCUSD-PERP'
352
+ v: '13206.4884'
353
+ vv: '433945264.39'
354
+ oi: '318.5162'
355
+ t: 1653992543383
356
+ }
357
+ ]
358
+ }
359
+ }
360
+
361
+ type CryptoComIndexMessage = {
362
+ id: -1
363
+ method: 'subscribe'
364
+ code: 0
365
+ result: {
366
+ instrument_name: 'BTCUSD-INDEX'
367
+ subscription: 'index.BTCUSD-INDEX'
368
+ channel: 'index'
369
+ data: [{ v: '31601.35'; t: 1653992545000 }]
370
+ }
371
+ }
372
+
373
+ type CryptoComMarkPriceMessage = {
374
+ id: 1
375
+ method: 'subscribe'
376
+ code: 0
377
+ result: {
378
+ instrument_name: 'BTCUSD-PERP'
379
+ subscription: 'mark.BTCUSD-PERP'
380
+ channel: 'mark'
381
+ data: [{ v: '31606.3'; t: 1653992543000 }]
382
+ }
383
+ }
384
+
385
+ type CryptoComFundingMessage = {
386
+ id: -1
387
+ method: 'subscribe'
388
+ code: 0
389
+ result: {
390
+ instrument_name: 'BTCUSD-PERP'
391
+ subscription: 'funding.BTCUSD-PERP'
392
+ channel: 'funding'
393
+ data: [{ v: '0.00000700'; t: 1653992579000 }]
394
+ }
395
+ }
@@ -31,6 +31,7 @@ import { BybitBookChangeMapper, BybitDerivativeTickerMapper, BybitLiquidationsMa
31
31
  import { BybitSpotBookChangeMapper, BybitSpotBookTickerMapper, BybitSpotTradesMapper } from './bybitspot'
32
32
  import { CoinbaseBookChangMapper, coinbaseBookTickerMapper, coinbaseTradesMapper } from './coinbase'
33
33
  import { coinflexBookChangeMapper, CoinflexDerivativeTickerMapper, coinflexTradesMapper } from './coinflex'
34
+ import { CryptoComBookChangeMapper, CryptoComBookTickerMapper, CryptoComDerivativeTickerMapper, CryptoComTradesMapper } from './cryptocom'
34
35
  import {
35
36
  cryptofacilitiesBookChangeMapper,
36
37
  CryptofacilitiesDerivativeTickerMapper,
@@ -168,7 +169,9 @@ const tradesMappers = {
168
169
  serum: () => new SerumTradesMapper('serum'),
169
170
  'star-atlas': () => new SerumTradesMapper('star-atlas'),
170
171
  mango: () => new SerumTradesMapper('mango'),
171
- 'bybit-spot': () => new BybitSpotTradesMapper('bybit-spot')
172
+ 'bybit-spot': () => new BybitSpotTradesMapper('bybit-spot'),
173
+ 'crypto-com': () => new CryptoComTradesMapper('crypto-com'),
174
+ 'crypto-com-derivatives': () => new CryptoComTradesMapper('crypto-com-derivatives')
172
175
  }
173
176
 
174
177
  const bookChangeMappers = {
@@ -236,7 +239,9 @@ const bookChangeMappers = {
236
239
  dydx: () => new DydxBookChangeMapper(),
237
240
  serum: () => new SerumBookChangeMapper('serum'),
238
241
  'star-atlas': () => new SerumBookChangeMapper('star-atlas'),
239
- mango: () => new SerumBookChangeMapper('mango')
242
+ mango: () => new SerumBookChangeMapper('mango'),
243
+ 'crypto-com': () => new CryptoComBookChangeMapper('crypto-com'),
244
+ 'crypto-com-derivatives': () => new CryptoComBookChangeMapper('crypto-com-derivatives')
240
245
  }
241
246
 
242
247
  const derivativeTickersMappers = {
@@ -264,7 +269,8 @@ const derivativeTickersMappers = {
264
269
  'gate-io-futures': () => new GateIOFuturesDerivativeTickerMapper(),
265
270
  coinflex: () => new CoinflexDerivativeTickerMapper(),
266
271
  ascendex: () => new AscendexDerivativeTickerMapper(),
267
- dydx: () => new DydxDerivativeTickerMapper()
272
+ dydx: () => new DydxDerivativeTickerMapper(),
273
+ 'crypto-com-derivatives': () => new CryptoComDerivativeTickerMapper('crypto-com-derivatives')
268
274
  }
269
275
 
270
276
  const optionsSummaryMappers = {
@@ -341,7 +347,9 @@ const bookTickersMappers = {
341
347
  'star-atlas': () => new SerumBookTickerMapper('star-atlas'),
342
348
  mango: () => new SerumBookTickerMapper('mango'),
343
349
  'gate-io-futures': () => new GateIOFuturesBookTickerMapper('gate-io-futures'),
344
- 'bybit-spot': () => new BybitSpotBookTickerMapper('bybit-spot')
350
+ 'bybit-spot': () => new BybitSpotBookTickerMapper('bybit-spot'),
351
+ 'crypto-com': () => new CryptoComBookTickerMapper('crypto-com'),
352
+ 'crypto-com-derivatives': () => new CryptoComBookTickerMapper('crypto-com-derivatives')
345
353
  }
346
354
 
347
355
  export const normalizeTrades = <T extends keyof typeof tradesMappers>(exchange: T, localTimestamp: Date): Mapper<T, Trade> => {
@@ -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
+ }
@@ -42,6 +42,8 @@ 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'
45
47
 
46
48
  export * from './realtimefeed'
47
49
 
@@ -91,7 +93,9 @@ const realTimeFeedsMap: {
91
93
  'star-atlas': StarAtlasRealTimeFeed,
92
94
  'huobi-dm-options': HuobiDMOptionsRealTimeFeed,
93
95
  mango: MangoRealTimeFeed,
94
- 'bybit-spot': BybitSpotRealTimeFeed
96
+ 'bybit-spot': BybitSpotRealTimeFeed,
97
+ 'crypto-com': CryptoComRealTimeFeed,
98
+ 'crypto-com-derivatives': CryptoComDerivativesRealTimeFeed
95
99
  }
96
100
 
97
101
  export function getRealTimeFeedFactory(exchange: Exchange): RealTimeFeed {