UNPKG

tardis-dev

Version:

Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js

435 lines (390 loc) 11.7 kB
import { upperCaseSymbols } from '../handy' import { BookChange, Exchange, BookTicker, Trade, DerivativeTicker } from '../types' import { Mapper, PendingTickerInfoHelper } from './mapper' export class CryptoComTradesMapper implements Mapper<'crypto-com' | 'crypto-com-derivatives', Trade> { constructor(private readonly _exchange: Exchange) {} canHandle(message: CryptoComTradeMessage) { return message.result !== undefined && message.result.channel === 'trade' } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'trade', symbols } as const ] } *map(message: CryptoComTradeMessage, localTimestamp: Date): IterableIterator<Trade> { message.result.data.reverse() for (const item of message.result.data) { const trade: Trade = { type: 'trade', symbol: message.result.instrument_name, exchange: this._exchange, id: item.d.toString(), price: Number(item.p), amount: Number(item.q), side: item.s === 'BUY' ? 'buy' : 'sell', timestamp: new Date(item.t), localTimestamp } yield trade } } } export class CryptoComBookChangeMapper implements Mapper<'crypto-com' | 'crypto-com-derivatives', BookChange> { constructor(protected readonly _exchange: Exchange) {} canHandle(message: CryptoComBookMessage) { return message.result !== undefined && message.result.channel.startsWith('book') } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'book', symbols } as const ] } *map(message: CryptoComBookMessage, localTimestamp: Date) { if (message.result.data === undefined || message.result.data[0] === undefined) { return } const bids = (message.result.channel === 'book' ? message.result.data[0].bids : message.result.data[0].update.bids) || [] const asks = (message.result.channel === 'book' ? message.result.data[0].asks : message.result.data[0].update.asks) || [] yield { type: 'book_change', symbol: message.result.instrument_name, exchange: this._exchange, isSnapshot: message.result.channel === 'book', bids: bids.map(this._mapBookLevel), asks: asks.map(this._mapBookLevel), timestamp: new Date(message.result.data[0].t), localTimestamp } as const } private _mapBookLevel(level: [number | string, number | string]) { return { price: Number(level[0]), amount: Number(level[1]) } } } export class CryptoComBookTickerMapper implements Mapper<'crypto-com' | 'crypto-com-derivatives', BookTicker> { constructor(protected readonly _exchange: Exchange) {} canHandle(message: CryptoComTickerMessage) { return message.result !== undefined && message.result.channel === 'ticker' } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'ticker', symbols } as const ] } *map(message: CryptoComTickerMessage, localTimestamp: Date) { for (const item of message.result.data) { const bookTicker: BookTicker = { type: 'book_ticker', symbol: message.result.instrument_name, exchange: this._exchange, askAmount: undefined, askPrice: item.k !== undefined && item.k !== null ? Number(item.k) : undefined, bidPrice: item.b !== undefined && item.b !== null ? Number(item.b) : undefined, bidAmount: undefined, timestamp: new Date(item.t), localTimestamp: localTimestamp } yield bookTicker } } } export class CryptoComDerivativeTickerMapper implements Mapper<'crypto-com-derivatives', DerivativeTicker> { private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper() private readonly _indexPrices = new Map<string, number>() constructor(protected readonly exchange: Exchange) {} canHandle(message: CryptoComDerivativesTickerMessage | CryptoComIndexMessage | CryptoComMarkPriceMessage | CryptoComFundingMessage) { if (message.result === undefined) { return false } if (message.result.instrument_name === undefined) { return false } // spot symbols if (message.result.instrument_name.includes('_')) { return false } // options if (message.result.instrument_name.split('-').length === 3) { return false } return ( message.result.channel === 'ticker' || message.result.channel === 'index' || message.result.channel === 'mark' || message.result.channel === 'funding' ) } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) let indexes: string[] = [] if (symbols !== undefined) { indexes = [...new Set(symbols.map((s) => `${s.split('-')[0]}-INDEX`))] } const filters = [ { channel: 'ticker', symbols } as const, { channel: 'index', symbols: indexes } as const, { channel: 'mark', symbols } as const, { channel: 'funding', symbols } as const, { channel: 'estimatedfunding', symbols } as const ] return filters } *map( message: | CryptoComDerivativesTickerMessage | CryptoComIndexMessage | CryptoComMarkPriceMessage | CryptoComFundingMessage | CryptoComEstFundingMessage, localTimestamp: Date ): IterableIterator<DerivativeTicker> { if (message.result.channel === 'index') { this._indexPrices.set(message.result.instrument_name.split('-')[0], Number(message.result.data[0].v)) return } const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(message.result.instrument_name, this.exchange) const lastIndexPrice = this._indexPrices.get(message.result.instrument_name.split('-')[0]) if (lastIndexPrice !== undefined) { pendingTickerInfo.updateIndexPrice(lastIndexPrice) } if (message.result.channel === 'ticker') { if (message.result.data[0].a !== null && message.result.data[0].a !== undefined) { pendingTickerInfo.updateLastPrice(Number(message.result.data[0].a)) } if (message.result.data[0].oi !== null && message.result.data[0].oi !== undefined) { pendingTickerInfo.updateOpenInterest(Number(message.result.data[0].oi)) } } if (message.result.channel === 'mark') { if (message.result.data[0].v !== null && message.result.data[0].v !== undefined) { pendingTickerInfo.updateMarkPrice(Number(message.result.data[0].v)) } } if (message.result.channel === 'funding') { if (message.result.data[0].v !== null && message.result.data[0].v !== undefined) { pendingTickerInfo.updateFundingRate(Number(message.result.data[0].v)) const nextFundingTimestamp = new Date(message.result.data[0].t) nextFundingTimestamp.setUTCHours(nextFundingTimestamp.getUTCHours() + 1) nextFundingTimestamp.setUTCMinutes(0, 0, 0) pendingTickerInfo.updateFundingTimestamp(nextFundingTimestamp) } } if (message.result.channel === 'estimatedfunding') { if (message.result.data[0] && message.result.data[0].v !== null && message.result.data[0].v !== undefined) { pendingTickerInfo.updatePredictedFundingRate(Number(message.result.data[0].v)) } } pendingTickerInfo.updateTimestamp(new Date(message.result.data[0].t)) if (pendingTickerInfo.hasChanged()) { yield pendingTickerInfo.getSnapshot(localTimestamp) } } } type CryptoComTradeMessage = | { method: 'subscribe' result: { instrument_name: 'ETH_CRO' // instrument_name subscription: 'trade.ETH_CRO' channel: 'trade' data: [ { p: 162.12 // price q: 11.085 // quantity s: 'BUY' // side d: 1210447366 // trade id t: 1587523078844 // trade time dataTime: 0 // please ignore this field } ] } } | { id: -1 code: 0 method: 'subscribe' result: { channel: 'trade' subscription: 'trade.BTCUSD-PERP' instrument_name: 'BTCUSD-PERP' data: [{ d: '4611686018439397540'; t: 1653992578435; p: '31603.5'; q: '0.1000'; s: 'BUY'; i: 'BTCUSD-PERP' }] } } type CryptoComBookMessage = | { code: 0 method: 'subscribe' result: { instrument_name: 'ETH_CRO' subscription: 'book.ETH_CRO.150' channel: 'book' depth: 150 data: [ { bids: [number, number][] asks: [number, number][] t: 1659311999933 s: 788293808 } ] } } | { code: 0 method: 'subscribe' result: { instrument_name: 'DOT_USDT' subscription: 'book.DOT_USDT.150' channel: 'book.update' depth: 150 data: [ { update: { bids: [number, number][]; asks: [number, number][] } t: 1659312000046 s: 763793123 } ] } } | { id: -1 code: 0 method: 'subscribe' result: { channel: 'book.update' subscription: 'book.BTCUSD-PERP.50' instrument_name: 'BTCUSD-PERP' depth: 50 data: [ { update: { asks: [string, string][]; bids: [string, string][] } t: 1653992578436 tt: 1653992578428 u: 72560693920 pu: 72560688000 cs: 380529173 } ] } } type CryptoComTickerMessage = | { code: 0 method: 'subscribe' result: { instrument_name: 'GODS_USDT' subscription: 'ticker.GODS_USDT' channel: 'ticker' data: [ { i: 'GODS_USDT' b: 0.4262 k: 0.4272 a: 0.4272 t: 1659311999946 v: 100623.01 vv: 42986.1541 h: 0.4624 l: 0.4229 c: -0.0062 pc: -1.4302 } ] } } | CryptoComDerivativesTickerMessage type CryptoComDerivativesTickerMessage = { id: -1 code: 0 method: 'subscribe' result: { channel: 'ticker' instrument_name: 'BTCUSD-PERP' subscription: 'ticker.BTCUSD-PERP' data: [ { h: '32222.5' l: '30240.0' a: '31611.0' c: '0.0320' b: '31613.0' k: '31613.5' i: 'BTCUSD-PERP' v: '13206.4884' vv: '433945264.39' oi: '318.5162' t: 1653992543383 } ] } } type CryptoComIndexMessage = { id: -1 method: 'subscribe' code: 0 result: { instrument_name: 'BTCUSD-INDEX' subscription: 'index.BTCUSD-INDEX' channel: 'index' data: [{ v: '31601.35'; t: 1653992545000 }] } } type CryptoComMarkPriceMessage = { id: 1 method: 'subscribe' code: 0 result: { instrument_name: 'BTCUSD-PERP' subscription: 'mark.BTCUSD-PERP' channel: 'mark' data: [{ v: '31606.3'; t: 1653992543000 }] } } type CryptoComFundingMessage = { id: -1 method: 'subscribe' code: 0 result: { instrument_name: 'BTCUSD-PERP' subscription: 'funding.BTCUSD-PERP' channel: 'funding' data: [{ v: '0.00000700'; t: 1653992579000 }] } } type CryptoComEstFundingMessage = { id: 1 method: 'subscribe' code: 0 result: { instrument_name: 'AAVEUSD-PERP' subscription: 'estimatedfunding.AAVEUSD-PERP' channel: 'estimatedfunding' data: [{ v: '0.000039493'; t: 1727308799000 }] } }