tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
218 lines • 10.1 kB
JavaScript
;
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