UNPKG

ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges

1,127 lines (1,125 loc) • 104 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 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'; import { sha256 } from '../static_dependencies/noble-hashes/sha256.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, }, 'urls': { 'api': { 'ws': { 'api': { 'spot': { 'public': 'wss://{hostname}/ws', 'private': 'wss://{hostname}/ws/v2', 'feed': 'wss://{hostname}/feed', }, 'future': { 'linear': { 'public': 'wss://api.hbdm.com/linear-swap-ws', 'private': 'wss://api.hbdm.com/linear-swap-notification', }, 'inverse': { 'public': 'wss://api.hbdm.com/ws', 'private': 'wss://api.hbdm.com/notification', }, }, 'swap': { 'inverse': { 'public': 'wss://api.hbdm.com/swap-ws', 'private': 'wss://api.hbdm.com/swap-notification', }, 'linear': { 'public': 'wss://api.hbdm.com/linear-swap-ws', 'private': 'wss://api.hbdm.com/linear-swap-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', }, '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', }, 'inverse': { 'public': 'wss://api.hbdm.vn/swap-ws', 'private': 'wss://api.hbdm.vn/swap-notification', }, }, }, }, }, }, 'options': { 'tradesLimit': 1000, 'OHLCVLimit': 1000, 'api': 'api', 'watchOrderBook': { 'maxRetries': 3, 'checksum': true, }, 'ws': { 'gunzip': true, }, 'watchTicker': { 'name': 'market.{marketId}.detail', // 'market.{marketId}.bbo' or 'market.{marketId}.ticker' }, }, 'exceptions': { 'ws': { 'exact': { 'bad-request': BadRequest, '2002': AuthenticationError, '2021': BadRequest, '2001': BadSymbol, '2011': BadSymbol, '2040': BadRequest, '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() { const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; 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 = {}) { await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const options = this.safeValue(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); } 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'); 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']; 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 = {}) { 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); } 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'); 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 = {}) { 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); } 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'); 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.ohlcvs[symbol], timeframe); if (stored === undefined) { const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000); stored = new ArrayCacheByTimestamp(limit); 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} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async watchOrderBook(symbol, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const allowedLimits = [20, 150]; // 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. if (limit === undefined) { limit = market['spot'] ? 150 : 20; } if (!this.inArray(limit, allowedLimits)) { throw new ExchangeError(this.id + ' watchOrderBook market accepts limits of 20 and 150 only'); } let messageHash = undefined; if (market['spot']) { messageHash = 'market.' + market['id'] + '.mbp.' + limit.toString(); } else { messageHash = 'market.' + market['id'] + '.depth.size_' + limit.toString() + '.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(); } 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'); const id = this.safeString(message, 'id'); const lastTimestamp = this.safeInteger(subscription, 'lastTimestamp'); try { const orderbook = 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'); 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); const 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 = []; this.orderbooks[symbol] = orderbook; client.resolve(orderbook, messageHash); } } catch (e) { delete client.subscriptions[messageHash]; 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) { 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 > orderbook['nonce']) { 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 - 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'); 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); 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'); 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 * @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(); 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']); } 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 { 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'; } trades = await this.subscribePrivate(channel, messageHash, type, subType, params); 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'); const marketCode = (market !== undefined) ? market['lowercaseId'].toLowerCase() : undefined; 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]; } /** * @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 * @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 = {}) { 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']); } let messageHash = undefined; let channel = undefined; if (type === 'spot') { messageHash = 'orders' + '#' + suffix; channel = messageHash; } else { const channelAndMessageHash = this.getOrderChannelAndMessageHash(type, subType, market, params); channel = this.safeString(channelAndMessageHash, 0); messageHash = this.safeString(channelAndMessageHash, 1); } const orders = await this.subscribePrivate(channel, messageHash, type, subType, params); if (this.newUpdates) { limit = orders.getLimit(symbol, limit); } return this.filterBySinceLimit(orders, since, limit, 'timestamp', true); } handleOrder(client, message) { // // spot // // { // "action":"push", // "ch":"orders#btcusdt", // or "orders#*" for global subscriptions // "data": { // "orderSource": "spot-web", // "orderCreateTime": 1645116048355, // "accountId": 44234548, // "orderPrice": "100", // "orderSize": "0.05", // "symbol": "ethusdt", // "type": "buy-limit", // "orderId": "478861479986886", // "eventType": "creation", // "clientOrderId": '', // "orderStatus": "submitted" // } // } // // spot wrapped trade // // { // "action": "push", // "ch": "orders#ltcusdt", // "data": { // "tradePrice": "130.01", // "tradeVolume": "0.0385", // "tradeTime": 1648714741525, // "aggressor": true, // "execAmt": "0.0385", // "orderSource": "spot-web", // "orderSize": "0.0385", // "remainAmt": "0", // "tradeId": 101541578884, // "symbol": "ltcusdt", // "type": "sell-market", // "eventType": "trade", // "clientOrderId": '', // "orderStatus": "filled", // "orderId": 509835753860328 // } // } // // non spot order // // { // "contract_type": "swap", // "pair": "LTC-USDT", // "business_type": "swap", // "op": "notify", // "topic": "orders_cross.ltc-usdt", // "ts": 1650354508696, // "symbol": "LTC", // "contract_code": "LTC-USDT", // "volume": 1, // "price": 110.34, // "order_price_type": "lightning", // "direction": "sell", // "offset": "close", // "status": 6, // "lever_rate": 1, // "order_id": "966002354015051776", // "order_id_str": "966002354015051776", // "client_order_id": null, // "order_source": "web", // "order_type": 1, // "created_at": 1650354508649, // "trade_volume": 1, // "trade_turnover": 11.072, // "fee": -0.005536, // "trade_avg_price": 110.72, // "margin_frozen": 0, // "profit": -0.045, // "trade": [ // { // "trade_fee": -0.005536, // "fee_asset": "USDT", // "real_profit": 0.473, // "profit": -0.045, // "trade_id": 86678766507, // "id": "86678766507-966002354015051776-1", // "trade_volume": 1, // "trade_price": 110.72, // "trade_turnover": 11.072, // "created_at": 1650354508656, // "role": "taker" // } // ], // "canceled_at": 0, // "fee_asset": "USDT", // "margin_asset": "USDT", // "uid": "359305390", // "liquidation_type": "0", // "margin_mode": "cross", // "margin_account": "USDT", // "is_tpsl": 0, // "real_profit": 0.473, // "trade_partition": "USDT", // "reduce_only": 1 // } // // const messageHash = this.safeString2(message, 'ch', 'topic'); const data = this.safeValue(message, 'data'); let marketId = this.safeString(message, 'contract_code'); if (marketId === undefined) { marketId = this.safeString(data, 'symbol'); } const market = this.safeMarket(marketId); let parsedOrder = undefined; if (data !== undefined) { // spot updates const eventType = this.safeString(data, 'eventType'); if (eventType === 'trade') { // when a spot order is filled we get an update message // with the trade info const parsedTrade = this.parseOrderTrade(data, market); // inject trade in existing order by faking an order object const orderId = this.safeString(parsedTrade, 'order'); const trades = [parsedTrade]; const status = this.parseOrderStatus(this.safeString2(data, 'orderStatus', 'status', 'closed')); const filled = this.safeString(data, 'execAmt'); const remaining = this.safeString(data, 'remainAmt'); const order = { 'id': orderId, 'trades': trades, 'status': status, 'symbol': market['symbol'], 'filled': this.parseNumber(filled), 'remaining': this.parseNumber(remaining), }; parsedOrder = order; } else { parsedOrder = this.parseWsOrder(data, market); } } else { // contract branch parsedOrder = this.parseWsOrder(message, market); const rawTrades = this.safeValue(message, 'trade', []); const tradesLength = rawTrades.length; if (tradesLength > 0) { const tradesObject = { 'trades': rawTrades, 'ch': messageHash, 'symbol': marketId, }; // inject order params in every trade const extendTradeParams = { 'order': this.safeString(parsedOrder, 'id'), 'type': this.safeString(parsedOrder, 'type'), 'side': this.safeString(parsedOrder, 'side'), }; // trades arrive inside an order update // we're forwarding them to handleMyTrade // so they can be properly resolved this.handleMyTrade(client, tradesObject, extendTradeParams); } } if (this.orders === undefined) { const limit = this.safeInteger(this.options, 'ordersLimit', 1000); this.orders = new ArrayCacheBySymbolById(limit); } const cachedOrders = this.orders; cachedOrders.append(parsedOrder); client.resolve(this.orders, messageHash); // when we make a global subscription (for contracts only) our message hash can't have a symbol/currency attached // so we're removing it here let genericMessageHash = messageHash.replace('.' + market['lowercaseId'], ''); const lowerCaseBaseId = this.safeStringLower(market, 'baseId'); genericMessageHash = genericMessageHash.replace('.' + lowerCaseBaseId, ''); client.resolve(this.orders, genericMessageHash); } parseWsOrder(order, market = undefined) { // // spot // // { // "orderSource": "spot-web", // "orderCreateTime": 1645116048355, // creating only // "accountId": 44234548, // "orderPrice": "100", // "orderSize": "0.05", // "orderValue": "3.71676361", // market-buy only // "symbol": "ethusdt", // "type": "buy-limit", // "orderId": "478861479986886", // "eventType": "creation", // "clientOrderId": '', // "orderStatus": "submitted" // "lastActTime":1645118621810 // except creating // "execAmt":"0" // } // // swap order // // { // "contract_type": "swap", // "pair": "LTC-USDT", // "business_type": "swap", // "op": "notify", // "topic": "orders_cross.ltc-usdt", // "ts": 1648717911384, // "symbol": "LTC", // "contract_code": "LTC-USDT", // "volume": 1, // "price": 129.13, // "order_price_type": "lightning", // "direction": "sell", // "offset": "close", // "status": 6, // "lever_rate": 5, // "order_id": "959137967397068800", // "order_id_str": "959137967397068800", // "client_order_id": null, // "order_source": "web", // "order_type": 1, // "created_at": 1648717911344, // "trade_volume": 1, // "trade_turnover": 12.952, // "fee": -0.006476, // "trade_avg_price": 129.52, // "margin_frozen": 0, // "profit": -0.005, // "trade": [ // { // "trade_fee": -0.006476, // "fee_asset": "USDT", // "real_profit": -0.005, // "profit": -0.005, // "trade_id": 83619995370, // "id": "83619995370-959137967397068800-1", // "trade_volume": 1, // "trade_price": 129.52, // "trade_turnover": 12.952, // "created_at": 1648717911352, // "role": "taker" // } // ], // "canceled_at": 0, // "fee_asset": "USDT", // "margin_asset": "USDT", // "uid": "359305390", // "liquidation_type": "0", // "margin_mode": "cross", // "margin_account": "USDT", // "is_tpsl": 0, // "real_profit": -0.005, // "trade_partition": "USDT", // "reduce_only": 1 // } // // { // "op":"notify", // "topic":"orders.ada", // "ts":1604388667226, // "symbol":"ADA", // "contract_type":"quarter", // "contract_code":"ADA201225", // "volume":1, // "price":0.0905, // "order_price_type":"post_only", // "direction":"sell", // "offset":"open", // "status":6, // "lever_rate":20, // "order_id":773207641127878656, // "order_id_str":"773207641127878656", // "client_order_id":null, // "order_source":"web", // "order_type":1, // "created_at":1604388667146, // "trade_volume":1, // "trade_turnover":10, // "fee":-0.022099447513812154, // "trade_avg_price":0.0905, // "margin_frozen":0, // "profit":0, // "trade":[], // "canceled_at":0, // "fee_asset":"ADA", // "uid":"123456789", // "liquidation_type":"0", // "is_tpsl": 0, // "real_profit": 0 // } // const lastTradeTimestamp = this.safeInteger2(order, 'lastActTime', 'ts'); const created = this.safeInteger(order, 'orderCreateTime'); const marketId = this.safeString2(order, 'contract_code', 'symbol'); market = this.safeMarket(marketId, market); const symbol = this.safeSymbol(marketId, market); const amount = this.safeString2(order, 'orderSize', 'volume'); const status = this.parseOrderStatus