UNPKG

tardis-dev

Version:

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

218 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BinanceDeliveryRealTimeFeed = exports.BinanceFuturesRealTimeFeed = exports.BinanceUSRealTimeFeed = exports.BinanceJerseyRealTimeFeed = exports.BinanceRealTimeFeed = void 0; const handy_1 = require("../handy"); const realtimefeed_1 = require("./realtimefeed"); const binanceHttpOptions = { timeout: 10 * 1000, retry: { limit: 10, statusCodes: [418, 429, 500, 403], maxRetryAfter: 120 * 1000 } }; class BinanceRealTimeFeedBase extends realtimefeed_1.MultiConnectionRealTimeFeedBase { *_getRealTimeFeeds(exchange, filters, timeoutIntervalMS, onError) { 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, this.depthRequestRequestWeight, 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 realtimefeed_1.PoolingClientBase { constructor(exchange, _httpURL, _instruments) { super(exchange, 3); this._httpURL = _httpURL; this._instruments = _instruments; } async poolDataToStream(outputStream) { for (const instruments of (0, handy_1.batch)(this._instruments, 10)) { await Promise.allSettled(instruments.map(async (instrument) => { if (outputStream.destroyed) { return; } const openInterestResponse = (await handy_1.httpClient .get(`${this._httpURL}/openInterest?symbol=${instrument.toUpperCase()}`, binanceHttpOptions) .json()); const openInterestMessage = { stream: `${instrument.toLocaleLowerCase()}@openInterest`, generated: true, data: openInterestResponse }; if (outputStream.writable) { outputStream.write(openInterestMessage); } })); } } } class BinanceSingleConnectionRealTimeFeed extends realtimefeed_1.RealTimeFeedBase { constructor(exchange, filters, wssURL, _httpURL, _suffixes, _depthRequestRequestWeight, timeoutIntervalMS, onError) { super(exchange, filters, timeoutIntervalMS, onError); this.wssURL = wssURL; this._httpURL = _httpURL; this._suffixes = _suffixes; this._depthRequestRequestWeight = _depthRequestRequestWeight; } mapToSubscribeMessages(filters) { 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; } messageIsError(message) { // subscription confirmation message if (message.result === null) { return false; } if (message.stream === undefined) { return true; } if (message.error !== undefined) { return true; } return false; } async provideManualSnapshots(filters, shouldCancel) { const depthSnapshotFilter = filters.find((f) => f.channel === 'depthSnapshot'); if (!depthSnapshotFilter) { return; } let currentWeightLimit = 0; const exchangeInfoResponse = await handy_1.httpClient.get(`${this._httpURL}/exchangeInfo`, binanceHttpOptions); const exchangeInfo = JSON.parse(exchangeInfoResponse.body); const REQUEST_WEIGHT_LIMIT_ENV = `${this._exchange.toUpperCase().replace(/-/g, '_')}_REQUEST_WEIGHT_LIMIT`; const DELAY_ENV = `${this._exchange.toUpperCase().replace(/-/g, '_')}_SNAPSHOTS_DELAY_MS`; if (process.env[REQUEST_WEIGHT_LIMIT_ENV] !== undefined) { currentWeightLimit = Number.parseInt(process.env[REQUEST_WEIGHT_LIMIT_ENV]); } if (!currentWeightLimit) { currentWeightLimit = exchangeInfo.rateLimits.find((d) => d.rateLimitType === 'REQUEST_WEIGHT').limit; } let usedWeight = Number.parseInt(exchangeInfoResponse.headers['x-mbx-used-weight-1m']); this.debug('current x-mbx-used-weight-1m limit: %s, already used weight: %s', currentWeightLimit, usedWeight); let concurrencyLimit = 4; const CONCURRENCY_LIMIT_WEIGHT_ENV = `${this._exchange.toUpperCase().replace(/-/g, '_')}_CONCURRENCY_LIMIT`; if (process.env[CONCURRENCY_LIMIT_WEIGHT_ENV] !== undefined) { concurrencyLimit = Number.parseInt(process.env[CONCURRENCY_LIMIT_WEIGHT_ENV]); } this.debug('current snapshots requests concurrency limit: %s', concurrencyLimit); let minWeightBuffer = 2 * concurrencyLimit * this._depthRequestRequestWeight; const MIN_WEIGHT_BUFFER_ENV = `${this._exchange.toUpperCase().replace(/-/g, '_')}_MIN_AVAILABLE_WEIGHT_BUFFER`; if (process.env[MIN_WEIGHT_BUFFER_ENV] !== undefined) { minWeightBuffer = Number.parseInt(process.env[MIN_WEIGHT_BUFFER_ENV]); } for (const symbolsBatch of (0, handy_1.batch)(depthSnapshotFilter.symbols, concurrencyLimit)) { if (shouldCancel()) { return; } this.debug('requesting manual snapshots for: %s', symbolsBatch); const usedWeights = await Promise.all(symbolsBatch.map(async (symbol) => { if (shouldCancel()) { return 0; } const isOverRateLimit = currentWeightLimit - usedWeight < minWeightBuffer; if (isOverRateLimit) { const secondsToWait = 61 - new Date().getUTCSeconds(); this.debug('reached rate limit (x-mbx-used-weight-1m limit: %s, used weight: %s, minimum available weight buffer: %s), waiting: %s seconds', currentWeightLimit, usedWeight, minWeightBuffer, secondsToWait); await (0, handy_1.wait)(secondsToWait * 1000); } const depthSnapshotResponse = await handy_1.httpClient.get(`${this._httpURL}/depth?symbol=${symbol.toUpperCase()}&limit=1000`, binanceHttpOptions); const snapshot = { stream: `${symbol}@depthSnapshot`, generated: true, data: JSON.parse(depthSnapshotResponse.body) }; this.manualSnapshotsBuffer.push(snapshot); if (process.env[DELAY_ENV] !== undefined) { const msToWait = Number.parseInt(process.env[DELAY_ENV]); await (0, handy_1.wait)(msToWait); } return Number.parseInt(depthSnapshotResponse.headers['x-mbx-used-weight-1m']); })); usedWeight = Math.max(...usedWeights); this.debug('requested manual snapshots successfully for: %s, used weight: %s', symbolsBatch, usedWeight); } this.debug('requested all manual snapshots successfully'); } } class BinanceRealTimeFeed extends BinanceRealTimeFeedBase { constructor() { super(...arguments); this.wssURL = 'wss://stream.binance.com/stream?timeUnit=microsecond'; this.httpURL = 'https://api.binance.com/api/v1'; this.suffixes = { depth: '100ms' }; this.depthRequestRequestWeight = 10; } } exports.BinanceRealTimeFeed = BinanceRealTimeFeed; class BinanceJerseyRealTimeFeed extends BinanceRealTimeFeedBase { constructor() { super(...arguments); this.wssURL = 'wss://stream.binance.je:9443/stream'; this.httpURL = 'https://api.binance.je/api/v1'; this.suffixes = { depth: '100ms' }; this.depthRequestRequestWeight = 10; } } exports.BinanceJerseyRealTimeFeed = BinanceJerseyRealTimeFeed; class BinanceUSRealTimeFeed extends BinanceRealTimeFeedBase { constructor() { super(...arguments); this.wssURL = 'wss://stream.binance.us:9443/stream'; this.httpURL = 'https://api.binance.us/api/v1'; this.suffixes = { depth: '100ms' }; this.depthRequestRequestWeight = 10; } } exports.BinanceUSRealTimeFeed = BinanceUSRealTimeFeed; class BinanceFuturesRealTimeFeed extends BinanceRealTimeFeedBase { constructor() { super(...arguments); this.wssURL = 'wss://fstream.binance.com/stream'; this.httpURL = 'https://fapi.binance.com/fapi/v1'; this.suffixes = { depth: '0ms', markPrice: '1s' }; this.depthRequestRequestWeight = 20; } } exports.BinanceFuturesRealTimeFeed = BinanceFuturesRealTimeFeed; class BinanceDeliveryRealTimeFeed extends BinanceRealTimeFeedBase { constructor() { super(...arguments); this.wssURL = 'wss://dstream.binance.com/stream'; this.httpURL = 'https://dapi.binance.com/dapi/v1'; this.suffixes = { depth: '0ms', markPrice: '1s', indexPrice: '1s' }; this.depthRequestRequestWeight = 20; } } exports.BinanceDeliveryRealTimeFeed = BinanceDeliveryRealTimeFeed; //# sourceMappingURL=binance.js.map