UNPKG

ccxt

Version:

A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go

1,066 lines (1,064 loc) • 133 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import { sha256 } from '@noble/hashes/sha2.js'; import htxRest from '../htx.js'; import { ExchangeError, InvalidNonce, ChecksumError, ArgumentsRequired, BadRequest, BadSymbol, AuthenticationError, NetworkError } from '../base/errors.js'; import { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide } from '../base/ws/Cache.js'; // --------------------------------------------------------------------------- export default class htx extends htxRest { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'createOrderWs': false, 'editOrderWs': false, 'fetchOpenOrdersWs': false, 'fetchOrderWs': false, 'cancelOrderWs': false, 'cancelOrdersWs': false, 'cancelAllOrdersWs': false, 'fetchTradesWs': false, 'fetchBalanceWs': false, 'watchOrderBook': true, 'watchOrders': true, 'watchTickers': false, 'watchTicker': true, 'watchTrades': true, 'watchTradesForSymbols': false, 'watchMyTrades': true, 'watchBalance': true, 'watchOHLCV': true, 'unWatchTicker': true, 'unWatchOHLCV': true, 'unWatchTrades': true, 'unWatchOrderBook': true, }, 'urls': { 'api': { 'ws': { 'api': { 'spot': { 'public': 'wss://{hostname}/ws', 'private': 'wss://{hostname}/ws/v2', 'feed': 'wss://{hostname}/feed', }, 'future': { 'linear': { 'public': 'wss://api.hbdm.vn/linear-swap-ws', 'private': 'wss://api.hbdm.vn/linear-swap-notification', 'privateV5': 'wss://api.hbdm.vn/ws/v5/notification', }, 'inverse': { 'public': 'wss://api.hbdm.vn/ws', 'private': 'wss://api.hbdm.vn/notification', }, }, 'swap': { 'inverse': { 'public': 'wss://api.hbdm.vn/swap-ws', 'private': 'wss://api.hbdm.vn/swap-notification', }, 'linear': { 'public': 'wss://api.hbdm.vn/linear-swap-ws', 'private': 'wss://api.hbdm.vn/linear-swap-notification', 'privateV5': 'wss://api.hbdm.vn/ws/v5/notification', }, }, }, // these settings work faster for clients hosted on AWS 'api-aws': { 'spot': { 'public': 'wss://api-aws.huobi.pro/ws', 'private': 'wss://api-aws.huobi.pro/ws/v2', 'feed': 'wss://{hostname}/feed', }, 'future': { 'linear': { 'public': 'wss://api.hbdm.vn/linear-swap-ws', 'private': 'wss://api.hbdm.vn/linear-swap-notification', 'privateV5': 'wss://api.hbdm.vn/ws/v5/notification', }, 'inverse': { 'public': 'wss://api.hbdm.vn/ws', 'private': 'wss://api.hbdm.vn/notification', }, }, 'swap': { 'linear': { 'public': 'wss://api.hbdm.vn/linear-swap-ws', 'private': 'wss://api.hbdm.vn/linear-swap-notification', 'privateV5': 'wss://api.hbdm.vn/ws/v5/notification', }, 'inverse': { 'public': 'wss://api.hbdm.vn/swap-ws', 'private': 'wss://api.hbdm.vn/swap-notification', }, }, }, }, }, }, 'options': { 'tradesLimit': 1000, 'OHLCVLimit': 1000, 'api': 'api', // or api-aws for clients hosted on AWS 'watchOrderBook': { 'maxRetries': 3, 'checksum': true, 'depth': 150, // 150 or 20 }, 'ws': { 'gunzip': true, }, 'watchTicker': { 'name': 'market.{marketId}.detail', // 'market.{marketId}.bbo' or 'market.{marketId}.ticker' }, }, 'exceptions': { 'ws': { 'exact': { 'bad-request': BadRequest, // { ts: 1586323747018, status: 'error', 'err-code': 'bad-request', err-msg': 'invalid mbp.150.symbol linkusdt', id: '2'} '2002': AuthenticationError, // { action: 'sub', code: 2002, ch: 'accounts.update#2', message: 'invalid.auth.state' } '2021': BadRequest, '2001': BadSymbol, // { action: 'sub', code: 2001, ch: 'orders#2ltcusdt', message: 'invalid.symbol'} '2011': BadSymbol, // { op: 'sub', cid: '1649149285', topic: 'orders_cross.ltc-usdt', 'err-code': 2011, 'err-msg': "Contract doesn't exist.", ts: 1649149287637 } '2040': BadRequest, // { op: 'sub', cid: '1649152947', 'err-code': 2040, 'err-msg': 'Missing required parameter.', ts: 1649152948684 } '4007': BadRequest, // { op: 'sub', cid: '1', topic: 'accounts_unify.USDT', 'err-code': 4007, 'err-msg': 'Non - single account user is not available, please check through the cross and isolated account asset interface', ts: 1698419318540 } }, }, }, }); } requestId() { this.lockId(); const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; this.unlockId(); return requestId.toString(); } /** * @method * @name htx#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://www.htx.com/en-us/opend/newApiPages/?id=7ec53561-7773-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c33ab2-77ae-11ed-9966-0242ac110003 * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure} */ async watchTicker(symbol, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); symbol = market['symbol']; const options = this.safeDict(this.options, 'watchTicker', {}); const topic = this.safeString(options, 'name', 'market.{marketId}.detail'); if (topic === 'market.{marketId}.ticker' && market['type'] !== 'spot') { throw new BadRequest(this.id + ' watchTicker() with name market.{marketId}.ticker is only allowed for spot markets, use market.{marketId}.detail instead'); } const messageHash = this.implodeParams(topic, { 'marketId': market['id'] }); const url = this.getUrlByMarketType(market['type'], market['linear']); return await this.subscribePublic(url, symbol, messageHash, undefined, params); } /** * @method * @name htx#unWatchTicker * @description unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list * @see https://www.htx.com/en-us/opend/newApiPages/?id=7ec53561-7773-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c33ab2-77ae-11ed-9966-0242ac110003 * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure} */ async unWatchTicker(symbol, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); const topic = 'ticker'; const options = this.safeDict(this.options, 'watchTicker', {}); const channel = this.safeString(options, 'name', 'market.{marketId}.detail'); if (channel === 'market.{marketId}.ticker' && market['type'] !== 'spot') { throw new BadRequest(this.id + ' watchTicker() with name market.{marketId}.ticker is only allowed for spot markets, use market.{marketId}.detail instead'); } const subMessageHash = this.implodeParams(channel, { 'marketId': market['id'] }); return await this.unsubscribePublic(market, subMessageHash, topic, params); } handleTicker(client, message) { // // "market.btcusdt.detail" // { // "ch": "market.btcusdt.detail", // "ts": 1583494163784, // "tick": { // "id": 209988464418, // "low": 8988, // "high": 9155.41, // "open": 9078.91, // "close": 9136.46, // "vol": 237813910.5928412, // "amount": 26184.202558551195, // "version": 209988464418, // "count": 265673 // } // } // "market.btcusdt.bbo" // { // "ch": "market.btcusdt.bbo", // "ts": 1671941599613, // "tick": { // "seqId": 161499562790, // "ask": 16829.51, // "askSize": 0.707776, // "bid": 16829.5, // "bidSize": 1.685945, // "quoteTime": 1671941599612, // "symbol": "btcusdt" // } // } // const tick = this.safeValue(message, 'tick', {}); const ch = this.safeString(message, 'ch'); if (ch === undefined) { return message; } const parts = ch.split('.'); const marketId = this.safeString(parts, 1); const market = this.safeMarket(marketId); const ticker = this.parseTicker(tick, market); const timestamp = this.safeValue(message, 'ts'); ticker['timestamp'] = timestamp; ticker['datetime'] = this.iso8601(timestamp); const symbol = ticker['symbol']; if (symbol !== undefined) { this.tickers[symbol] = ticker; } client.resolve(ticker, ch); return message; } /** * @method * @name htx#watchTrades * @description get the list of most recent trades for a particular symbol * @see https://www.htx.com/en-us/opend/newApiPages/?id=7ec53b69-7773-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c33c21-77ae-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c33cfe-77ae-11ed-9966-0242ac110003 * @param {string} symbol unified symbol of the market to fetch trades for * @param {int} [since] timestamp in ms of the earliest trade to fetch * @param {int} [limit] the maximum amount of trades to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=public-trades} */ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); symbol = market['symbol']; const messageHash = 'market.' + market['id'] + '.trade.detail'; const url = this.getUrlByMarketType(market['type'], market['linear']); const trades = await this.subscribePublic(url, symbol, messageHash, undefined, params); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } /** * @method * @name htx#unWatchTrades * @description unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list * @see https://www.htx.com/en-us/opend/newApiPages/?id=7ec53b69-7773-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c33c21-77ae-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c33cfe-77ae-11ed-9966-0242ac110003 * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure} */ async unWatchTrades(symbol, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); const topic = 'trades'; const options = this.safeDict(this.options, 'watchTrades', {}); const channel = this.safeString(options, 'name', 'market.{marketId}.trade.detail'); const subMessageHash = this.implodeParams(channel, { 'marketId': market['id'] }); return await this.unsubscribePublic(market, subMessageHash, topic, params); } handleTrades(client, message) { // // { // "ch": "market.btcusdt.trade.detail", // "ts": 1583495834011, // "tick": { // "id": 105004645372, // "ts": 1583495833751, // "data": [ // { // "id": 1.050046453727319e+22, // "ts": 1583495833751, // "tradeId": 102090727790, // "amount": 0.003893, // "price": 9150.01, // "direction": "sell" // } // ] // } // } // const tick = this.safeValue(message, 'tick', {}); const data = this.safeValue(tick, 'data', {}); const ch = this.safeString(message, 'ch'); if (ch === undefined) { return message; } const parts = ch.split('.'); const marketId = this.safeString(parts, 1); const market = this.safeMarket(marketId); const symbol = market['symbol']; let tradesCache = this.safeValue(this.trades, symbol); if (tradesCache === undefined) { const limit = this.safeInteger(this.options, 'tradesLimit', 1000); tradesCache = new ArrayCache(limit); this.trades[symbol] = tradesCache; } for (let i = 0; i < data.length; i++) { const trade = this.parseTrade(data[i], market); tradesCache.append(trade); } client.resolve(tradesCache, ch); return message; } /** * @method * @name htx#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://www.htx.com/en-us/opend/newApiPages/?id=7ec53241-7773-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c3346a-77ae-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c33563-77ae-11ed-9966-0242ac110003 * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @param {int} [since] timestamp in ms of the earliest candle to fetch * @param {int} [limit] the maximum amount of candles to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); symbol = market['symbol']; const interval = this.safeString(this.timeframes, timeframe, timeframe); const messageHash = 'market.' + market['id'] + '.kline.' + interval; const url = this.getUrlByMarketType(market['type'], market['linear']); const ohlcv = await this.subscribePublic(url, symbol, messageHash, undefined, params); if (this.newUpdates) { limit = ohlcv.getLimit(symbol, limit); } return this.filterBySinceLimit(ohlcv, since, limit, 0, true); } /** * @method * @name htx#unWatchOHLCV * @description unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://www.htx.com/en-us/opend/newApiPages/?id=7ec53241-7773-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c3346a-77ae-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=28c33563-77ae-11ed-9966-0242ac110003 * @param {string} symbol unified symbol of the market * @param {string} timeframe the length of time each candle represents * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {object} [params.timezone] if provided, kline intervals are interpreted in that timezone instead of UTC, example '+08:00' * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async unWatchOHLCV(symbol, timeframe = '1m', params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); const interval = this.safeString(this.timeframes, timeframe, timeframe); const subMessageHash = 'market.' + market['id'] + '.kline.' + interval; const topic = 'ohlcv'; params['symbolsAndTimeframes'] = [[market['symbol'], timeframe]]; return await this.unsubscribePublic(market, subMessageHash, topic, params); } handleOHLCV(client, message) { // // { // "ch": "market.btcusdt.kline.1min", // "ts": 1583501786794, // "tick": { // "id": 1583501760, // "open": 9094.5, // "close": 9094.51, // "low": 9094.5, // "high": 9094.51, // "amount": 0.44639786263800907, // "vol": 4059.76919054, // "count": 16 // } // } // const ch = this.safeString(message, 'ch'); if (ch === undefined) { return; } const parts = ch.split('.'); const marketId = this.safeString(parts, 1); const market = this.safeMarket(marketId); const symbol = market['symbol']; const interval = this.safeString(parts, 3); const timeframe = this.findTimeframe(interval); this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {}); let stored = this.safeValue(this.safeValue(this.ohlcvs, symbol), timeframe); if (stored === undefined) { const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000); stored = new ArrayCacheByTimestamp(limit); if (symbol !== undefined && timeframe !== undefined) { this.ohlcvs[symbol][timeframe] = stored; } } const tick = this.safeValue(message, 'tick'); const parsed = this.parseOHLCV(tick, market); stored.append(parsed); client.resolve(stored, ch); } /** * @method * @name htx#watchOrderBook * @see https://huobiapi.github.io/docs/dm/v1/en/#subscribe-market-depth-data * @see https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#subscribe-incremental-market-depth-data * @see https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-subscribe-incremental-market-depth-data * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int} [limit] the maximum amount of order book entries to return * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an [order book structure]{@link https://docs.ccxt.com/?id=order-book-structure} */ async watchOrderBook(symbol, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); symbol = market['symbol']; const allowedLimits = [5, 20, 150, 400]; // 2) 5-level/20-level incremental MBP is a tick by tick feed, // which means whenever there is an order book change at that level, it pushes an update; // 150-levels/400-level incremental MBP feed is based on the gap // between two snapshots at 100ms interval. const options = this.safeDict(this.options, 'watchOrderBook', {}); if (limit === undefined) { limit = this.safeInteger(options, 'depth', 150); } if (!this.inArray(limit, allowedLimits)) { throw new ExchangeError(this.id + ' watchOrderBook market accepts limits of 5, 20, 150 or 400 only'); } let messageHash = undefined; if (market['spot']) { messageHash = 'market.' + market['id'] + '.mbp.' + this.numberToString(limit); } else { messageHash = 'market.' + market['id'] + '.depth.size_' + this.numberToString(limit) + '.high_freq'; } const url = this.getUrlByMarketType(market['type'], market['linear'], false, true); let method = this.handleOrderBookSubscription; if (!market['spot']) { params = this.extend(params); params['data_type'] = 'incremental'; method = undefined; } const orderbook = await this.subscribePublic(url, symbol, messageHash, method, params); return orderbook.limit(); } /** * @method * @name htx#unWatchOrderBook * @description unsubscribe from the orderbook channel * @see https://huobiapi.github.io/docs/dm/v1/en/#subscribe-market-depth-data * @see https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#subscribe-incremental-market-depth-data * @see https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-subscribe-incremental-market-depth-data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {int} [params.limit] orderbook limit, default is undefined * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/?id=order-book-structure} */ async unWatchOrderBook(symbol, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } const market = this.market(symbol); const topic = 'orderbook'; const options = this.safeDict(this.options, 'watchOrderBook', {}); const depth = this.safeInteger(options, 'depth', 150); let subMessageHash = undefined; if (market['spot']) { subMessageHash = 'market.' + market['id'] + '.mbp.' + this.numberToString(depth); } else { subMessageHash = 'market.' + market['id'] + '.depth.size_' + this.numberToString(depth) + '.high_freq'; } if (!(market['spot'])) { params['data_type'] = 'incremental'; } return await this.unsubscribePublic(market, subMessageHash, topic, params); } handleOrderBookSnapshot(client, message, subscription) { // // { // "id": 1583473663565, // "rep": "market.btcusdt.mbp.150", // "status": "ok", // "ts": 1698359289261, // "data": { // "seqNum": 104999417756, // "bids": [ // [9058.27, 0], // [9058.43, 0], // [9058.99, 0], // ], // "asks": [ // [9084.27, 0.2], // [9085.69, 0], // [9085.81, 0], // ] // } // } // const symbol = this.safeString(subscription, 'symbol'); const messageHash = this.safeString(subscription, 'messageHash'); if (messageHash === undefined) { return; } const id = this.safeString(message, 'id'); const lastTimestamp = this.safeInteger(subscription, 'lastTimestamp'); try { const orderbook = this.safeValue(this.orderbooks, symbol); const data = this.safeValue(message, 'data'); const messages = orderbook.cache; const firstMessage = this.safeValue(messages, 0, {}); const snapshot = this.parseOrderBook(data, symbol); const tick = this.safeValue(firstMessage, 'tick'); const sequence = this.safeInteger(tick, 'prevSeqNum'); const nonce = this.safeInteger(data, 'seqNum'); if (nonce === undefined) { return; } snapshot['nonce'] = nonce; const snapshotTimestamp = this.safeInteger(message, 'ts'); subscription['lastTimestamp'] = snapshotTimestamp; const snapshotLimit = this.safeInteger(subscription, 'limit'); const snapshotOrderBook = this.orderBook(snapshot, snapshotLimit); client.resolve(snapshotOrderBook, id); if ((sequence === undefined) || (nonce < sequence)) { const maxAttempts = this.handleOption('watchOrderBook', 'maxRetries', 3); let numAttempts = this.safeInteger(subscription, 'numAttempts', 0); // retry to synchronize if we have not reached maxAttempts yet if (numAttempts < maxAttempts) { // safety guard if (messageHash in client.subscriptions) { numAttempts = this.sum(numAttempts, 1); let delayTime = 1000; if ((lastTimestamp !== undefined) && (snapshotTimestamp !== undefined)) { delayTime = this.sum(1000, lastTimestamp - snapshotTimestamp); } subscription['numAttempts'] = numAttempts; client.subscriptions[messageHash] = subscription; this.delay(delayTime, this.watchOrderBookSnapshot, client, message, subscription); } } else { // throw upon failing to synchronize in maxAttempts throw new InvalidNonce(this.id + ' failed to synchronize WebSocket feed with the snapshot for symbol ' + symbol + ' in ' + maxAttempts.toString() + ' attempts'); } } else { orderbook.reset(snapshot); // unroll the accumulated deltas for (let i = 0; i < messages.length; i++) { this.handleOrderBookMessage(client, messages[i]); } orderbook.cache = []; if (symbol !== undefined) { this.orderbooks[symbol] = orderbook; } client.resolve(orderbook, messageHash); } } catch (e) { if (messageHash !== undefined) { delete client.subscriptions[messageHash]; } if (symbol !== undefined) { delete this.orderbooks[symbol]; } client.reject(e, messageHash); } } async watchOrderBookSnapshot(client, message, subscription) { const messageHash = this.safeString(subscription, 'messageHash'); const symbol = this.safeString(subscription, 'symbol'); const limit = this.safeInteger(subscription, 'limit'); const timestamp = this.safeInteger(message, 'ts'); const params = this.safeValue(subscription, 'params'); const attempts = this.safeInteger(subscription, 'numAttempts', 0); const market = this.market(symbol); const url = this.getUrlByMarketType(market['type'], market['linear'], false, true); const requestId = this.requestId(); const request = { 'req': messageHash, 'id': requestId, }; // this is a temporary subscription by a specific requestId // it has a very short lifetime until the snapshot is received over ws const snapshotSubscription = { 'id': requestId, 'messageHash': messageHash, 'symbol': symbol, 'limit': limit, 'params': params, 'numAttempts': attempts, 'lastTimestamp': timestamp, 'method': this.handleOrderBookSnapshot, }; try { const orderbook = await this.watch(url, requestId, request, requestId, snapshotSubscription); return orderbook.limit(); } catch (e) { if (messageHash !== undefined) { delete client.subscriptions[messageHash]; } client.reject(e, messageHash); } return undefined; } handleDelta(bookside, delta) { const price = this.safeFloat(delta, 0); const amount = this.safeFloat(delta, 1); bookside.store(price, amount); } handleDeltas(bookside, deltas) { for (let i = 0; i < deltas.length; i++) { this.handleDelta(bookside, deltas[i]); } } handleOrderBookMessage(client, message) { // spot markets // // { // "ch": "market.btcusdt.mbp.150", // "ts": 1583472025885, // "tick": { // "seqNum": 104998984994, // "prevSeqNum": 104998984977, // "bids": [ // [9058.27, 0], // [9058.43, 0], // [9058.99, 0], // ], // "asks": [ // [9084.27, 0.2], // [9085.69, 0], // [9085.81, 0], // ] // } // } // // non-spot market update // // { // "ch":"market.BTC220218.depth.size_150.high_freq", // "tick":{ // "asks":[], // "bids":[ // [43445.74,1], // [43444.48,0 ], // [40593.92,9] // ], // "ch":"market.BTC220218.depth.size_150.high_freq", // "event":"update", // "id":152727500274, // "mrid":152727500274, // "ts":1645023376098, // "version":37536690 // }, // "ts":1645023376098 // } // non-spot market snapshot // // { // "ch":"market.BTC220218.depth.size_150.high_freq", // "tick":{ // "asks":[ // [43445.74,1], // [43444.48,0 ], // [40593.92,9] // ], // "bids":[ // [43445.74,1], // [43444.48,0 ], // [40593.92,9] // ], // "ch":"market.BTC220218.depth.size_150.high_freq", // "event":"snapshot", // "id":152727500274, // "mrid":152727500274, // "ts":1645023376098, // "version":37536690 // }, // "ts":1645023376098 // } // const ch = this.safeValue(message, 'ch'); const parts = ch.split('.'); const marketId = this.safeString(parts, 1); const market = this.safeMarket(marketId); const symbol = market['symbol']; const orderbook = this.orderbooks[symbol]; const tick = this.safeValue(message, 'tick', {}); const seqNum = this.safeInteger(tick, 'seqNum'); const prevSeqNum = this.safeInteger(tick, 'prevSeqNum'); const event = this.safeString(tick, 'event'); const version = this.safeInteger(tick, 'version'); const timestamp = this.safeInteger(message, 'ts'); if (event === 'snapshot') { const snapshot = this.parseOrderBook(tick, symbol, timestamp); orderbook.reset(snapshot); orderbook['nonce'] = version; } if ((prevSeqNum !== undefined) && prevSeqNum > this.safeInteger(orderbook, 'nonce', 0)) { const checksum = this.handleOption('watchOrderBook', 'checksum', true); if (checksum) { throw new ChecksumError(this.id + ' ' + this.orderbookChecksumMessage(symbol)); } } const spotConditon = market['spot'] && (prevSeqNum === orderbook['nonce']); const nonSpotCondition = market['contract'] && (version !== undefined) && (version - 1 === orderbook['nonce']); if (spotConditon || nonSpotCondition) { const asks = this.safeValue(tick, 'asks', []); const bids = this.safeValue(tick, 'bids', []); this.handleDeltas(orderbook['asks'], asks); this.handleDeltas(orderbook['bids'], bids); orderbook['nonce'] = spotConditon ? seqNum : version; orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601(timestamp); } } handleOrderBook(client, message) { // // deltas // // spot markets // // { // "ch": "market.btcusdt.mbp.150", // "ts": 1583472025885, // "tick": { // "seqNum": 104998984994, // "prevSeqNum": 104998984977, // "bids": [ // [9058.27, 0], // [9058.43, 0], // [9058.99, 0], // ], // "asks": [ // [9084.27, 0.2], // [9085.69, 0], // [9085.81, 0], // ] // } // } // // non spot markets // // { // "ch":"market.BTC220218.depth.size_150.high_freq", // "tick":{ // "asks":[], // "bids":[ // [43445.74,1], // [43444.48,0 ], // [40593.92,9] // ], // "ch":"market.BTC220218.depth.size_150.high_freq", // "event":"update", // "id":152727500274, // "mrid":152727500274, // "ts":1645023376098, // "version":37536690 // }, // "ts":1645023376098 // } // const messageHash = this.safeString(message, 'ch'); const tick = this.safeDict(message, 'tick'); const event = this.safeString(tick, 'event'); const ch = this.safeString(message, 'ch'); if (ch === undefined) { return; } const parts = ch.split('.'); const marketId = this.safeString(parts, 1); const symbol = this.safeSymbol(marketId); if (!(symbol in this.orderbooks)) { const size = this.safeString(parts, 3); if (size === undefined) { return; } const sizeParts = size.split('_'); const limit = this.safeInteger(sizeParts, 1); this.orderbooks[symbol] = this.orderBook({}, limit); } const orderbook = this.orderbooks[symbol]; if ((event === undefined) && (orderbook['nonce'] === undefined)) { orderbook.cache.push(message); } else { this.handleOrderBookMessage(client, message); client.resolve(orderbook, messageHash); } } handleOrderBookSubscription(client, message, subscription) { const symbol = this.safeString(subscription, 'symbol'); const market = this.market(symbol); const limit = this.safeInteger(subscription, 'limit'); if (symbol !== undefined) { this.orderbooks[symbol] = this.orderBook({}, limit); } if (market['spot']) { this.spawn(this.watchOrderBookSnapshot, client, message, subscription); } } /** * @method * @name htx#watchMyTrades * @description watches information on multiple trades made by the user * @see https://www.htx.com/en-us/opend/newApiPages/?id=7ec53dd5-7773-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=8cb89359-77b5-11ed-9966-195a35275ff * @param {string} symbol unified market symbol of the market trades were made in * @param {int} [since] the earliest time in ms to fetch trades for * @param {int} [limit] the maximum number of trade structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=trade-structure} */ async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) { this.checkRequiredCredentials(); if (this.markets === undefined) { await this.loadMarkets(); } let type = undefined; let marketId = '*'; // wildcard let market = undefined; let messageHash = undefined; let channel = undefined; let trades = undefined; let subType = undefined; if (symbol !== undefined) { market = this.market(symbol); symbol = market['symbol']; type = market['type']; subType = market['linear'] ? 'linear' : 'inverse'; marketId = market['lowercaseId']; } else { type = this.safeString(this.options, 'defaultType', 'spot'); type = this.safeString(params, 'type', type); subType = this.safeString2(this.options, 'subType', 'defaultSubType', 'linear'); subType = this.safeString(params, 'subType', subType); params = this.omit(params, ['type', 'subType']); } const linear = (subType === 'linear'); const swap = (type === 'swap'); const future = (type === 'future'); const isV5Linear = (linear && (swap || future)); if (type === 'spot') { let mode = undefined; if (mode === undefined) { mode = this.safeString2(this.options, 'watchMyTrades', 'mode', '0'); mode = this.safeString(params, 'mode', mode); params = this.omit(params, 'mode'); } messageHash = 'trade.clearing' + '#' + marketId + '#' + mode; channel = messageHash; } else if (isV5Linear) { const channelAndMessageHashAndParams = this.getV5LinearChannelAndMessageHash('trade', market, params); channel = this.safeString(channelAndMessageHashAndParams, 0); messageHash = this.safeString(channelAndMessageHashAndParams, 1); params = this.safeValue(channelAndMessageHashAndParams, 2, {}); } else { const channelAndMessageHash = this.getOrderChannelAndMessageHash(type, subType, market, params); channel = this.safeString(channelAndMessageHash, 0); const orderMessageHash = this.safeString(channelAndMessageHash, 1); // we will take advantage of the order messageHash because already handles stuff // like symbol/margin/subtype/type variations messageHash = orderMessageHash + ':' + 'trade'; } const subscriptionParams = { 'isV5': isV5Linear, }; trades = await this.subscribePrivate(channel, messageHash, type, subType, params, subscriptionParams); if (trades === undefined) { throw new ArgumentsRequired(this.id + ' watchMyTrades() trades is required'); } if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true); } getOrderChannelAndMessageHash(type, subType, market = undefined, params = {}) { let messageHash = undefined; let channel = undefined; let orderType = this.safeString(this.options, 'orderType', 'orders'); // orders or matchOrders orderType = this.safeString(params, 'orderType', orderType); params = this.omit(params, 'orderType'); let marketCode = undefined; if ((market !== undefined) && (market['lowercaseId'] !== undefined)) { marketCode = market['lowercaseId'].toLowerCase(); } const baseId = (market !== undefined) ? market['baseId'] : undefined; const prefix = orderType; messageHash = prefix; if (subType === 'linear') { // USDT Margined Contracts Example: LTC/USDT:USDT const marginMode = this.safeString(params, 'margin', 'cross'); const marginPrefix = (marginMode === 'cross') ? prefix + '_cross' : prefix; messageHash = marginPrefix; if (marketCode !== undefined) { messageHash += '.' + marketCode; channel = messageHash; } else { channel = marginPrefix + '.' + '*'; } } else if (type === 'future') { // inverse futures Example: BCH/USD:BCH-220408 if (baseId !== undefined) { channel = prefix + '.' + baseId.toLowerCase(); messageHash = channel; } else { channel = prefix + '.' + '*'; } } else { // inverse swaps: Example: BTC/USD:BTC if (marketCode !== undefined) { channel = prefix + '.' + marketCode; messageHash = channel; } else { channel = prefix + '.' + '*'; } } return [channel, messageHash]; } getV5LinearChannelAndMessageHash(topic, market = undefined, params = {}) { const contractCode = (market !== undefined) ? market['id'] : this.safeString(params, 'contract_code', '*'); const channel = topic; let messageHash = topic; if ((contractCode !== undefined) && (contractCode !== '*')) { messageHash = topic + '.' + contractCode.toLowerCase(); } params = this.omit(params, 'contract_code'); const requestParams = this.extend({ 'contract_code': contractCode, }, params); return [channel, messageHash, requestParams]; } /** * @method * @name htx#watchOrders * @description watches information on multiple orders made by the user * @see https://www.htx.com/en-us/opend/newApiPages/?id=7ec53c8f-7773-11ed-9966-0242ac110003 * @see https://www.htx.com/en-us/opend/newApiPages/?id=8cb89359-77b5-11ed-9966-195a208afe7 * @param {string} symbol unified market symbol of the market orders were made in * @param {int} [since] the earliest time in ms to fetch orders for * @param {int} [limit] the maximum number of order structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/?id=order-structure} */ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) { if (this.markets === undefined) { await this.loadMarkets(); } let type = undefined; let subType = undefined; let market = undefined; let suffix = '*'; // wildcard if (symbol !== undefined) { market = this.market(symbol); symbol = market['symbol']; type = market['type']; suffix = market['lowercaseId']; subType = market['linear'] ? 'linear' : 'inverse'; } else { type = this.safeString(this.options, 'defaultType', 'spot'); type = this.safeString(params, 'type', type); subType = this.safeString2(this.options, 'subType', 'defaultSubType', 'linear'); subType = this.safeString(params, 'subType', subType); params = this.omit(params, ['type', 'subType']); } const linear = (subType === 'linear'); const swap = (type === 'swap'); const future = (type === 'future'); const isV5Linear = (linear && (swap || future)); let messageHash = undefined; let channel = undefined; if (type === 'spot') { messageHash = 'orders' + '#' + suffix; channel = messageHash; } else if (isV5Linear) { const channelAndMessageHashAndParams = this.getV5LinearChannelAndMessageHash('orders', market, params); channel = this.safeString(channelAndMessageHashAndParams, 0); messageHash = this.safeString(channelAndMessageHashAndParams, 1); params = this.safeValue(channelAndMessageHashAndParams, 2, {}); } else { const channelAndMessageHash = this.getOrderChannelAndMessageHash(type, subType, market, params); channel = this.safeString(channelAndMessageHash, 0); messageHash = this.safeString(channelAndMessageHash, 1); } const subscriptionParams = { 'isV5': isV5Linear, }; const orders = await this.subscribePrivate(channel, messageHash, type, subType, params, subscriptionParams); if (this.newUpdates) { limit = orders.getLimit(symbol, limit); } return this.filterBySinceLimit(orders, since, limit, 'timestamp', true); } handleOrder(client, message) { // // spot // // for new order creation // // { // "action":"push", // "ch":"orders#btcusdt", // or "orders#*" for global subscriptions // "data": { // "orderStatus": "submitted", // "eventType": "creation", // "totalTradeAmount": 0 // for "submitted" order status // "orderCreateTime": 1645116048355, // only when `submitted` status // "orderSource": "spot-web", // "accountId": 44234548, // "orderPrice": "100", // "orderSize": "0.05", // "symbol": "ethusdt", // "type": "buy-limit", // "orderId": "478861479986886", // "clientOrderId": '', // } // } // // for filled order, additional fiel