UNPKG

tardis-dev

Version:

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

266 lines (217 loc) 7.19 kB
import { asNumberIfValid, upperCaseSymbols } from '../handy' import { BookChange, OptionSummary, Trade } from '../types' import { Mapper } from './mapper' // https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md export class BinanceOptionsTradesMapper implements Mapper<'binance-options', Trade> { private readonly _lastTradeId = new Map<string, number>() canHandle(message: BinanceResponse<any>) { if (message.stream === undefined) { return false } return message.stream.endsWith('@TRADE') } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'TRADE', symbols } as const ] } *map(binanceTradeResponse: BinanceResponse<BinanceOptionsTradeData>, localTimestamp: Date) { const symbol = binanceTradeResponse.data.s for (const optionsTrade of binanceTradeResponse.data.t) { // binance options does not only return real-time trade as it happens but just snapshot of last 'x' trades always // so we need to decide which are real-time trades and which are stale/already processed const timestamp = new Date(optionsTrade.T) const tradeIdNumeric = Number(optionsTrade.t) const lastProcessedTradeId = this._lastTradeId.get(symbol) const isAlreadyProcessed = lastProcessedTradeId !== undefined && lastProcessedTradeId >= tradeIdNumeric const isStaleTrade = localTimestamp.valueOf() - timestamp.valueOf() > 10000 if (isAlreadyProcessed || isStaleTrade) { continue } this._lastTradeId.set(symbol, Number(optionsTrade.t)) const trade: Trade = { type: 'trade', symbol, exchange: 'binance-options', id: optionsTrade.t, price: Number(optionsTrade.p), amount: Number(optionsTrade.q), side: optionsTrade.s === '-1' ? 'sell' : 'buy', timestamp, localTimestamp: localTimestamp } yield trade } } } export class BinanceOptionsBookChangeMapper implements Mapper<'binance-options', BookChange> { canHandle(message: BinanceResponse<any>) { if (message.stream === undefined) { return false } return message.stream.endsWith('@DEPTH100') } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) return [ { channel: 'DEPTH100', symbols } as const ] } *map(message: BinanceResponse<BinanceOptionsDepthData>, localTimestamp: Date) { const bookChange: BookChange = { type: 'book_change', symbol: message.data.s, exchange: 'binance-options', isSnapshot: true, bids: message.data.b.map(this.mapBookLevel), asks: message.data.a.map(this.mapBookLevel), timestamp: new Date(message.data.E), localTimestamp } yield bookChange } protected mapBookLevel(level: BinanceBookLevel) { const price = Number(level[0]) const amount = Number(level[1]) return { price, amount } } } export class BinanceOptionSummaryMapper implements Mapper<'binance-options', OptionSummary> { private readonly _indexPrices = new Map<string, number>() canHandle(message: BinanceResponse<any>) { if (message.stream === undefined) { return false } return message.stream.endsWith('@TICKER') || message.stream.endsWith('@INDEX') } getFilters(symbols?: string[]) { symbols = upperCaseSymbols(symbols) const indexes = symbols !== undefined ? symbols.map((s) => { const symbolParts = s.split('-') return `${symbolParts[0]}USDT` }) : undefined return [ { channel: 'TICKER', symbols } as const, { channel: 'INDEX', symbols: indexes } as const ] } *map(message: BinanceResponse<BinanceOptionsTickerData | BinanceOptionsIndexData>, localTimestamp: Date) { if (message.stream.endsWith('@INDEX')) { const lastIndexPrice = Number(message.data.p) if (lastIndexPrice > 0) { this._indexPrices.set(message.data.s, lastIndexPrice) } return } const optionInfo = message.data as BinanceOptionsTickerData const [base, expiryPart, strikePrice, optionType] = optionInfo.s.split('-') const expirationDate = new Date(`20${expiryPart.slice(0, 2)}-${expiryPart.slice(2, 4)}-${expiryPart.slice(4, 6)}Z`) expirationDate.setUTCHours(8) const isPut = optionType === 'P' const underlyingIndex = `${base}USDT` let bestBidPrice = asNumberIfValid(optionInfo.bo) if (bestBidPrice === 0) { bestBidPrice = undefined } let bestAskPrice = asNumberIfValid(optionInfo.ao) if (bestAskPrice === 0) { bestAskPrice = undefined } let bestBidIV = bestBidPrice !== undefined ? asNumberIfValid(optionInfo.b) : undefined if (bestBidIV === -1) { bestBidIV = undefined } let bestAskIV = bestAskPrice !== undefined ? asNumberIfValid(optionInfo.a) : undefined if (bestAskIV === -1) { bestAskIV = undefined } const optionSummary: OptionSummary = { type: 'option_summary', symbol: optionInfo.s, exchange: 'binance-options', optionType: isPut ? 'put' : 'call', strikePrice: Number(strikePrice), expirationDate, bestBidPrice, bestBidAmount: undefined, bestBidIV, bestAskPrice, bestAskAmount: undefined, bestAskIV, lastPrice: asNumberIfValid(optionInfo.c), openInterest: undefined, markPrice: asNumberIfValid(optionInfo.mp), markIV: undefined, delta: asNumberIfValid(optionInfo.d), gamma: asNumberIfValid(optionInfo.g), vega: asNumberIfValid(optionInfo.v), theta: asNumberIfValid(optionInfo.t), rho: undefined, underlyingPrice: this._indexPrices.get(underlyingIndex), underlyingIndex, timestamp: new Date(optionInfo.E), localTimestamp: localTimestamp } yield optionSummary } } type BinanceResponse<T> = { stream: string data: T } type BinanceOptionsTradeData = { e: 'trade' E: number s: string t: { t: string; p: string; q: string; T: number; s: '-1' | '1' }[] } type BinanceOptionsDepthData = { e: 'depth' E: number s: string b: BinanceBookLevel[] a: BinanceBookLevel[] } type BinanceOptionsTickerData = { e: 'ticker' // event type E: 1591677962357 // event time s: 'BTC-200630-9000-P' // Option trading pair o: '1000' // 24-hour opening price h: '1000' // Highest price l: '1000' // Lowest price c: '1000' // latest price V: '2' // Trading volume A: '0' // trade amount p: '0' // price change Q: '2000' // volume of last completed trade F: 1 // first trade ID L: 1 // last trade ID n: 1 // number of trades b: '0' // BuyImplied volatility a: '0' // SellImplied volatility d: '0' // delta t: '0' // theta g: '0' // gamma v: '0' // vega, bo: '3.1' // best bid price ao: '90.36' // best ask price mp: '6.96' // mark price } type BinanceOptionsIndexData = { e: 'index'; E: 1614556800182; s: 'BTCUSDT'; p: '45160.127864' } type BinanceBookLevel = [string, string]