UNPKG

tardis-dev

Version:

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

340 lines (304 loc) 9.71 kB
import { upperCaseSymbols } from '../handy' import { BookChange, DerivativeTicker, Liquidation, Trade } from '../types' import { Mapper, PendingTickerInfoHelper } from './mapper' export class DydxV4TradesMapper implements Mapper<'dydx-v4', Trade> { canHandle(message: DyDxTrade) { return message.channel === 'v4_trades' && message.type === 'channel_data' } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'v4_trades', symbols } as const ] } *map(message: DyDxTrade, localTimestamp: Date): IterableIterator<Trade> { for (let trade of message.contents.trades) { yield { type: 'trade', symbol: message.id, exchange: 'dydx-v4', id: trade.id, price: Number(trade.price), amount: Number(trade.size), side: trade.side === 'SELL' ? 'sell' : 'buy', timestamp: trade.createdAt ? new Date(trade.createdAt) : localTimestamp, localTimestamp: localTimestamp } } } } function mapSnapshotPriceLevel(level: { price: string; size: string }) { return { price: Number(level.price), amount: Number(level.size) } } function mapUpdatePriceLevel(level: [string, string]) { return { price: Number(level[0]), amount: Number(level[1]) } } export class DydxV4BookChangeMapper implements Mapper<'dydx-v4', BookChange> { canHandle(message: DyDxOrderbookSnapshot | DyDxOrderBookUpdate) { return message.channel === 'v4_orderbook' } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'v4_orderbook', symbols } as const ] } *map(message: DyDxOrderbookSnapshot | DyDxOrderBookUpdate, localTimestamp: Date): IterableIterator<BookChange> { if (message.type === 'subscribed') { yield { type: 'book_change', symbol: message.id, exchange: 'dydx-v4', isSnapshot: true, bids: message.contents.bids.map(mapSnapshotPriceLevel), asks: message.contents.asks.map(mapSnapshotPriceLevel), timestamp: localTimestamp, localTimestamp } } else { if (!message.contents) { return } const bookChange: BookChange = { type: 'book_change', symbol: message.id, exchange: 'dydx-v4', isSnapshot: false, bids: message.contents.bids !== undefined ? message.contents.bids.map(mapUpdatePriceLevel) : [], asks: message.contents.asks !== undefined ? message.contents.asks.map(mapUpdatePriceLevel) : [], timestamp: localTimestamp, localTimestamp } if (bookChange.bids.length > 0 || bookChange.asks.length > 0) { yield bookChange } } } } export class DydxV4DerivativeTickerMapper implements Mapper<'dydx-v4', DerivativeTicker> { private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper() canHandle(message: DydxMarketsSnapshot | DyDxMarketsUpdate | DyDxTrade) { return message.channel === 'v4_markets' || (message.channel === 'v4_trades' && message.type === 'channel_data') } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'v4_markets', symbols: [] as string[] } as const, { channel: 'v4_trades', symbols } as const ] } *map(message: DydxMarketsSnapshot | DyDxMarketsUpdate | DyDxTrade, localTimestamp: Date): IterableIterator<DerivativeTicker> { if (message.channel === 'v4_trades') { const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(message.id, 'dydx-v4') pendingTickerInfo.updateLastPrice(Number(message.contents.trades[message.contents.trades.length - 1].price)) return } if (message.type === 'subscribed' || (message.type === 'channel_data' && message.contents.trading !== undefined)) { const contents = message.type === 'subscribed' ? message.contents.markets : message.contents.trading for (const key in contents) { const marketInfo = (contents as any)[key] as DydxMarketsSnapshotContent | DydxMarketTradeUpdate const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(key, 'dydx-v4') if (marketInfo.oraclePrice !== undefined) { pendingTickerInfo.updateMarkPrice(Number(marketInfo.oraclePrice)) } if (marketInfo.openInterest !== undefined) { pendingTickerInfo.updateOpenInterest(Number(marketInfo.openInterest)) } if (marketInfo.nextFundingRate !== undefined) { pendingTickerInfo.updateFundingRate(Number(marketInfo.nextFundingRate)) } pendingTickerInfo.updateTimestamp(localTimestamp) if (pendingTickerInfo.hasChanged()) { yield pendingTickerInfo.getSnapshot(localTimestamp) } } } if (message.type === 'channel_data' && message.contents.oraclePrices !== undefined) { for (const key in message.contents.oraclePrices) { const oraclePriceInfo = (message.contents.oraclePrices as any)[key] as OraclePriceInfo const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(key, 'dydx-v4') if (oraclePriceInfo.oraclePrice !== undefined) { pendingTickerInfo.updateMarkPrice(Number(oraclePriceInfo.oraclePrice)) } pendingTickerInfo.updateTimestamp(localTimestamp) if (pendingTickerInfo.hasChanged()) { yield pendingTickerInfo.getSnapshot(localTimestamp) } } } } } export class DydxV4LiquidationsMapper implements Mapper<'dydx-v4', Liquidation> { canHandle(message: DyDxTrade) { return message.channel === 'v4_trades' && message.type === 'channel_data' } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'v4_trades', symbols } as const ] } *map(message: DyDxTrade, localTimestamp: Date): IterableIterator<Liquidation> { for (let trade of message.contents.trades) { if (trade.type === 'LIQUIDATED') { yield { type: 'liquidation', symbol: message.id, exchange: 'dydx-v4', id: trade.id, price: Number(trade.price), amount: Number(trade.size), side: trade.side === 'SELL' ? 'sell' : 'buy', timestamp: trade.createdAt ? new Date(trade.createdAt) : localTimestamp, localTimestamp: localTimestamp } } } } } type DyDxTrade = { type: 'channel_data' connection_id: '3a2e4c0c-7579-4bf6-a570-e0979418bbe9' message_id: 15897 id: 'BTC-USD' channel: 'v4_trades' version: '2.1.0' contents: { trades: [ { id: '0165e6170000000200000002' size: '0.0001' price: '60392' side: 'BUY' | 'SELL' createdAt: '2024-08-23T00:00:57.627Z' type: 'LIMIT' | 'LIQUIDATED' } ] } } type DyDxOrderbookSnapshot = { type: 'subscribed' connection_id: '67838890-75de-4bf3-a638-d7bcdea5f245' message_id: 7 channel: 'v4_orderbook' id: 'GRT-USD' contents: { bids: [{ price: '0.1547'; size: '35520' }] asks: [{ price: '0.155'; size: '3220' }] } } type DyDxOrderBookUpdate = { type: 'channel_data' connection_id: '00908030-4a70-43aa-9263-8ccdf57b5d40' message_id: 10290 id: 'EOS-USD' channel: 'v4_orderbook' version: '1.0.0' contents: { bids: [['0.1003', '2017130']]; asks: undefined | [['0.1003', '2017130']] } } type DydxMarketsSnapshot = { type: 'subscribed' connection_id: '3a2e4c0c-7579-4bf6-a570-e0979418bbe9' message_id: 17 channel: 'v4_markets' contents: { markets: { [key: string]: DydxMarketsSnapshotContent } } } type DydxMarketsSnapshotContent = { clobPairId: '0' ticker: 'BTC-USD' status: 'ACTIVE' oraclePrice: '60387.51779' priceChange24H: '-782.58326' volume24H: '247515340.0835' trades24H: 73556 nextFundingRate: '0.00001351666666666667' initialMarginFraction: '0.05' maintenanceMarginFraction: '0.03' openInterest: '648.2389' atomicResolution: -10 quantumConversionExponent: -9 tickSize: '1' stepSize: '0.0001' stepBaseQuantums: 1000000 subticksPerTick: 100000 marketType: 'CROSS' openInterestLowerCap: '0' openInterestUpperCap: '0' baseOpenInterest: '648.4278' } type DyDxMarketsUpdate = | { type: 'channel_data' connection_id: '3a2e4c0c-7579-4bf6-a570-e0979418bbe9' message_id: 15871 channel: 'v4_markets' version: '1.0.0' contents: { oraclePrices: undefined trading: { 'ETH-USD': DydxMarketTradeUpdate } } } | { type: 'channel_data' connection_id: '3a2e4c0c-7579-4bf6-a570-e0979418bbe9' message_id: 50 channel: 'v4_markets' version: '1.0.0' contents: { trading: undefined oraclePrices: { 'ZERO-USD': OraclePriceInfo } } } type OraclePriceInfo = { oraclePrice: string; effectiveAt: string; effectiveAtHeight: string; marketId: number } type DydxMarketTradeUpdate = { id?: string clobPairId?: string ticker?: string marketId?: number oraclePrice: undefined baseAsset?: string quoteAsset?: string initialMarginFraction?: string maintenanceMarginFraction?: string basePositionSize?: string incrementalPositionSize?: string maxPositionSize?: string openInterest?: string quantumConversionExponent?: number atomicResolution?: number subticksPerTick?: number stepBaseQuantums?: number priceChange24H?: string volume24H?: string trades24H?: number nextFundingRate?: string }