UNPKG

@hackape/tardis-dev

Version:

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

194 lines (159 loc) 5.78 kB
import got from 'got' import { Writable } from 'stream' import { batch } from '../handy' import { Filter } from '../types' import { MultiConnectionRealTimeFeedBase, PoolingClientBase, RealTimeFeedBase } from './realtimefeed' abstract class BinanceRealTimeFeedBase extends MultiConnectionRealTimeFeedBase { protected abstract wssURL: string protected abstract httpURL: string protected abstract suffixes: { [key: string]: string } protected *_getRealTimeFeeds(exchange: string, filters: Filter<string>[], timeoutIntervalMS?: number, onError?: (error: Error) => void) { const wsFilters = filters.filter((f) => f.channel !== 'openInterest' && f.channel !== 'recentTrades') if (wsFilters.length > 0) { yield new BinanceSingleConnectionRealTimeFeed( exchange, wsFilters, this.wssURL, this.httpURL, this.suffixes, timeoutIntervalMS, onError ) } const openInterestFilters = filters.filter((f) => f.channel === 'openInterest') if (openInterestFilters.length > 0) { const instruments = openInterestFilters.flatMap((s) => s.symbols!) yield new BinanceFuturesOpenInterestClient(exchange, this.httpURL, instruments) } } } class BinanceFuturesOpenInterestClient extends PoolingClientBase { constructor(exchange: string, private readonly _httpURL: string, private readonly _instruments: string[]) { super(exchange, 30) } protected async poolDataToStream(outputStream: Writable) { for (const instruments of batch(this._instruments, 10)) { await Promise.all( instruments.map(async (instrument) => { if (outputStream.destroyed) { return } const openInterestResponse = (await got .get(`${this._httpURL}/openInterest?symbol=${instrument.toUpperCase()}`, { timeout: 2000 }) .json()) as any const openInterestMessage = { stream: `${instrument.toLocaleLowerCase()}@openInterest`, generated: true, data: openInterestResponse } if (outputStream.writable) { outputStream.write(openInterestMessage) } }) ) } } } class BinanceSingleConnectionRealTimeFeed extends RealTimeFeedBase { constructor( exchange: string, filters: Filter<string>[], protected wssURL: string, private readonly _httpURL: string, private readonly _suffixes: { [key: string]: string }, timeoutIntervalMS: number | undefined, onError?: (error: Error) => void ) { super(exchange, filters, timeoutIntervalMS, onError) } protected mapToSubscribeMessages(filters: Filter<string>[]): any[] { const payload = filters .filter((f) => f.channel !== 'depthSnapshot') .map((filter, index) => { if (!filter.symbols || filter.symbols.length === 0) { throw new Error('BinanceRealTimeFeed requires explicitly specified symbols when subscribing to live feed') } const suffix = this._suffixes[filter.channel] const channel = suffix !== undefined ? `${filter.channel}@${suffix}` : filter.channel return { method: 'SUBSCRIBE', params: filter.symbols.map((symbol) => `${symbol}@${channel}`), id: index + 1 } }) return payload } protected messageIsError(message: any): boolean { // subscription confirmation message if (message.result === null) { return false } if (message.stream === undefined) { return true } if (message.error !== undefined) { return true } return false } protected async provideManualSnapshots(filters: Filter<string>[], shouldCancel: () => boolean) { const depthSnapshotFilter = filters.find((f) => f.channel === 'depthSnapshot') if (!depthSnapshotFilter) { return } this.debug('requesting manual snapshots for: %s', depthSnapshotFilter.symbols) for (let symbol of depthSnapshotFilter.symbols!) { if (shouldCancel()) { return } const depthSnapshotResponse = (await got .get(`${this._httpURL}/depth?symbol=${symbol.toUpperCase()}&limit=1000`, { timeout: 2000 }) .json()) as any const snapshot = { stream: `${symbol}@depthSnapshot`, generated: true, data: depthSnapshotResponse } this.manualSnapshotsBuffer.push(snapshot) } this.debug('requested manual snapshots successfully for: %s ', depthSnapshotFilter.symbols) } } export class BinanceRealTimeFeed extends BinanceRealTimeFeedBase { protected wssURL = 'wss://stream.binance.com:9443/stream' protected httpURL = 'https://api.binance.com/api/v1' protected suffixes = { depth: '100ms' } } export class BinanceJerseyRealTimeFeed extends BinanceRealTimeFeedBase { protected wssURL = 'wss://stream.binance.je:9443/stream' protected httpURL = 'https://api.binance.je/api/v1' protected suffixes = { depth: '100ms' } } export class BinanceUSRealTimeFeed extends BinanceRealTimeFeedBase { protected wssURL = 'wss://stream.binance.us:9443/stream' protected httpURL = 'https://api.binance.us/api/v1' protected suffixes = { depth: '100ms' } } export class BinanceFuturesRealTimeFeed extends BinanceRealTimeFeedBase { protected wssURL = 'wss://fstream3.binance.com/stream' protected httpURL = 'https://fapi.binance.com/fapi/v1' protected suffixes = { depth: '0ms', markPrice: '1s' } } export class BinanceDeliveryRealTimeFeed extends BinanceRealTimeFeedBase { protected wssURL = 'wss://dstream.binance.com/stream' protected httpURL = 'https://dapi.binance.com/dapi/v1' protected suffixes = { depth: '0ms', markPrice: '1s', indexPrice: '1s' } }