UNPKG

tardis-dev

Version:

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

133 lines (110 loc) 4.99 kB
import { onlyUnique } from '../handy.ts' import { Filter } from '../types.ts' import { MultiConnectionRealTimeFeedBase, RealTimeFeedBase } from './realtimefeed.ts' export class BinanceEuropeanOptionsRealTimeFeed extends MultiConnectionRealTimeFeedBase { protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[], timeoutIntervalMS?: number, onError?: (error: Error) => void) { // V2 API uses two separate WebSocket endpoints: // 1. Public path: for optionTrade, depth20, bookTicker, optionTicker // 2. Market path: for optionIndexPrice, optionMarkPrice, optionOpenInterest const publicChannels = ['optionTrade', 'depth20', 'bookTicker', 'optionTicker'] const marketChannels = ['optionIndexPrice', 'optionMarkPrice', 'optionOpenInterest'] const publicFilters = filters.filter((f) => publicChannels.includes(f.channel)) const marketFilters = filters.filter((f) => marketChannels.includes(f.channel)) if (publicFilters.length > 0) { yield new BinanceEuropeanOptionsSingleFeed( 'wss://fstream.binance.com/public/stream', exchange, publicFilters, filters, // Pass all filters so we can look up optionTicker when processing optionOpenInterest timeoutIntervalMS, onError ) } if (marketFilters.length > 0) { yield new BinanceEuropeanOptionsSingleFeed( 'wss://fstream.binance.com/market/stream', exchange, marketFilters, filters, // Pass all filters so we can look up optionTicker when processing optionOpenInterest timeoutIntervalMS, onError ) } } } class BinanceEuropeanOptionsSingleFeed extends RealTimeFeedBase { constructor( protected wssURL: string, exchange: string, filters: Filter<string>[], private readonly _allFilters: Filter<string>[], timeoutIntervalMS: number | undefined, onError?: (error: Error) => void ) { super(exchange, filters, timeoutIntervalMS, onError) } protected mapToSubscribeMessages(filters: Filter<string>[]): any[] { const payload = filters.map((filter, index) => { if (!filter.symbols || filter.symbols.length === 0) { throw new Error('BinanceEuropeanOptionsRealTimeFeed requires explicitly specified symbols when subscribing to live feed') } return { method: 'SUBSCRIBE', params: filter.symbols .map((symbol) => { const lowerSymbol = symbol.toLowerCase() // Public path channels - use lowercase symbol directly if (filter.channel === 'optionTrade') { return [`${lowerSymbol}@${filter.channel}`] } if (filter.channel === 'depth20') { return [`${lowerSymbol}@${filter.channel}@100ms`] } if (filter.channel === 'bookTicker') { return [`${lowerSymbol}@${filter.channel}`] } if (filter.channel === 'optionTicker') { return [`${lowerSymbol}@${filter.channel}`] } // Market path channels - use lowercase underlying if (filter.channel === 'optionIndexPrice' || filter.channel === 'optionMarkPrice') { // Symbol is the underlying (e.g., 'btcusdt') return [`${lowerSymbol}@${filter.channel}`] } if (filter.channel === 'optionOpenInterest') { // Need to extract expirations from option symbols // The symbol here is the underlying (e.g., 'btcusdt') // We need to find all option symbols that match this underlying to extract expirations // Look for optionTicker filter in all filters to get actual option symbols const optionTickerFilter = this._allFilters.find((f) => f.channel === 'optionTicker') if (optionTickerFilter !== undefined) { // Extract expirations from option symbols that match this underlying const underlyingBase = lowerSymbol.replace('usdt', '').toUpperCase() const expirations = optionTickerFilter .symbols!.filter((s) => s.toUpperCase().startsWith(underlyingBase + '-')) .map((s) => { const symbolParts = s.split('-') return symbolParts[1] // Extract expiration (e.g., '251219') }) .filter(onlyUnique) .map((exp) => exp.toLowerCase()) return expirations.map((expiration) => { return `${lowerSymbol}@${filter.channel}@${expiration}` }) } } return [`${lowerSymbol}@${filter.channel}`] }) .flatMap((s) => s), id: index + 1 } }) return payload } protected messageIsError(message: any): boolean { if (message.data !== undefined && message.data.e === 'error') { return true } return false } }