UNPKG

tardis-dev

Version:

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

395 lines (333 loc) 11.1 kB
import { asNumberIfValid } from '../handy' import { BookChange, BookTicker, DerivativeTicker, Liquidation, OptionSummary, Trade } from '../types' import { Mapper, PendingTickerInfoHelper } from './mapper' // https://docs.deribit.com/v2/#subscriptions function deribitCasing(symbols?: string[]) { if (symbols !== undefined) { return symbols.map((symbol) => { if (symbol.endsWith('-C') || symbol.endsWith('-P')) { const parts = symbol.split('-') if (parts[2] !== undefined && parts[2].toUpperCase().includes('D')) { parts[2] = parts[2].replace('D', 'd') return parts.join('-') } else { return symbol.toUpperCase() } } else { return symbol.toUpperCase() } }) } return } export const deribitTradesMapper: Mapper<'deribit', Trade> = { canHandle(message: any) { const channel = message.params !== undefined ? (message.params.channel as string | undefined) : undefined if (channel === undefined) { return false } return channel.startsWith('trades') }, getFilters(symbols?: string[]) { symbols = deribitCasing(symbols) return [ { channel: 'trades', symbols } ] }, *map(message: DeribitTradesMessage, localTimestamp: Date): IterableIterator<Trade> { for (const deribitTrade of message.params.data) { yield { type: 'trade', symbol: deribitTrade.instrument_name.toUpperCase(), exchange: 'deribit', id: deribitTrade.trade_id, price: deribitTrade.price, amount: deribitTrade.amount, side: deribitTrade.direction, timestamp: new Date(deribitTrade.timestamp), localTimestamp: localTimestamp } } } } const mapBookLevel = (level: DeribitBookLevel) => { const price = level[1] const amount = level[0] === 'delete' ? 0 : level[2] return { price, amount } } export const deribitBookChangeMapper: Mapper<'deribit', BookChange> = { canHandle(message: any) { const channel = message.params && (message.params.channel as string | undefined) if (channel === undefined) { return false } return channel.startsWith('book') }, getFilters(symbols?: string[]) { symbols = deribitCasing(symbols) return [ { channel: 'book', symbols } ] }, *map(message: DeribitBookMessage, localTimestamp: Date): IterableIterator<BookChange> { const deribitBookChange = message.params.data // snapshots do not have prev_change_id set const isSnapshot = (deribitBookChange.type !== undefined && deribitBookChange.type === 'snapshot') || deribitBookChange.prev_change_id === undefined || deribitBookChange.prev_change_id === 0 yield { type: 'book_change', symbol: deribitBookChange.instrument_name.toUpperCase(), exchange: 'deribit', isSnapshot, bids: deribitBookChange.bids.map(mapBookLevel), asks: deribitBookChange.asks.map(mapBookLevel), timestamp: new Date(deribitBookChange.timestamp), localTimestamp: localTimestamp } } } export class DeribitDerivativeTickerMapper implements Mapper<'deribit', DerivativeTicker> { private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper() canHandle(message: any) { const channel = message.params && (message.params.channel as string | undefined) if (channel === undefined) { return false } return channel.startsWith('ticker') && (message.params.data.greeks === undefined || message.params.data.combo_state === 'active') } getFilters(symbols?: string[]) { symbols = deribitCasing(symbols) return [ { channel: 'ticker', symbols } as const ] } *map(message: DeribitTickerMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> { const deribitTicker = message.params.data const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(deribitTicker.instrument_name, 'deribit') pendingTickerInfo.updateFundingRate(deribitTicker.current_funding) pendingTickerInfo.updateIndexPrice(deribitTicker.index_price) pendingTickerInfo.updateMarkPrice(deribitTicker.mark_price) pendingTickerInfo.updateOpenInterest(deribitTicker.open_interest) pendingTickerInfo.updateLastPrice(deribitTicker.last_price) pendingTickerInfo.updateTimestamp(new Date(deribitTicker.timestamp)) if (pendingTickerInfo.hasChanged()) { yield pendingTickerInfo.getSnapshot(localTimestamp) } } } export class DeribitOptionSummaryMapper implements Mapper<'deribit', OptionSummary> { getFilters(symbols?: string[]) { symbols = deribitCasing(symbols) return [ { channel: 'ticker', symbols } as const ] } canHandle(message: any) { const channel = message.params && message.params.channel if (channel === undefined) { return false } return ( channel.startsWith('ticker') && (message.params.data.instrument_name.endsWith('-P') || message.params.data.instrument_name.endsWith('-C')) ) } *map(message: DeribitOptionTickerMessage, localTimestamp: Date) { //MATIC_USDC-9MAR24-1d02-C const optionInfo = message.params.data //e.g., BTC-8JUN20-8750-P const symbolParts = optionInfo.instrument_name.split('-') const isPut = symbolParts[3] === 'P' let strikePriceString = symbolParts[2] if (strikePriceString.includes('d')) { strikePriceString = strikePriceString.replace('d', '.') } const strikePrice = Number(strikePriceString) const expirationDate = new Date(symbolParts[1] + 'Z') expirationDate.setUTCHours(8) const optionSummary: OptionSummary = { type: 'option_summary', symbol: optionInfo.instrument_name.toUpperCase(), exchange: 'deribit', optionType: isPut ? 'put' : 'call', strikePrice, expirationDate, bestBidPrice: asNumberIfValid(optionInfo.best_bid_price), bestBidAmount: asNumberIfValid(optionInfo.best_bid_amount), bestBidIV: asNumberIfValid(optionInfo.bid_iv), bestAskPrice: asNumberIfValid(optionInfo.best_ask_price), bestAskAmount: asNumberIfValid(optionInfo.best_ask_amount), bestAskIV: asNumberIfValid(optionInfo.ask_iv), lastPrice: asNumberIfValid(optionInfo.last_price), openInterest: optionInfo.open_interest, markPrice: optionInfo.mark_price, markIV: optionInfo.mark_iv, delta: optionInfo.greeks.delta, gamma: optionInfo.greeks.gamma, vega: optionInfo.greeks.vega, theta: optionInfo.greeks.theta, rho: optionInfo.greeks.rho, underlyingPrice: optionInfo.underlying_price, underlyingIndex: optionInfo.underlying_index, timestamp: new Date(optionInfo.timestamp), localTimestamp: localTimestamp } yield optionSummary } } export const deribitLiquidationsMapper: Mapper<'deribit', Liquidation> = { canHandle(message: any) { const channel = message.params !== undefined ? (message.params.channel as string | undefined) : undefined if (channel === undefined) { return false } return channel.startsWith('trades') }, getFilters(symbols?: string[]) { symbols = deribitCasing(symbols) return [ { channel: 'trades', symbols } ] }, *map(message: DeribitTradesMessage, localTimestamp: Date): IterableIterator<Liquidation> { for (const deribitTrade of message.params.data) { if (deribitTrade.liquidation !== undefined) { let side // "T" when liquidity taker side was under liquidation if (deribitTrade.liquidation === 'T') { side = deribitTrade.direction } else { // "M" when maker (passive) side of trade was under liquidation side = deribitTrade.direction === 'buy' ? ('sell' as const) : ('buy' as const) } yield { type: 'liquidation', symbol: deribitTrade.instrument_name.toUpperCase(), exchange: 'deribit', id: deribitTrade.trade_id, price: deribitTrade.price, amount: deribitTrade.amount, side, timestamp: new Date(deribitTrade.timestamp), localTimestamp: localTimestamp } } } } } export const deribitBookTickerMapper: Mapper<'deribit', BookTicker> = { canHandle(message: any) { const channel = message.params !== undefined ? (message.params.channel as string | undefined) : undefined if (channel === undefined) { return false } return channel.startsWith('ticker') }, getFilters(symbols?: string[]) { symbols = deribitCasing(symbols) return [ { channel: 'ticker', symbols } ] }, *map(message: DeribitTickerMessage, localTimestamp: Date): IterableIterator<BookTicker> { const deribitTicker = message.params.data const ticker: BookTicker = { type: 'book_ticker', symbol: deribitTicker.instrument_name.toUpperCase(), exchange: 'deribit', askAmount: asNumberIfValid(deribitTicker.best_ask_amount), askPrice: asNumberIfValid(deribitTicker.best_ask_price), bidPrice: asNumberIfValid(deribitTicker.best_bid_price), bidAmount: asNumberIfValid(deribitTicker.best_bid_amount), timestamp: new Date(deribitTicker.timestamp), localTimestamp: localTimestamp } yield ticker } } type DeribitMessage = { params: { channel: string } } type DeribitTradesMessage = DeribitMessage & { params: { data: { trade_id: string instrument_name: string timestamp: number direction: 'buy' | 'sell' price: number amount: number trade_seq: number liquidation?: 'M' | 'T' | 'MT' }[] } } type DeribitBookLevel = ['new' | 'change' | 'delete', number, number] type DeribitBookMessage = DeribitMessage & { params: { data: { timestamp: number instrument_name: string prev_change_id?: number bids: DeribitBookLevel[] asks: DeribitBookLevel[] type?: 'snapshot' | 'change' } } } type DeribitTickerMessage = DeribitMessage & { params: { data: { timestamp: number open_interest: number last_price: number | undefined mark_price: number instrument_name: string index_price: number current_funding?: number funding_8h?: number best_bid_price: number | undefined best_bid_amount: number | undefined best_ask_price: number | undefined best_ask_amount: number | undefined } } } type DeribitOptionTickerMessage = DeribitTickerMessage & { params: { data: { underlying_price: number underlying_index: string timestamp: number open_interest: number mark_price: number mark_iv: number last_price: number | null greeks: { vega: number; theta: number; rho: number; gamma: number; delta: number } bid_iv: number | undefined ask_iv: number | undefined } } }