UNPKG

tardis-dev

Version:

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

495 lines (450 loc) 13.2 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: item.ks !== undefined && item.ks !== null ? Number(item.ks) : undefined, askPrice: item.k !== undefined && item.k !== null ? Number(item.k) : undefined, bidPrice: item.b !== undefined && item.b !== null ? Number(item.b) : undefined, bidAmount: item.bs !== undefined && item.bs !== null ? Number(item.bs) : 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 bs?: 0.1 k: 0.4272 ks?: 0.2 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' bs?: '0.1000' k: '31613.5' ks?: '0.2000' i: 'BTCUSD-PERP' v: '13206.4884' vv: '433945264.39' oi: '318.5162' t: 1653992543383 } ] } } | { id: 2 method: 'subscribe' code: 0 result: { instrument_name: 'ESUSD-PERP' subscription: 'ticker.ESUSD-PERP' channel: 'ticker' data: [ { h: '0.09625' l: '0.09230' a: '0.09481' c: '-0.0038' b: '0.09451' bs: '1' k: '0.09452' ks: '8461' i: 'ESUSD-PERP' v: '115' vv: '10.84' oi: '78522' t: 1765238404604 } ] } } | { id: -1 code: 0 method: 'subscribe' result: { channel: 'ticker' instrument_name: 'MATIC_USD' subscription: 'ticker.MATIC_USD' id: 1 data: [ { h: '1.24383' l: '1.18086' a: '1.19604' c: '-0.0315' b: '1.19591' bs: '0.1' k: '1.19643' ks: '0.7' i: 'MATIC_USD' v: '854908.9' vv: '1043976.96' oi: '0' t: 1677628802241 } ] } } 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 }] } }