tardis-dev
Version:
Convenient access to tick-level historical and real-time cryptocurrency market data via Node.js
344 lines • 14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HuobiDMOptionsRealTimeFeed = exports.HuobiDMLinearSwapRealTimeFeed = exports.HuobiDMSwapRealTimeFeed = exports.HuobiDMRealTimeFeed = exports.HuobiRealTimeFeed = void 0;
const zlib_1 = require("zlib");
const realtimefeed_1 = require("./realtimefeed");
const handy_1 = require("../handy");
class HuobiRealTimeFeedBase extends realtimefeed_1.MultiConnectionRealTimeFeedBase {
_marketDataChannels = ['depth', 'detail', 'trade', 'bbo'];
_notificationsChannels = ['funding_rate', 'liquidation_orders', 'contract_info'];
*_getRealTimeFeeds(exchange, filters, timeoutIntervalMS, onError) {
const marketByPriceFilters = filters.filter((f) => f.channel === 'mbp');
if (marketByPriceFilters.length > 0) {
// https://huobiapi.github.io/docs/spot/v1/en/#market-by-price-incremental-update
const marketByPriceWSUrl = this.wssURL.replace('/ws', '/feed');
yield new HuobiMarketDataRealTimeFeed(exchange, marketByPriceFilters, marketByPriceWSUrl, this.suffixes, timeoutIntervalMS, onError);
}
const basisFilters = filters.filter((f) => f.channel === 'basis');
if (basisFilters.length > 0) {
const basisWSURL = this.wssURL.replace('/ws', '/ws_index').replace('/swap-ws', '/ws_index').replace('/linear-swap-ws', '/ws_index');
yield new HuobiMarketDataRealTimeFeed(exchange, basisFilters, basisWSURL, this.suffixes, timeoutIntervalMS, onError);
}
const marketDataFilters = filters.filter((f) => this._marketDataChannels.includes(f.channel));
if (marketDataFilters.length > 0) {
yield new HuobiMarketDataRealTimeFeed(exchange, marketDataFilters, this.wssURL, this.suffixes, timeoutIntervalMS, onError);
}
const notificationsFilters = filters.filter((f) => this._notificationsChannels.includes(f.channel));
if (notificationsFilters.length > 0) {
const notificationsWSURL = this.wssURL
.replace('/swap-ws', '/swap-notification')
.replace('/ws', '/notification')
.replace('/linear-swap-ws', '/linear-swap-notification');
yield new HuobiNotificationsRealTimeFeed(exchange, notificationsFilters, notificationsWSURL, timeoutIntervalMS, onError);
}
const openInterestFilters = filters.filter((f) => f.channel === 'open_interest');
if (openInterestFilters.length > 0) {
const instruments = openInterestFilters.flatMap((s) => s.symbols);
yield new HuobiOpenInterestClient(exchange, this.httpURL, instruments, this.getOpenInterestURLPath.bind(this));
}
const optionMarketIndexFilters = filters.filter((f) => f.channel === 'option_market_index');
if (optionMarketIndexFilters.length > 0) {
const instruments = optionMarketIndexFilters.flatMap((s) => s.symbols);
yield new HuobiOptionsMarketIndexClient(exchange, this.httpURL, instruments);
}
const optionIndexFilters = filters.filter((f) => f.channel === 'option_index');
if (optionIndexFilters.length > 0) {
const instruments = optionIndexFilters.flatMap((s) => s.symbols);
yield new HuobiOptionsIndexClient(exchange, this.httpURL, instruments);
}
}
getOpenInterestURLPath(symbol) {
return symbol;
}
}
class HuobiMarketDataRealTimeFeed extends realtimefeed_1.RealTimeFeedBase {
wssURL;
_suffixes;
constructor(exchange, filters, wssURL, _suffixes, timeoutIntervalMS, onError) {
super(exchange, filters, timeoutIntervalMS, onError);
this.wssURL = wssURL;
this._suffixes = _suffixes;
}
mapToSubscribeMessages(filters) {
return filters
.map((filter) => {
if (!filter.symbols || filter.symbols.length === 0) {
throw new Error('HuobiRealTimeFeed requires explicitly specified symbols when subscribing to live feed');
}
return filter.symbols.map((symbol) => {
const sub = `market.${symbol}.${filter.channel}${this._suffixes[filter.channel] !== undefined ? this._suffixes[filter.channel] : ''}`;
return {
id: '1',
sub,
data_type: sub.endsWith('.high_freq') ? 'incremental' : undefined
};
});
})
.flatMap((s) => s);
}
async provideManualSnapshots(filters, shouldCancel) {
const mbpFilter = filters.find((f) => f.channel === 'mbp');
if (!mbpFilter) {
return;
}
await (0, handy_1.wait)(1.5 * handy_1.ONE_SEC_IN_MS);
for (let symbol of mbpFilter.symbols) {
if (shouldCancel()) {
return;
}
setTimeout(() => {
this.send({
id: '1',
req: `market.${symbol}.mbp.400`
});
this.debug('sent mbp.400 "req" for: %s', mbpFilter.symbols);
}, 3000);
await (0, handy_1.wait)(50);
}
}
decompress = (message) => {
message = (0, zlib_1.unzipSync)(message);
return message;
};
messageIsError(message) {
if (message.status === 'error') {
return true;
}
return false;
}
isIgnoredError(message) {
// ignore market not found errors
if (message['err-msg'] !== undefined && message['err-msg'].includes('invalid symbol ')) {
return true;
}
return false;
}
onMessage(message) {
if (message.ping !== undefined) {
this.send({
pong: message.ping
});
}
}
messageIsHeartbeat(message) {
return message.ping !== undefined;
}
}
class HuobiNotificationsRealTimeFeed extends realtimefeed_1.RealTimeFeedBase {
wssURL;
constructor(exchange, filters, wssURL, timeoutIntervalMS, onError) {
super(exchange, filters, timeoutIntervalMS, onError);
this.wssURL = wssURL;
}
mapToSubscribeMessages(filters) {
return filters
.map((filter) => {
if (!filter.symbols || filter.symbols.length === 0) {
throw new Error('HuobiNotificationsRealTimeFeed requires explicitly specified symbols when subscribing to live feed');
}
return filter.symbols.map((symbol) => {
return {
op: 'sub',
cid: '1',
topic: `public.${symbol}.${filter.channel}`
};
});
})
.flatMap((s) => s);
}
decompress = (message) => {
message = (0, zlib_1.unzipSync)(message);
return message;
};
messageIsError(message) {
if (message.op === 'error' || message.op === 'close') {
return true;
}
const errorCode = message['err-code'];
if (errorCode !== undefined && errorCode !== 0) {
return true;
}
return false;
}
onMessage(message) {
if (message.op === 'ping') {
this.send({
op: 'pong',
ts: message.ts
});
}
}
messageIsHeartbeat(message) {
return message.ping !== undefined;
}
}
class HuobiOpenInterestClient extends realtimefeed_1.PoolingClientBase {
_httpURL;
_instruments;
_getURLPath;
constructor(exchange, _httpURL, _instruments, _getURLPath) {
super(exchange, 4);
this._httpURL = _httpURL;
this._instruments = _instruments;
this._getURLPath = _getURLPath;
}
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 url = `${this._httpURL}/${this._getURLPath(instrument)}`;
const openInterestResponse = (await handy_1.httpClient.get(url, { timeout: 10000 }).json());
if (openInterestResponse.status !== 'ok') {
throw new Error(`open interest response error:${JSON.stringify(openInterestResponse)}, url:${url}`);
}
const openInterestMessage = {
ch: `market.${instrument}.open_interest`,
generated: true,
data: openInterestResponse.data,
ts: openInterestResponse.ts
};
if (outputStream.writable) {
outputStream.write(openInterestMessage);
}
}));
}
}
}
class HuobiOptionsMarketIndexClient extends realtimefeed_1.PoolingClientBase {
_httpURL;
_instruments;
constructor(exchange, _httpURL, _instruments) {
super(exchange, 4);
this._httpURL = _httpURL;
this._instruments = _instruments;
}
async poolDataToStream(outputStream) {
for (const instruments of (0, handy_1.batch)(this._instruments, 10)) {
await Promise.all(instruments.map(async (instrument) => {
if (outputStream.destroyed) {
return;
}
const url = `${this._httpURL}/option_market_index?contract_code=${instrument}`;
const marketIndexResponse = (await handy_1.httpClient.get(url, { timeout: 10000 }).json());
if (marketIndexResponse.status !== 'ok') {
throw new Error(`open interest response error:${JSON.stringify(marketIndexResponse)}, url:${url}`);
}
const marketIndexMessage = {
ch: `market.${instrument}.option_market_index`,
generated: true,
data: marketIndexResponse.data[0],
ts: marketIndexResponse.ts
};
if (outputStream.writable) {
outputStream.write(marketIndexMessage);
}
}));
}
}
}
class HuobiOptionsIndexClient extends realtimefeed_1.PoolingClientBase {
_httpURL;
_instruments;
constructor(exchange, _httpURL, _instruments) {
super(exchange, 4);
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 url = `${this._httpURL}/option_index?symbol=${instrument}`;
const optionIndexResponse = (await handy_1.httpClient.get(url, { timeout: 10000 }).json());
if (optionIndexResponse.status !== 'ok') {
throw new Error(`open interest response error:${JSON.stringify(optionIndexResponse)}, url:${url}`);
}
const optionIndexMessage = {
ch: `market.${instrument}.option_index`,
generated: true,
data: optionIndexResponse.data[0],
ts: optionIndexResponse.ts
};
if (outputStream.writable) {
outputStream.write(optionIndexMessage);
}
}));
}
}
}
class HuobiRealTimeFeed extends HuobiRealTimeFeedBase {
wssURL = 'wss://api-aws.huobi.pro/ws';
httpURL = 'https://api.huobi.pro/v1';
suffixes = {
trade: '.detail',
depth: '.step0',
mbp: '.400'
};
}
exports.HuobiRealTimeFeed = HuobiRealTimeFeed;
class HuobiDMRealTimeFeed extends HuobiRealTimeFeedBase {
wssURL = 'wss://api.hbdm.com/ws';
httpURL = 'https://api.hbdm.com/api/v1';
suffixes = {
trade: '.detail',
depth: '.size_150.high_freq',
basis: '.1min.close'
};
_contractTypeMap = {
CW: 'this_week',
NW: 'next_week',
CQ: 'quarter',
NQ: 'next_quarter'
};
getOpenInterestURLPath(symbol) {
const split = symbol.split('_');
const index = split[0];
const contractType = this._contractTypeMap[split[1]];
return `contract_open_interest?symbol=${index}&contract_type=${contractType}`;
}
}
exports.HuobiDMRealTimeFeed = HuobiDMRealTimeFeed;
class HuobiDMSwapRealTimeFeed extends HuobiRealTimeFeedBase {
wssURL = 'wss://api.hbdm.com/swap-ws';
httpURL = 'https://api.hbdm.com/swap-api/v1';
suffixes = {
trade: '.detail',
depth: '.size_150.high_freq',
basis: '.1min.close'
};
getOpenInterestURLPath(symbol) {
return `swap_open_interest?contract_code=${symbol}`;
}
}
exports.HuobiDMSwapRealTimeFeed = HuobiDMSwapRealTimeFeed;
class HuobiDMLinearSwapRealTimeFeed extends HuobiRealTimeFeedBase {
wssURL = 'wss://api.hbdm.com/linear-swap-ws';
httpURL = 'https://api.hbdm.com/linear-swap-api/v1';
suffixes = {
trade: '.detail',
depth: '.size_150.high_freq',
basis: '.1min.close'
};
getOpenInterestURLPath(symbol) {
return `swap_open_interest?contract_code=${symbol}`;
}
}
exports.HuobiDMLinearSwapRealTimeFeed = HuobiDMLinearSwapRealTimeFeed;
class HuobiDMOptionsRealTimeFeed extends HuobiRealTimeFeedBase {
wssURL = 'wss://api.hbdm.com/option-ws';
httpURL = 'https://api.hbdm.com/option-api/v1';
suffixes = {
trade: '.detail',
depth: '.size_150.high_freq',
basis: '.1min.close'
};
getOpenInterestURLPath(symbol) {
return `option_open_interest?contract_code=${symbol}`;
}
}
exports.HuobiDMOptionsRealTimeFeed = HuobiDMOptionsRealTimeFeed;
//# sourceMappingURL=huobi.js.map