UNPKG

tardis-dev

Version:

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

327 lines (279 loc) 10.1 kB
import { upperCaseSymbols } from '../handy' import { BookChange, BookTicker, DerivativeTicker, Exchange, FilterForExchange, Liquidation, Trade } from '../types' import { Mapper, PendingTickerInfoHelper } from './mapper' export const wooxTradesMapper: Mapper<'woo-x', Trade> = { canHandle(message: WooxTradeMessage) { return message.topic !== undefined && message.topic.endsWith('@trade') }, getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'trade', symbols } ] }, *map(message: WooxTradeMessage, localTimestamp: Date): IterableIterator<Trade> { const timestamp = new Date(message.ts) // any trade with source = 0 is mainly for informational purposes for our users, not actual trades printed on WOO X if (message.data.source === 0) { return } yield { type: 'trade', symbol: message.data.symbol, exchange: 'woo-x', id: undefined, price: message.data.price, amount: message.data.size, side: message.data.side === 'SELL' ? 'sell' : 'buy', timestamp, localTimestamp: localTimestamp } } } export class WooxBookChangeMapper implements Mapper<'woo-x', BookChange> { private readonly _symbolToDepthInfoMapping: { [key: string]: LocalDepthInfo } = {} canHandle(message: WooxOrderbookMessage | WooxOrderbookupdateMessage) { if ('id' in message) { return message.id.endsWith('@orderbook') } if ('topic' in message) { return message.topic.endsWith('@orderbookupdate') } return false } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'orderbook', symbols }, { channel: 'orderbookupdate', symbols } ] } *map(message: WooxOrderbookMessage | WooxOrderbookupdateMessage, localTimestamp: Date): IterableIterator<BookChange> { const symbol = message.data.symbol if (this._symbolToDepthInfoMapping[symbol] === undefined) { this._symbolToDepthInfoMapping[symbol] = { bufferedUpdates: [] } } const symbolDepthInfo = this._symbolToDepthInfoMapping[symbol] const snapshotAlreadyProcessed = symbolDepthInfo.snapshotProcessed // first check if received message is snapshot and process it as such if it is if ('id' in message && message.success) { yield { type: 'book_change', symbol, exchange: 'woo-x', isSnapshot: true, bids: message.data.bids.map(this._mapBookLevel), asks: message.data.asks.map(this._mapBookLevel), timestamp: new Date(message.data.ts), localTimestamp } // mark given symbol depth info that has snapshot processed symbolDepthInfo.lastUpdateTimestamp = message.data.ts symbolDepthInfo.snapshotProcessed = true // if there were any depth updates buffered, let's proccess those for (const update of symbolDepthInfo.bufferedUpdates) { const bookChange = this._mapBookDepthUpdate(update, localTimestamp, symbolDepthInfo, symbol) if (bookChange !== undefined) { yield bookChange } } // remove all buffered updates symbolDepthInfo.bufferedUpdates = [] } else if (snapshotAlreadyProcessed) { // snapshot was already processed let's map the message as normal book_change const bookChange = this._mapBookDepthUpdate(message as WooxOrderbookupdateMessage, localTimestamp, symbolDepthInfo, symbol) if (bookChange !== undefined) { yield bookChange } } else { // if snapshot hasn't been yet processed and we've got depthUpdate message, let's buffer it for later processing symbolDepthInfo.bufferedUpdates.push(message as WooxOrderbookupdateMessage) } } private _mapBookDepthUpdate( wooxBookUpdate: WooxOrderbookupdateMessage, localTimestamp: Date, depthInfo: LocalDepthInfo, symbol: string ): BookChange | undefined { if (wooxBookUpdate.data.prevTs < depthInfo.lastUpdateTimestamp!) { return } return { type: 'book_change', symbol, exchange: 'woo-x', isSnapshot: false, bids: wooxBookUpdate.data.bids.map(this._mapBookLevel), asks: wooxBookUpdate.data.asks.map(this._mapBookLevel), timestamp: new Date(wooxBookUpdate.ts), localTimestamp } } private _mapBookLevel(level: [number, number]) { const price = level[0] const amount = level[1] return { price, amount } } } export class WooxDerivativeTickerMapper implements Mapper<'woo-x', DerivativeTicker> { private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper() private readonly _indexPrices = new Map<string, number>() canHandle(message: WooxTradeMessage | WooxEstFundingRate | WooxMarkPrice | WooxIndexPrice | WooxOpenInterest) { if (message.topic === undefined) { return false } const symbol = (message.data && message.data.symbol) || '' const isPerp = symbol.startsWith('PERP_') return ( (message.topic.endsWith('@trade') && isPerp) || (message.topic.endsWith('@markprice') && isPerp) || (message.topic.endsWith('@estfundingrate') && isPerp) || message.topic.endsWith('@indexprice') || (message.topic.endsWith('@openinterest') && isPerp) ) } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) const spotSymbols = symbols !== undefined ? symbols.map((s) => s.replace('PERP_', 'SPOT_')) : [] return [ { channel: 'trade', symbols }, { channel: 'markprice', symbols }, { channel: 'estfundingrate', symbols }, { channel: 'openinterest', symbols }, { channel: 'indexprice', symbols: spotSymbols } ] } *map(message: any, localTimestamp: Date): IterableIterator<DerivativeTicker> { if (message.topic.endsWith('@indexprice')) { this._indexPrices.set(message.data.symbol.replace('SPOT_', 'PERP_'), message.data.price) } else { const symbol = message.data.symbol const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(symbol, 'woo-x') const lastIndexPrice = this._indexPrices.get(symbol) if (lastIndexPrice !== undefined) { pendingTickerInfo.updateIndexPrice(lastIndexPrice) } if (message.topic.endsWith('@markprice')) { pendingTickerInfo.updateMarkPrice(message.data.price) pendingTickerInfo.updateTimestamp(new Date(message.ts)) } if (message.topic.endsWith('@trade')) { pendingTickerInfo.updateLastPrice(message.data.price) pendingTickerInfo.updateTimestamp(new Date(message.ts)) } if (message.topic.endsWith('@estfundingrate')) { pendingTickerInfo.updateFundingRate(message.data.fundingRate) pendingTickerInfo.updateFundingTimestamp(new Date(message.data.fundingTs)) pendingTickerInfo.updateTimestamp(new Date(message.ts)) } if (message.topic.endsWith('@openinterest')) { pendingTickerInfo.updateOpenInterest(message.data.openInterest) pendingTickerInfo.updateTimestamp(new Date(message.ts)) } if (pendingTickerInfo.hasChanged()) { yield pendingTickerInfo.getSnapshot(localTimestamp) } } } } export class WooxBookTickerMapper implements Mapper<'woo-x', BookTicker> { canHandle(message: WooxTradeMessage) { return message.topic !== undefined && message.topic.endsWith('@bbo') } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'bbo', symbols } ] } *map(wooxBBOMessage: WooxBBOMessage, localTimestamp: Date) { const wooxBookTicker = wooxBBOMessage.data const ticker: BookTicker = { type: 'book_ticker', symbol: wooxBookTicker.symbol, exchange: 'woo-x', askAmount: wooxBookTicker.askSize !== undefined ? wooxBookTicker.askSize : undefined, askPrice: wooxBookTicker.ask !== undefined ? wooxBookTicker.ask : undefined, bidPrice: wooxBookTicker.bid !== undefined ? wooxBookTicker.bid : undefined, bidAmount: wooxBookTicker.bidSize !== undefined ? wooxBookTicker.bidSize : undefined, timestamp: new Date(wooxBBOMessage.ts), localTimestamp: localTimestamp } yield ticker } } type LocalDepthInfo = { bufferedUpdates: WooxOrderbookupdateMessage[] snapshotProcessed?: boolean lastUpdateTimestamp?: number } type WooxTradeMessage = { topic: 'PERP_GALA_USDT@trade' ts: 1674431999995 data: { symbol: 'PERP_GALA_USDT'; price: 0.048756; size: 4109; side: 'SELL'; source: 0 } } type WooxOrderbookupdateMessage = { topic: 'PERP_BTC_USDT@orderbookupdate' ts: 1674432000020 data: { symbol: 'PERP_BTC_USDT' prevTs: 1674432000030 asks: [[22712.7, 15.4675]] bids: [[22708.0, 4.503]] } } type WooxOrderbookMessage = { id: 'PERP_BTC_USDT@orderbook' event: 'request' success: true ts: 1674432000034 data: { symbol: 'PERP_BTC_USDT' ts: 1674432000020 asks: [[22712.7, 15.4675], [26772.1, 0.248]] bids: [[22708.0, 4.503], [18555.0, 0.002]] } } type WooxBBOMessage = { topic: 'SPOT_ETH_USDT@bbo' ts: 1674431999996 data: { symbol: 'SPOT_ETH_USDT'; ask: 1627.61; askSize: 38.3755; bid: 1627.26; bidSize: 20.424926 } } type WooxMarkPrice = { topic: 'PERP_BTC_USDT@markprice'; ts: 1674432000007; data: { symbol: 'PERP_BTC_USDT'; price: 22711.11 } } type WooxEstFundingRate = { topic: 'PERP_BTC_USDT@estfundingrate' ts: 1674432059002 data: { symbol: 'PERP_BTC_USDT'; fundingRate: 0.00000782; fundingTs: 1674435600005 } } type WooxIndexPrice = { topic: 'SPOT_BTC_USDT@indexprice'; ts: 1674432000024; data: { symbol: 'SPOT_BTC_USDT'; price: 22708.44 } } type WooxOpenInterest = { topic: 'PERP_BTC_USDT@openinterest'; ts: 1674432013624; data: { symbol: 'PERP_BTC_USDT'; openInterest: 83.2241 } }