UNPKG

@proton/ccxt

Version:

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

1,163 lines (1,161 loc) 91.1 kB
'use strict'; var huobi$1 = require('../huobi.js'); var errors = require('../base/errors.js'); var Cache = require('../base/ws/Cache.js'); var sha256 = require('../static_dependencies/noble-hashes/sha256.js'); // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- class huobi extends huobi$1 { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'watchOrderBook': true, 'watchOrders': true, 'watchTickers': false, 'watchTicker': true, 'watchTrades': true, 'watchMyTrades': true, 'watchBalance': true, 'watchOHLCV': true, }, 'urls': { 'api': { 'ws': { 'api': { 'spot': { 'public': 'wss://{hostname}/ws', 'private': 'wss://{hostname}/ws/v2', }, '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', }, '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': { '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', }, }, }, }, }, }, 'options': { 'tradesLimit': 1000, 'OHLCVLimit': 1000, 'api': 'api', 'maxOrderBookSyncAttempts': 3, 'ws': { 'gunzip': true, }, 'watchTicker': { 'name': 'market.{marketId}.detail', // 'market.{marketId}.bbo' or 'market.{marketId}.ticker' }, }, 'exceptions': { 'ws': { 'exact': { 'bad-request': errors.BadRequest, '2002': errors.AuthenticationError, '2021': errors.BadRequest, '2001': errors.BadSymbol, '2011': errors.BadSymbol, '2040': errors.BadRequest, // { op: 'sub', cid: '1649152947', 'err-code': 2040, 'err-msg': 'Missing required parameter.', ts: 1649152948684 } }, }, }, }); } requestId() { const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; return requestId.toString(); } async watchTicker(symbol, params = {}) { /** * @method * @name huobi#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the huobi api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ 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 errors.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; } async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name huobi#watchTrades * @description get the list of most recent trades for a particular symbol * @param {string} symbol unified symbol of the market to fetch trades for * @param {int|undefined} since timestamp in ms of the earliest trade to fetch * @param {int|undefined} limit the maximum amount of trades to fetch * @param {object} params extra parameters specific to the huobi api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades} */ 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 Cache.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; } async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name huobi#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @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|undefined} since timestamp in ms of the earliest candle to fetch * @param {int|undefined} limit the maximum amount of candles to fetch * @param {object} params extra parameters specific to the huobi api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ 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 Cache.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); } async watchOrderBook(symbol, limit = undefined, params = {}) { /** * @method * @name huobi#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|undefined} limit the maximum amount of order book entries to return * @param {object} params extra parameters specific to the huobi api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const allowedSpotLimits = [150]; const allowedSwapLimits = [20, 150]; limit = (limit === undefined) ? 150 : limit; if (market['spot'] && !this.inArray(limit, allowedSpotLimits)) { throw new errors.ExchangeError(this.id + ' watchOrderBook spot market accepts limits of 150 only'); } if (!market['spot'] && !this.inArray(limit, allowedSwapLimits)) { throw new errors.ExchangeError(this.id + ' watchOrderBook swap 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']); 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', // 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'); 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, 'seqNum'); const nonce = this.safeInteger(data, 'seqNum'); snapshot['nonce'] = nonce; const snapshotLimit = this.safeInteger(subscription, 'limit'); const snapshotOrderBook = this.orderBook(snapshot, snapshotLimit); client.resolve(snapshotOrderBook, id); if ((sequence !== undefined) && (nonce < sequence)) { const maxAttempts = this.safeInteger(this.options, 'maxOrderBookSyncAttempts', 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); subscription['numAttempts'] = numAttempts; client.subscriptions[messageHash] = subscription; this.spawn(this.watchOrderBookSnapshot, client, message, subscription); } } else { // throw upon failing to synchronize in maxAttempts throw new errors.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++) { const message = messages[i]; this.handleOrderBookMessage(client, message, orderbook); } this.orderbooks[symbol] = orderbook; client.resolve(orderbook, messageHash); } } catch (e) { client.reject(e, messageHash); } } async watchOrderBookSnapshot(client, message, subscription) { const messageHash = this.safeString(subscription, 'messageHash'); try { const symbol = this.safeString(subscription, 'symbol'); const limit = this.safeInteger(subscription, 'limit'); 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']); 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, 'method': this.handleOrderBookSnapshot, }; const orderbook = await this.watch(url, requestId, request, requestId, snapshotSubscription); return orderbook.limit(); } catch (e) { delete client.subscriptions[messageHash]; client.reject(e, messageHash); } } 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, orderbook) { // 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 symbol = this.safeSymbol(marketId); const tick = this.safeValue(message, 'tick', {}); const seqNum = this.safeInteger2(tick, 'seqNum', 'version'); const prevSeqNum = this.safeInteger(tick, 'prevSeqNum'); const event = this.safeString(tick, 'event'); const timestamp = this.safeInteger(message, 'ts'); if (event === 'snapshot') { const snapshot = this.parseOrderBook(tick, symbol, timestamp); orderbook.reset(snapshot); orderbook['nonce'] = seqNum; } if ((prevSeqNum === undefined || prevSeqNum <= orderbook['nonce']) && (seqNum > orderbook['nonce'])) { const asks = this.safeValue(tick, 'asks', []); const bids = this.safeValue(tick, 'bids', []); this.handleDeltas(orderbook['asks'], asks); this.handleDeltas(orderbook['bids'], bids); orderbook['nonce'] = seqNum; orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601(timestamp); } return orderbook; } 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 tick = this.safeValue(message, 'tick', {}); const event = this.safeString(tick, 'event'); const messageHash = this.safeString(message, 'ch'); const ch = this.safeValue(message, 'ch'); const parts = ch.split('.'); const marketId = this.safeString(parts, 1); const symbol = this.safeSymbol(marketId); let orderbook = this.safeValue(this.orderbooks, symbol); if (orderbook === undefined) { const size = this.safeString(parts, 3); const sizeParts = size.split('_'); const limit = this.safeInteger(sizeParts, 1); orderbook = this.orderBook({}, limit); } if (orderbook['nonce'] === undefined) { orderbook.cache.push(message); } if (event !== undefined || orderbook['nonce'] !== undefined) { this.orderbooks[symbol] = this.handleOrderBookMessage(client, message, orderbook); client.resolve(orderbook, messageHash); } } handleOrderBookSubscription(client, message, subscription) { const symbol = this.safeString(subscription, 'symbol'); const limit = this.safeInteger(subscription, 'limit'); if (symbol in this.orderbooks) { delete this.orderbooks[symbol]; } this.orderbooks[symbol] = this.orderBook({}, limit); if (this.markets[symbol]['spot'] === true) { this.spawn(this.watchOrderBookSnapshot, client, message, subscription); } } async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name huobi#watchMyTrades * @description watches information on multiple trades made by the user * @param {string} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the huobi api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure */ this.checkRequiredCredentials(); let type = undefined; let marketId = '*'; // wildcard let market = undefined; let messageHash = undefined; let channel = undefined; let trades = undefined; let subType = undefined; if (symbol !== undefined) { await this.loadMarkets(); 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'] : undefined; const baseId = (market !== undefined) ? market['lowercaseBaseId'] : 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; 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]; } async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name huobi#watchOrders * @description watches information on multiple orders made by the user * @param {string|undefined} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the huobi api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} */ 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 order = { 'id': orderId, 'trades': trades, 'status': 'closed', 'symbol': market['symbol'], }; 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 Cache.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'], ''); genericMessageHash = genericMessageHash.replace('.' + market['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(this.safeString2(order, 'orderStatus', 'status')); const id = this.safeString2(order, 'orderId', 'order_id'); const clientOrderId = this.safeString2(order, 'clientOrderId', 'client_order_id'); const price = this.safeString2(order, 'orderPrice', 'price'); const filled = this.safeString(order, 'execAmt'); const typeSide = this.safeString(order, 'type'); const feeCost = this.safeString(order, 'fee'); let fee = undefined; if (feeCost !== undefined) { const feeCurrencyId = this.safeString(order, 'fee_asset'); fee = { 'cost': feeCost, 'currency': this.safeCurrencyCode(feeCurrencyId), }; } const avgPrice = this.safeString(order, 'trade_avg_price'); const rawTrades = this.safeValue(order, 'trade'); let typeSideParts = []; if (typeSide !== undefined) { typeSideParts = typeSide.split('-'); } let type = this.safeStringLower(typeSideParts, 1); if (type === undefined) { type = this.safeString(order, 'order_price_type'); } let side = this.safeStringLower(typeSideParts, 0); if (side === undefined) { side = this.safeString(order, 'direction'); } const cost = this.safeString(order, 'orderValue'); return this.safeOrder({ 'info': order, 'id': id, 'clientOrderId': clientOrderId, 'timestamp': created, 'datetime': this.iso8601(created), 'lastTradeTimestamp': lastTradeTimestamp, 'status': status, 'symbol': symbol, 'type': type, 'timeInForce': undefined, 'postOnly': undefined, 'side': side, 'price': price, 'amount': amount, 'filled': filled, 'remaining': undefined, 'cost': cost, 'fee': fee, 'average': avgPrice, 'trades': rawTrades, }, market); } parseOrderTrade(trade, market = undefined) { // spot private wrapped trade // // { // 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 // } // market = this.safeMarket(undefined, market); const symbol = market['symbol']; const tradeId = this.safeString(trade, 'tradeId'); const price = this.safeString(trade, 'tradePrice'); const amount = this.safeString(trade, 'tradeVolume'); const order = this.safeString(trade, 'orderId'); const timestamp = this.safeInteger(trade, 'tradeTime'); let type = this.safeString(trade, 'type'); let side = undefined; if (type !== undefined) { const typeParts = type.split('-'); side = typeParts[0]; type = typeParts[1]; } const aggressor = this.safeValue(trade, 'aggressor'); let takerOrMaker = undefined; if (aggressor !== undefined) { takerOrMaker = aggressor ? '