UNPKG

tardis-dev

Version:

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

344 lines 14 kB
"use strict"; 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