UNPKG

tardis-dev

Version:

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

375 lines (327 loc) 12.3 kB
import { asNonZeroNumberOrUndefined, asNumberOrUndefined } from '../handy.ts' import { BookChange, BookPriceLevel, BookTicker, DerivativeTicker, OptionSummary, Trade } from '../types.ts' import { Mapper, PendingTickerInfoHelper } from './mapper.ts' export class BullishTradesMapper implements Mapper<'bullish', Trade> { canHandle(message: BullishMessage): message is BullishAnonymousTradeUpdateMessage { return message.dataType === 'V1TAAnonymousTradeUpdate' && message.type === 'update' } getFilters(symbols?: string[]) { return [ { channel: 'V1TAAnonymousTradeUpdate' as const, symbols } ] } *map(message: BullishAnonymousTradeUpdateMessage, localTimestamp: Date): IterableIterator<Trade> { for (const trade of message.data.trades) { yield { type: 'trade', symbol: trade.symbol, exchange: 'bullish', id: trade.tradeId, price: Number(trade.price), amount: Number(trade.quantity), side: this.mapBullishTradeSide(trade.side, trade.isTaker), timestamp: new Date(trade.createdAtDatetime), localTimestamp } } } private mapBullishTradeSide(side: BullishTradeSide, isTaker: boolean): 'buy' | 'sell' { if (isTaker) { // Bullish side already represents the taker/aggressor side. return side === 'BUY' ? 'buy' : 'sell' } // Bullish side represents the maker/resting order, so the taker was on the opposite side. return side === 'BUY' ? 'sell' : 'buy' } } export class BullishBookChangeMapper implements Mapper<'bullish', BookChange> { canHandle(message: BullishMessage): message is BullishLevel2Message { return message.dataType === 'V1TALevel2' } getFilters(symbols?: string[]) { return [ { channel: 'V1TALevel2' as const, symbols } ] } *map(message: BullishLevel2Message, localTimestamp: Date): IterableIterator<BookChange> { yield { type: 'book_change', symbol: message.data.symbol, exchange: 'bullish', isSnapshot: message.type === 'snapshot', bids: this.mapLevels(message.data.bids), asks: this.mapLevels(message.data.asks), timestamp: new Date(message.data.datetime), localTimestamp } } private mapLevels(levels: string[]): BookPriceLevel[] { const result = new Array<BookPriceLevel>(levels.length / 2) for (let index = 0, resultIndex = 0; index < levels.length; index += 2, resultIndex++) { result[resultIndex] = { price: Number(levels[index]), amount: Number(levels[index + 1]) } } return result } } export class BullishBookTickerMapper implements Mapper<'bullish', BookTicker> { canHandle(message: BullishMessage): message is BullishLevel1Message { return message.dataType === 'V1TALevel1' && (message.type === 'snapshot' || message.type === 'update') } getFilters(symbols?: string[]) { return [ { channel: 'V1TALevel1' as const, symbols } ] } *map(message: BullishLevel1Message, localTimestamp: Date): IterableIterator<BookTicker> { yield { type: 'book_ticker', symbol: message.data.symbol, exchange: 'bullish', bidPrice: asNonZeroNumberOrUndefined(message.data.bid[0]), bidAmount: asNonZeroNumberOrUndefined(message.data.bid[1]), askPrice: asNonZeroNumberOrUndefined(message.data.ask[0]), askAmount: asNonZeroNumberOrUndefined(message.data.ask[1]), timestamp: new Date(message.data.datetime), localTimestamp } } } export class BullishDerivativeTickerMapper implements Mapper<'bullish', DerivativeTicker> { private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper() private readonly indexPrices = new Map<string, { price: number; timestamp: Date }>() canHandle(message: BullishMessage): message is BullishDerivativeTickerMessage | BullishIndexPriceMessage { if (message.dataType === 'V1TAIndexPrice' && (message.type === 'snapshot' || message.type === 'update')) { return true } if (message.dataType === 'V1TATickerResponse' && (message.type === 'snapshot' || message.type === 'update')) { const tickerMessage = message as BullishTickerMessage return tickerMessage.data.symbol.endsWith('-PERP') || /-\d{8}$/.test(tickerMessage.data.symbol) } return false } getFilters(symbols?: string[]) { return [ { channel: 'V1TATickerResponse' as const, symbols }, { channel: 'V1TAIndexPrice' as const, symbols: symbols === undefined ? undefined : [...new Set(symbols.map((symbol) => symbol.split('-')[0]))] } ] } *map(message: BullishDerivativeTickerMessage | BullishIndexPriceMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> { if (message.dataType === 'V1TAIndexPrice') { const price = asNonZeroNumberOrUndefined(message.data.price) if (price !== undefined) { this.indexPrices.set(message.data.assetSymbol, { price, timestamp: new Date(message.data.updatedAtDatetime) }) } return } const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(message.data.symbol, 'bullish') const indexAsset = message.data.symbol.split('-')[0] const indexPrice = this.indexPrices.get(indexAsset) pendingTickerInfo.updateLastPrice(asNonZeroNumberOrUndefined(message.data.last)) pendingTickerInfo.updateMarkPrice(asNonZeroNumberOrUndefined(message.data.markPrice)) pendingTickerInfo.updateFundingRate(asNumberOrUndefined(message.data.fundingRate)) pendingTickerInfo.updateOpenInterest(asNumberOrUndefined(message.data.openInterest)) pendingTickerInfo.updateIndexPrice(indexPrice?.price) if (pendingTickerInfo.hasChanged()) { pendingTickerInfo.updateTimestamp(new Date(message.data.createdAtDatetime)) yield pendingTickerInfo.getSnapshot(localTimestamp) } } } export class BullishOptionSummaryMapper implements Mapper<'bullish', OptionSummary> { private readonly indexPrices = new Map<string, { price: number; timestamp: Date }>() canHandle(message: BullishMessage): message is BullishOptionTickerMessage | BullishIndexPriceMessage { if (message.dataType === 'V1TAIndexPrice' && (message.type === 'snapshot' || message.type === 'update')) { return true } if (message.dataType === 'V1TATickerResponse' && (message.type === 'snapshot' || message.type === 'update')) { const tickerMessage = message as BullishTickerMessage return tickerMessage.data.symbol.endsWith('-C') || tickerMessage.data.symbol.endsWith('-P') } return false } getFilters(symbols?: string[]) { return [ { channel: 'V1TATickerResponse' as const, symbols }, { channel: 'V1TAIndexPrice' as const, symbols: symbols === undefined ? undefined : [...new Set(symbols.map((symbol) => symbol.split('-')[0]))] } ] } *map(message: BullishOptionTickerMessage | BullishIndexPriceMessage, localTimestamp: Date): IterableIterator<OptionSummary> { if (message.dataType === 'V1TAIndexPrice') { const price = asNonZeroNumberOrUndefined(message.data.price) if (price !== undefined) { this.indexPrices.set(message.data.assetSymbol, { price, timestamp: new Date(message.data.updatedAtDatetime) }) } return } const [indexAsset, , dateText, strikePriceText, optionType] = message.data.symbol.split('-') const indexPrice = this.indexPrices.get(indexAsset) const expirationDate = new Date(`${dateText.slice(0, 4)}-${dateText.slice(4, 6)}-${dateText.slice(6, 8)}Z`) expirationDate.setUTCHours(8) yield { type: 'option_summary', symbol: message.data.symbol, exchange: 'bullish', optionType: optionType === 'P' ? 'put' : 'call', strikePrice: Number(strikePriceText), expirationDate, bestBidPrice: asNonZeroNumberOrUndefined(message.data.bestBid), bestBidAmount: asNonZeroNumberOrUndefined(message.data.bidVolume), bestBidIV: undefined, bestAskPrice: asNonZeroNumberOrUndefined(message.data.bestAsk), bestAskAmount: asNonZeroNumberOrUndefined(message.data.askVolume), bestAskIV: undefined, lastPrice: asNonZeroNumberOrUndefined(message.data.last), openInterest: asNumberOrUndefined(message.data.openInterest), markPrice: asNumberOrUndefined(message.data.markPrice), markIV: asNonZeroNumberOrUndefined(message.data.impliedVolatility), delta: asNumberOrUndefined(message.data.delta), gamma: asNumberOrUndefined(message.data.gamma), vega: asNumberOrUndefined(message.data.vega), theta: asNumberOrUndefined(message.data.theta), rho: undefined, underlyingPrice: indexPrice?.price, underlyingIndex: indexAsset, timestamp: new Date(message.data.createdAtDatetime), localTimestamp } } } type BullishMessage = BullishDataMessage<string, unknown> type BullishDataMessage<TDataType extends string, TData> = { type: BullishMessageRole dataType: TDataType data: TData } type BullishMessageRole = 'snapshot' | 'update' type BullishAnonymousTradeUpdateMessage = BullishDataMessage<'V1TAAnonymousTradeUpdate', BullishAnonymousTradeUpdateData> type BullishLevel2Message = BullishDataMessage<'V1TALevel2', BullishLevel2Data> type BullishLevel1Message = BullishDataMessage<'V1TALevel1', BullishLevel1Data> type BullishTickerMessage = BullishDataMessage<'V1TATickerResponse', BullishTickerData> type BullishDerivativeTickerMessage = BullishDataMessage<'V1TATickerResponse', BullishDerivativeTickerData> type BullishOptionTickerMessage = BullishDataMessage<'V1TATickerResponse', BullishOptionTickerData> type BullishIndexPriceMessage = BullishDataMessage<'V1TAIndexPrice', BullishIndexPriceData> type BullishAnonymousTradeUpdateData = { symbol: string createdAtTimestamp: string publishedAtTimestamp: string trades: BullishAnonymousTrade[] } type BullishAnonymousTrade = { symbol: string tradeId: string price: string quantity: string side: BullishTradeSide isTaker: boolean createdAtTimestamp: string publishedAtTimestamp: string lastUpdatedTimestamp: string createdAtDatetime: string } type BullishTradeSide = 'BUY' | 'SELL' type BullishLevel2Data = { timestamp: string bids: string[] asks: string[] publishedAtTimestamp: string datetime: string sequenceNumberRange: [number, number] symbol: string } type BullishLevel1Data = { timestamp: string bid: [string, string] ask: [string, string] publishedAtTimestamp: string datetime: string sequenceNumber: string symbol: string } type BullishIndexPriceData = { price: string assetSymbol: string updatedAtDatetime: string updatedAtTimestamp: string } type BullishTickerData = BullishSpotTickerData | BullishDerivativeTickerData | BullishOptionTickerData type BullishSpotTickerData = BullishTickerDataBase type BullishDerivativeTickerData = BullishTickerDataBase & { markPrice: string | null fundingRate?: string | null openInterest: string | null openInterestUSD: string | null } type BullishOptionTickerData = BullishTickerDataBase & { markPrice: string | null openInterest: string | null openInterestUSD: string | null delta: string | null gamma: string | null theta: string | null vega: string | null impliedVolatility: string | null } type BullishTickerDataBase = { askVolume: string | null average: string | null baseVolume: string bestAsk?: string | null bestBid?: string | null bidVolume?: string | null change: string close: string | null createdAtTimestamp: string publishedAtTimestamp: string high: string | null last: string | null lastTradeDatetime: string | null lastTradeSize: string low: string | null open: string | null percentage: string quoteVolume: string symbol: string type: 'ticker' vwap: string | null currentPrice: string | null ammData: BullishTickerAmmData[] | null createdAtDatetime: string otcBaseVolume: string } type BullishTickerAmmData = { feeTierId: string tierPrice: string currentPrice: string bidSpreadFee: string askSpreadFee: string }