ccxt 4.1.47 → 4.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +124 -124
  2. package/dist/ccxt.browser.js +670 -460
  3. package/dist/ccxt.browser.min.js +3 -3
  4. package/dist/cjs/ccxt.js +6 -1
  5. package/dist/cjs/src/abstract/htx.js +9 -0
  6. package/dist/cjs/src/binance.js +2 -0
  7. package/dist/cjs/src/bitmart.js +183 -62
  8. package/dist/cjs/src/coinex.js +67 -18
  9. package/dist/cjs/src/htx.js +8505 -0
  10. package/dist/cjs/src/huobi.js +5 -8503
  11. package/dist/cjs/src/kraken.js +0 -4
  12. package/dist/cjs/src/mexc.js +21 -21
  13. package/dist/cjs/src/pro/htx.js +2356 -0
  14. package/dist/cjs/src/pro/huobi.js +5 -2345
  15. package/dist/cjs/src/timex.js +1 -1
  16. package/js/ccxt.d.ts +8 -2
  17. package/js/ccxt.js +6 -2
  18. package/js/src/abstract/binance.d.ts +2 -0
  19. package/js/src/abstract/binancecoinm.d.ts +2 -0
  20. package/js/src/abstract/binanceus.d.ts +2 -0
  21. package/js/src/abstract/binanceusdm.d.ts +2 -0
  22. package/js/src/abstract/htx.d.ts +544 -0
  23. package/js/src/abstract/htx.js +11 -0
  24. package/js/src/abstract/huobi.d.ts +4 -4
  25. package/js/src/abstract/huobi.js +3 -3
  26. package/js/src/ace.d.ts +2 -2
  27. package/js/src/ascendex.d.ts +2 -2
  28. package/js/src/base/Exchange.d.ts +2 -2
  29. package/js/src/base/types.d.ts +0 -1
  30. package/js/src/bigone.d.ts +2 -2
  31. package/js/src/binance.d.ts +3 -56
  32. package/js/src/binance.js +2 -0
  33. package/js/src/bingx.d.ts +3 -55
  34. package/js/src/bitbns.d.ts +2 -2
  35. package/js/src/bitfinex.d.ts +2 -2
  36. package/js/src/bitfinex2.d.ts +2 -2
  37. package/js/src/bitget.d.ts +3 -53
  38. package/js/src/bithumb.d.ts +2 -2
  39. package/js/src/bitmart.d.ts +4 -2
  40. package/js/src/bitmart.js +183 -62
  41. package/js/src/bitmex.d.ts +2 -2
  42. package/js/src/bitopro.d.ts +2 -2
  43. package/js/src/bitpanda.d.ts +2 -2
  44. package/js/src/bitrue.d.ts +2 -2
  45. package/js/src/bitstamp.d.ts +2 -2
  46. package/js/src/bittrex.d.ts +2 -2
  47. package/js/src/bitvavo.d.ts +2 -2
  48. package/js/src/blockchaincom.d.ts +2 -2
  49. package/js/src/btcalpha.d.ts +2 -2
  50. package/js/src/btcturk.d.ts +2 -2
  51. package/js/src/bybit.d.ts +2 -2
  52. package/js/src/cex.d.ts +2 -2
  53. package/js/src/coinbase.d.ts +2 -2
  54. package/js/src/coinbasepro.d.ts +2 -2
  55. package/js/src/coinex.d.ts +2 -2
  56. package/js/src/coinex.js +67 -18
  57. package/js/src/coinfalcon.d.ts +2 -2
  58. package/js/src/coinlist.d.ts +2 -2
  59. package/js/src/coinone.d.ts +2 -2
  60. package/js/src/coinsph.d.ts +2 -2
  61. package/js/src/coinspot.d.ts +2 -2
  62. package/js/src/cryptocom.d.ts +2 -2
  63. package/js/src/currencycom.d.ts +2 -2
  64. package/js/src/delta.d.ts +2 -2
  65. package/js/src/deribit.d.ts +2 -2
  66. package/js/src/digifinex.d.ts +2 -2
  67. package/js/src/exmo.d.ts +2 -2
  68. package/js/src/gate.d.ts +2 -2
  69. package/js/src/gemini.d.ts +3 -51
  70. package/js/src/hitbtc.d.ts +2 -2
  71. package/js/src/hollaex.d.ts +2 -2
  72. package/js/src/htx.d.ts +257 -0
  73. package/js/src/htx.js +8506 -0
  74. package/js/src/huobi.d.ts +2 -255
  75. package/js/src/huobi.js +5 -8503
  76. package/js/src/huobijp.d.ts +2 -2
  77. package/js/src/idex.d.ts +2 -2
  78. package/js/src/indodax.d.ts +2 -2
  79. package/js/src/kraken.d.ts +2 -2
  80. package/js/src/kraken.js +0 -4
  81. package/js/src/krakenfutures.d.ts +2 -2
  82. package/js/src/kucoin.d.ts +2 -2
  83. package/js/src/kuna.d.ts +2 -2
  84. package/js/src/latoken.d.ts +2 -2
  85. package/js/src/lbank.d.ts +2 -2
  86. package/js/src/lbank2.d.ts +2 -2
  87. package/js/src/luno.d.ts +2 -2
  88. package/js/src/lykke.d.ts +2 -2
  89. package/js/src/mexc.d.ts +2 -2
  90. package/js/src/mexc.js +21 -21
  91. package/js/src/novadax.d.ts +2 -2
  92. package/js/src/oceanex.d.ts +2 -2
  93. package/js/src/okcoin.d.ts +3 -3
  94. package/js/src/okx.d.ts +3 -3
  95. package/js/src/phemex.d.ts +2 -2
  96. package/js/src/poloniex.d.ts +2 -2
  97. package/js/src/poloniexfutures.d.ts +2 -2
  98. package/js/src/pro/htx.d.ts +45 -0
  99. package/js/src/pro/htx.js +2357 -0
  100. package/js/src/pro/huobi.d.ts +2 -43
  101. package/js/src/pro/huobi.js +5 -2345
  102. package/js/src/probit.d.ts +2 -2
  103. package/js/src/tidex.d.ts +2 -2
  104. package/js/src/timex.d.ts +3 -53
  105. package/js/src/timex.js +1 -1
  106. package/js/src/tokocrypto.d.ts +2 -2
  107. package/js/src/upbit.d.ts +2 -2
  108. package/js/src/wavesexchange.d.ts +2 -2
  109. package/js/src/wazirx.d.ts +2 -2
  110. package/js/src/whitebit.d.ts +2 -2
  111. package/js/src/yobit.d.ts +2 -2
  112. package/js/src/zonda.d.ts +2 -2
  113. package/package.json +1 -1
  114. package/skip-tests.json +4 -4
@@ -0,0 +1,2357 @@
1
+ // ----------------------------------------------------------------------------
2
+
3
+ // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
4
+ // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
5
+ // EDIT THE CORRESPONDENT .ts FILE INSTEAD
6
+
7
+ // ---------------------------------------------------------------------------
8
+ import htxRest from '../htx.js';
9
+ import { ExchangeError, InvalidNonce, ArgumentsRequired, BadRequest, BadSymbol, AuthenticationError, NetworkError } from '../base/errors.js';
10
+ import { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide } from '../base/ws/Cache.js';
11
+ import { sha256 } from '../static_dependencies/noble-hashes/sha256.js';
12
+ // ---------------------------------------------------------------------------
13
+ export default class htx extends htxRest {
14
+ describe() {
15
+ return this.deepExtend(super.describe(), {
16
+ 'has': {
17
+ 'ws': true,
18
+ 'watchOrderBook': true,
19
+ 'watchOrders': true,
20
+ 'watchTickers': false,
21
+ 'watchTicker': true,
22
+ 'watchTrades': true,
23
+ 'watchMyTrades': true,
24
+ 'watchBalance': true,
25
+ 'watchOHLCV': true,
26
+ },
27
+ 'urls': {
28
+ 'api': {
29
+ 'ws': {
30
+ 'api': {
31
+ 'spot': {
32
+ 'public': 'wss://{hostname}/ws',
33
+ 'private': 'wss://{hostname}/ws/v2',
34
+ },
35
+ 'future': {
36
+ 'linear': {
37
+ 'public': 'wss://api.hbdm.com/linear-swap-ws',
38
+ 'private': 'wss://api.hbdm.com/linear-swap-notification',
39
+ },
40
+ 'inverse': {
41
+ 'public': 'wss://api.hbdm.com/ws',
42
+ 'private': 'wss://api.hbdm.com/notification',
43
+ },
44
+ },
45
+ 'swap': {
46
+ 'inverse': {
47
+ 'public': 'wss://api.hbdm.com/swap-ws',
48
+ 'private': 'wss://api.hbdm.com/swap-notification',
49
+ },
50
+ 'linear': {
51
+ 'public': 'wss://api.hbdm.com/linear-swap-ws',
52
+ 'private': 'wss://api.hbdm.com/linear-swap-notification',
53
+ },
54
+ },
55
+ },
56
+ // these settings work faster for clients hosted on AWS
57
+ 'api-aws': {
58
+ 'spot': {
59
+ 'public': 'wss://api-aws.huobi.pro/ws',
60
+ 'private': 'wss://api-aws.huobi.pro/ws/v2',
61
+ },
62
+ 'future': {
63
+ 'linear': {
64
+ 'public': 'wss://api.hbdm.vn/linear-swap-ws',
65
+ 'private': 'wss://api.hbdm.vn/linear-swap-notification',
66
+ },
67
+ 'inverse': {
68
+ 'public': 'wss://api.hbdm.vn/ws',
69
+ 'private': 'wss://api.hbdm.vn/notification',
70
+ },
71
+ },
72
+ 'swap': {
73
+ 'linear': {
74
+ 'public': 'wss://api.hbdm.vn/linear-swap-ws',
75
+ 'private': 'wss://api.hbdm.vn/linear-swap-notification',
76
+ },
77
+ 'inverse': {
78
+ 'public': 'wss://api.hbdm.vn/swap-ws',
79
+ 'private': 'wss://api.hbdm.vn/swap-notification',
80
+ },
81
+ },
82
+ },
83
+ },
84
+ },
85
+ },
86
+ 'options': {
87
+ 'tradesLimit': 1000,
88
+ 'OHLCVLimit': 1000,
89
+ 'api': 'api',
90
+ 'watchOrderBook': {
91
+ 'maxRetries': 3,
92
+ },
93
+ 'ws': {
94
+ 'gunzip': true,
95
+ },
96
+ 'watchTicker': {
97
+ 'name': 'market.{marketId}.detail', // 'market.{marketId}.bbo' or 'market.{marketId}.ticker'
98
+ },
99
+ },
100
+ 'exceptions': {
101
+ 'ws': {
102
+ 'exact': {
103
+ 'bad-request': BadRequest,
104
+ '2002': AuthenticationError,
105
+ '2021': BadRequest,
106
+ '2001': BadSymbol,
107
+ '2011': BadSymbol,
108
+ '2040': BadRequest,
109
+ '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 }
110
+ },
111
+ },
112
+ },
113
+ });
114
+ }
115
+ requestId() {
116
+ const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1);
117
+ this.options['requestId'] = requestId;
118
+ return requestId.toString();
119
+ }
120
+ async watchTicker(symbol, params = {}) {
121
+ /**
122
+ * @method
123
+ * @name huobi#watchTicker
124
+ * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
125
+ * @param {string} symbol unified symbol of the market to fetch the ticker for
126
+ * @param {object} [params] extra parameters specific to the huobi api endpoint
127
+ * @returns {object} a [ticker structure]{@link https://github.com/ccxt/ccxt/wiki/Manual#ticker-structure}
128
+ */
129
+ await this.loadMarkets();
130
+ const market = this.market(symbol);
131
+ symbol = market['symbol'];
132
+ const options = this.safeValue(this.options, 'watchTicker', {});
133
+ const topic = this.safeString(options, 'name', 'market.{marketId}.detail');
134
+ if (topic === 'market.{marketId}.ticker' && market['type'] !== 'spot') {
135
+ throw new BadRequest(this.id + ' watchTicker() with name market.{marketId}.ticker is only allowed for spot markets, use market.{marketId}.detail instead');
136
+ }
137
+ const messageHash = this.implodeParams(topic, { 'marketId': market['id'] });
138
+ const url = this.getUrlByMarketType(market['type'], market['linear']);
139
+ return await this.subscribePublic(url, symbol, messageHash, undefined, params);
140
+ }
141
+ handleTicker(client, message) {
142
+ //
143
+ // "market.btcusdt.detail"
144
+ // {
145
+ // "ch": "market.btcusdt.detail",
146
+ // "ts": 1583494163784,
147
+ // "tick": {
148
+ // "id": 209988464418,
149
+ // "low": 8988,
150
+ // "high": 9155.41,
151
+ // "open": 9078.91,
152
+ // "close": 9136.46,
153
+ // "vol": 237813910.5928412,
154
+ // "amount": 26184.202558551195,
155
+ // "version": 209988464418,
156
+ // "count": 265673
157
+ // }
158
+ // }
159
+ // "market.btcusdt.bbo"
160
+ // {
161
+ // "ch": "market.btcusdt.bbo",
162
+ // "ts": 1671941599613,
163
+ // "tick": {
164
+ // "seqId": 161499562790,
165
+ // "ask": 16829.51,
166
+ // "askSize": 0.707776,
167
+ // "bid": 16829.5,
168
+ // "bidSize": 1.685945,
169
+ // "quoteTime": 1671941599612,
170
+ // "symbol": "btcusdt"
171
+ // }
172
+ // }
173
+ //
174
+ const tick = this.safeValue(message, 'tick', {});
175
+ const ch = this.safeString(message, 'ch');
176
+ const parts = ch.split('.');
177
+ const marketId = this.safeString(parts, 1);
178
+ const market = this.safeMarket(marketId);
179
+ const ticker = this.parseTicker(tick, market);
180
+ const timestamp = this.safeValue(message, 'ts');
181
+ ticker['timestamp'] = timestamp;
182
+ ticker['datetime'] = this.iso8601(timestamp);
183
+ const symbol = ticker['symbol'];
184
+ this.tickers[symbol] = ticker;
185
+ client.resolve(ticker, ch);
186
+ return message;
187
+ }
188
+ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
189
+ /**
190
+ * @method
191
+ * @name huobi#watchTrades
192
+ * @description get the list of most recent trades for a particular symbol
193
+ * @param {string} symbol unified symbol of the market to fetch trades for
194
+ * @param {int} [since] timestamp in ms of the earliest trade to fetch
195
+ * @param {int} [limit] the maximum amount of trades to fetch
196
+ * @param {object} [params] extra parameters specific to the huobi api endpoint
197
+ * @returns {object[]} a list of [trade structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#public-trades}
198
+ */
199
+ await this.loadMarkets();
200
+ const market = this.market(symbol);
201
+ symbol = market['symbol'];
202
+ const messageHash = 'market.' + market['id'] + '.trade.detail';
203
+ const url = this.getUrlByMarketType(market['type'], market['linear']);
204
+ const trades = await this.subscribePublic(url, symbol, messageHash, undefined, params);
205
+ if (this.newUpdates) {
206
+ limit = trades.getLimit(symbol, limit);
207
+ }
208
+ return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
209
+ }
210
+ handleTrades(client, message) {
211
+ //
212
+ // {
213
+ // "ch": "market.btcusdt.trade.detail",
214
+ // "ts": 1583495834011,
215
+ // "tick": {
216
+ // "id": 105004645372,
217
+ // "ts": 1583495833751,
218
+ // "data": [
219
+ // {
220
+ // "id": 1.050046453727319e+22,
221
+ // "ts": 1583495833751,
222
+ // "tradeId": 102090727790,
223
+ // "amount": 0.003893,
224
+ // "price": 9150.01,
225
+ // "direction": "sell"
226
+ // }
227
+ // ]
228
+ // }
229
+ // }
230
+ //
231
+ const tick = this.safeValue(message, 'tick', {});
232
+ const data = this.safeValue(tick, 'data', {});
233
+ const ch = this.safeString(message, 'ch');
234
+ const parts = ch.split('.');
235
+ const marketId = this.safeString(parts, 1);
236
+ const market = this.safeMarket(marketId);
237
+ const symbol = market['symbol'];
238
+ let tradesCache = this.safeValue(this.trades, symbol);
239
+ if (tradesCache === undefined) {
240
+ const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
241
+ tradesCache = new ArrayCache(limit);
242
+ this.trades[symbol] = tradesCache;
243
+ }
244
+ for (let i = 0; i < data.length; i++) {
245
+ const trade = this.parseTrade(data[i], market);
246
+ tradesCache.append(trade);
247
+ }
248
+ client.resolve(tradesCache, ch);
249
+ return message;
250
+ }
251
+ async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
252
+ /**
253
+ * @method
254
+ * @name huobi#watchOHLCV
255
+ * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
256
+ * @param {string} symbol unified symbol of the market to fetch OHLCV data for
257
+ * @param {string} timeframe the length of time each candle represents
258
+ * @param {int} [since] timestamp in ms of the earliest candle to fetch
259
+ * @param {int} [limit] the maximum amount of candles to fetch
260
+ * @param {object} [params] extra parameters specific to the huobi api endpoint
261
+ * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
262
+ */
263
+ await this.loadMarkets();
264
+ const market = this.market(symbol);
265
+ symbol = market['symbol'];
266
+ const interval = this.safeString(this.timeframes, timeframe, timeframe);
267
+ const messageHash = 'market.' + market['id'] + '.kline.' + interval;
268
+ const url = this.getUrlByMarketType(market['type'], market['linear']);
269
+ const ohlcv = await this.subscribePublic(url, symbol, messageHash, undefined, params);
270
+ if (this.newUpdates) {
271
+ limit = ohlcv.getLimit(symbol, limit);
272
+ }
273
+ return this.filterBySinceLimit(ohlcv, since, limit, 0, true);
274
+ }
275
+ handleOHLCV(client, message) {
276
+ //
277
+ // {
278
+ // "ch": "market.btcusdt.kline.1min",
279
+ // "ts": 1583501786794,
280
+ // "tick": {
281
+ // "id": 1583501760,
282
+ // "open": 9094.5,
283
+ // "close": 9094.51,
284
+ // "low": 9094.5,
285
+ // "high": 9094.51,
286
+ // "amount": 0.44639786263800907,
287
+ // "vol": 4059.76919054,
288
+ // "count": 16
289
+ // }
290
+ // }
291
+ //
292
+ const ch = this.safeString(message, 'ch');
293
+ const parts = ch.split('.');
294
+ const marketId = this.safeString(parts, 1);
295
+ const market = this.safeMarket(marketId);
296
+ const symbol = market['symbol'];
297
+ const interval = this.safeString(parts, 3);
298
+ const timeframe = this.findTimeframe(interval);
299
+ this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {});
300
+ let stored = this.safeValue(this.ohlcvs[symbol], timeframe);
301
+ if (stored === undefined) {
302
+ const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
303
+ stored = new ArrayCacheByTimestamp(limit);
304
+ this.ohlcvs[symbol][timeframe] = stored;
305
+ }
306
+ const tick = this.safeValue(message, 'tick');
307
+ const parsed = this.parseOHLCV(tick, market);
308
+ stored.append(parsed);
309
+ client.resolve(stored, ch);
310
+ }
311
+ async watchOrderBook(symbol, limit = undefined, params = {}) {
312
+ /**
313
+ * @method
314
+ * @name huobi#watchOrderBook
315
+ * @see https://huobiapi.github.io/docs/dm/v1/en/#subscribe-market-depth-data
316
+ * @see https://huobiapi.github.io/docs/coin_margined_swap/v1/en/#subscribe-incremental-market-depth-data
317
+ * @see https://huobiapi.github.io/docs/usdt_swap/v1/en/#general-subscribe-incremental-market-depth-data
318
+ * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
319
+ * @param {string} symbol unified symbol of the market to fetch the order book for
320
+ * @param {int} [limit] the maximum amount of order book entries to return
321
+ * @param {object} [params] extra parameters specific to the huobi api endpoint
322
+ * @returns {object} A dictionary of [order book structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure} indexed by market symbols
323
+ */
324
+ await this.loadMarkets();
325
+ const market = this.market(symbol);
326
+ symbol = market['symbol'];
327
+ const allowedSpotLimits = [150];
328
+ const allowedSwapLimits = [20, 150];
329
+ limit = (limit === undefined) ? 150 : limit;
330
+ if (market['spot'] && !this.inArray(limit, allowedSpotLimits)) {
331
+ throw new ExchangeError(this.id + ' watchOrderBook spot market accepts limits of 150 only');
332
+ }
333
+ if (!market['spot'] && !this.inArray(limit, allowedSwapLimits)) {
334
+ throw new ExchangeError(this.id + ' watchOrderBook swap market accepts limits of 20 and 150 only');
335
+ }
336
+ let messageHash = undefined;
337
+ if (market['spot']) {
338
+ messageHash = 'market.' + market['id'] + '.mbp.' + limit.toString();
339
+ }
340
+ else {
341
+ messageHash = 'market.' + market['id'] + '.depth.size_' + limit.toString() + '.high_freq';
342
+ }
343
+ const url = this.getUrlByMarketType(market['type'], market['linear']);
344
+ let method = this.handleOrderBookSubscription;
345
+ if (!market['spot']) {
346
+ params = this.extend(params);
347
+ params['data_type'] = 'incremental';
348
+ method = undefined;
349
+ }
350
+ const orderbook = await this.subscribePublic(url, symbol, messageHash, method, params);
351
+ return orderbook.limit();
352
+ }
353
+ handleOrderBookSnapshot(client, message, subscription) {
354
+ //
355
+ // {
356
+ // "id": 1583473663565,
357
+ // "rep": "market.btcusdt.mbp.150",
358
+ // "status": "ok",
359
+ // "ts": 1698359289261,
360
+ // "data": {
361
+ // "seqNum": 104999417756,
362
+ // "bids": [
363
+ // [9058.27, 0],
364
+ // [9058.43, 0],
365
+ // [9058.99, 0],
366
+ // ],
367
+ // "asks": [
368
+ // [9084.27, 0.2],
369
+ // [9085.69, 0],
370
+ // [9085.81, 0],
371
+ // ]
372
+ // }
373
+ // }
374
+ //
375
+ const symbol = this.safeString(subscription, 'symbol');
376
+ const messageHash = this.safeString(subscription, 'messageHash');
377
+ const id = this.safeString(message, 'id');
378
+ try {
379
+ const orderbook = this.orderbooks[symbol];
380
+ const data = this.safeValue(message, 'data');
381
+ const messages = orderbook.cache;
382
+ const firstMessage = this.safeValue(messages, 0, {});
383
+ const snapshot = this.parseOrderBook(data, symbol);
384
+ const tick = this.safeValue(firstMessage, 'tick');
385
+ const sequence = this.safeInteger(tick, 'seqNum');
386
+ const nonce = this.safeInteger(data, 'seqNum');
387
+ snapshot['nonce'] = nonce;
388
+ const timestamp = this.safeInteger(message, 'ts');
389
+ snapshot['timestamp'] = timestamp;
390
+ snapshot['datetime'] = this.iso8601(timestamp);
391
+ const snapshotLimit = this.safeInteger(subscription, 'limit');
392
+ const snapshotOrderBook = this.orderBook(snapshot, snapshotLimit);
393
+ client.resolve(snapshotOrderBook, id);
394
+ if ((sequence !== undefined) && (nonce < sequence)) {
395
+ const maxAttempts = this.handleOption('watchOrderBook', 'maxRetries', 3);
396
+ let numAttempts = this.safeInteger(subscription, 'numAttempts', 0);
397
+ // retry to synchronize if we have not reached maxAttempts yet
398
+ if (numAttempts < maxAttempts) {
399
+ // safety guard
400
+ if (messageHash in client.subscriptions) {
401
+ numAttempts = this.sum(numAttempts, 1);
402
+ subscription['numAttempts'] = numAttempts;
403
+ client.subscriptions[messageHash] = subscription;
404
+ this.spawn(this.watchOrderBookSnapshot, client, message, subscription);
405
+ }
406
+ }
407
+ else {
408
+ // throw upon failing to synchronize in maxAttempts
409
+ throw new InvalidNonce(this.id + ' failed to synchronize WebSocket feed with the snapshot for symbol ' + symbol + ' in ' + maxAttempts.toString() + ' attempts');
410
+ }
411
+ }
412
+ else {
413
+ orderbook.reset(snapshot);
414
+ // unroll the accumulated deltas
415
+ for (let i = 0; i < messages.length; i++) {
416
+ this.handleOrderBookMessage(client, messages[i], orderbook);
417
+ }
418
+ this.orderbooks[symbol] = orderbook;
419
+ client.resolve(orderbook, messageHash);
420
+ }
421
+ }
422
+ catch (e) {
423
+ client.reject(e, messageHash);
424
+ }
425
+ }
426
+ async watchOrderBookSnapshot(client, message, subscription) {
427
+ const messageHash = this.safeString(subscription, 'messageHash');
428
+ try {
429
+ const symbol = this.safeString(subscription, 'symbol');
430
+ const limit = this.safeInteger(subscription, 'limit');
431
+ const params = this.safeValue(subscription, 'params');
432
+ const attempts = this.safeInteger(subscription, 'numAttempts', 0);
433
+ const market = this.market(symbol);
434
+ const url = this.getUrlByMarketType(market['type'], market['linear']);
435
+ const requestId = this.requestId();
436
+ const request = {
437
+ 'req': messageHash,
438
+ 'id': requestId,
439
+ };
440
+ // this is a temporary subscription by a specific requestId
441
+ // it has a very short lifetime until the snapshot is received over ws
442
+ const snapshotSubscription = {
443
+ 'id': requestId,
444
+ 'messageHash': messageHash,
445
+ 'symbol': symbol,
446
+ 'limit': limit,
447
+ 'params': params,
448
+ 'numAttempts': attempts,
449
+ 'method': this.handleOrderBookSnapshot,
450
+ };
451
+ const orderbook = await this.watch(url, requestId, request, requestId, snapshotSubscription);
452
+ return orderbook.limit();
453
+ }
454
+ catch (e) {
455
+ delete client.subscriptions[messageHash];
456
+ client.reject(e, messageHash);
457
+ }
458
+ }
459
+ handleDelta(bookside, delta) {
460
+ const price = this.safeFloat(delta, 0);
461
+ const amount = this.safeFloat(delta, 1);
462
+ bookside.store(price, amount);
463
+ }
464
+ handleDeltas(bookside, deltas) {
465
+ for (let i = 0; i < deltas.length; i++) {
466
+ this.handleDelta(bookside, deltas[i]);
467
+ }
468
+ }
469
+ handleOrderBookMessage(client, message, orderbook) {
470
+ // spot markets
471
+ //
472
+ // {
473
+ // "ch": "market.btcusdt.mbp.150",
474
+ // "ts": 1583472025885,
475
+ // "tick": {
476
+ // "seqNum": 104998984994,
477
+ // "prevSeqNum": 104998984977,
478
+ // "bids": [
479
+ // [9058.27, 0],
480
+ // [9058.43, 0],
481
+ // [9058.99, 0],
482
+ // ],
483
+ // "asks": [
484
+ // [9084.27, 0.2],
485
+ // [9085.69, 0],
486
+ // [9085.81, 0],
487
+ // ]
488
+ // }
489
+ // }
490
+ //
491
+ // non-spot market update
492
+ //
493
+ // {
494
+ // "ch":"market.BTC220218.depth.size_150.high_freq",
495
+ // "tick":{
496
+ // "asks":[],
497
+ // "bids":[
498
+ // [43445.74,1],
499
+ // [43444.48,0 ],
500
+ // [40593.92,9]
501
+ // ],
502
+ // "ch":"market.BTC220218.depth.size_150.high_freq",
503
+ // "event":"update",
504
+ // "id":152727500274,
505
+ // "mrid":152727500274,
506
+ // "ts":1645023376098,
507
+ // "version":37536690
508
+ // },
509
+ // "ts":1645023376098
510
+ // }
511
+ // non-spot market snapshot
512
+ //
513
+ // {
514
+ // "ch":"market.BTC220218.depth.size_150.high_freq",
515
+ // "tick":{
516
+ // "asks":[
517
+ // [43445.74,1],
518
+ // [43444.48,0 ],
519
+ // [40593.92,9]
520
+ // ],
521
+ // "bids":[
522
+ // [43445.74,1],
523
+ // [43444.48,0 ],
524
+ // [40593.92,9]
525
+ // ],
526
+ // "ch":"market.BTC220218.depth.size_150.high_freq",
527
+ // "event":"snapshot",
528
+ // "id":152727500274,
529
+ // "mrid":152727500274,
530
+ // "ts":1645023376098,
531
+ // "version":37536690
532
+ // },
533
+ // "ts":1645023376098
534
+ // }
535
+ //
536
+ const ch = this.safeValue(message, 'ch');
537
+ const parts = ch.split('.');
538
+ const marketId = this.safeString(parts, 1);
539
+ const symbol = this.safeSymbol(marketId);
540
+ const tick = this.safeValue(message, 'tick', {});
541
+ const seqNum = this.safeInteger2(tick, 'seqNum', 'version');
542
+ const prevSeqNum = this.safeInteger(tick, 'prevSeqNum');
543
+ const event = this.safeString(tick, 'event');
544
+ const timestamp = this.safeInteger(message, 'ts');
545
+ if (event === 'snapshot') {
546
+ const snapshot = this.parseOrderBook(tick, symbol, timestamp);
547
+ orderbook.reset(snapshot);
548
+ orderbook['nonce'] = seqNum;
549
+ }
550
+ if (prevSeqNum !== undefined && prevSeqNum > orderbook['nonce']) {
551
+ throw new InvalidNonce(this.id + ' watchOrderBook() received a mesage out of order');
552
+ }
553
+ if ((prevSeqNum === undefined || prevSeqNum <= orderbook['nonce']) && (seqNum > orderbook['nonce'])) {
554
+ const asks = this.safeValue(tick, 'asks', []);
555
+ const bids = this.safeValue(tick, 'bids', []);
556
+ this.handleDeltas(orderbook['asks'], asks);
557
+ this.handleDeltas(orderbook['bids'], bids);
558
+ orderbook['nonce'] = seqNum;
559
+ orderbook['timestamp'] = timestamp;
560
+ orderbook['datetime'] = this.iso8601(timestamp);
561
+ }
562
+ return orderbook;
563
+ }
564
+ handleOrderBook(client, message) {
565
+ //
566
+ // deltas
567
+ //
568
+ // spot markets
569
+ //
570
+ // {
571
+ // "ch": "market.btcusdt.mbp.150",
572
+ // "ts": 1583472025885,
573
+ // "tick": {
574
+ // "seqNum": 104998984994,
575
+ // "prevSeqNum": 104998984977,
576
+ // "bids": [
577
+ // [9058.27, 0],
578
+ // [9058.43, 0],
579
+ // [9058.99, 0],
580
+ // ],
581
+ // "asks": [
582
+ // [9084.27, 0.2],
583
+ // [9085.69, 0],
584
+ // [9085.81, 0],
585
+ // ]
586
+ // }
587
+ // }
588
+ //
589
+ // non spot markets
590
+ //
591
+ // {
592
+ // "ch":"market.BTC220218.depth.size_150.high_freq",
593
+ // "tick":{
594
+ // "asks":[],
595
+ // "bids":[
596
+ // [43445.74,1],
597
+ // [43444.48,0 ],
598
+ // [40593.92,9]
599
+ // ],
600
+ // "ch":"market.BTC220218.depth.size_150.high_freq",
601
+ // "event":"update",
602
+ // "id":152727500274,
603
+ // "mrid":152727500274,
604
+ // "ts":1645023376098,
605
+ // "version":37536690
606
+ // },
607
+ // "ts":1645023376098
608
+ // }
609
+ //
610
+ const tick = this.safeValue(message, 'tick', {});
611
+ const event = this.safeString(tick, 'event');
612
+ const messageHash = this.safeString(message, 'ch');
613
+ const ch = this.safeValue(message, 'ch');
614
+ const parts = ch.split('.');
615
+ const marketId = this.safeString(parts, 1);
616
+ const symbol = this.safeSymbol(marketId);
617
+ let orderbook = this.safeValue(this.orderbooks, symbol);
618
+ if (orderbook === undefined) {
619
+ const size = this.safeString(parts, 3);
620
+ const sizeParts = size.split('_');
621
+ const limit = this.safeInteger(sizeParts, 1);
622
+ orderbook = this.orderBook({}, limit);
623
+ }
624
+ if (orderbook['nonce'] === undefined) {
625
+ orderbook.cache.push(message);
626
+ }
627
+ if (event !== undefined || orderbook['nonce'] !== undefined) {
628
+ this.orderbooks[symbol] = this.handleOrderBookMessage(client, message, orderbook);
629
+ client.resolve(orderbook, messageHash);
630
+ }
631
+ }
632
+ handleOrderBookSubscription(client, message, subscription) {
633
+ const symbol = this.safeString(subscription, 'symbol');
634
+ const limit = this.safeInteger(subscription, 'limit');
635
+ if (symbol in this.orderbooks) {
636
+ delete this.orderbooks[symbol];
637
+ }
638
+ this.orderbooks[symbol] = this.orderBook({}, limit);
639
+ if (this.markets[symbol]['spot'] === true) {
640
+ this.spawn(this.watchOrderBookSnapshot, client, message, subscription);
641
+ }
642
+ }
643
+ async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
644
+ /**
645
+ * @method
646
+ * @name huobi#watchMyTrades
647
+ * @description watches information on multiple trades made by the user
648
+ * @param {string} symbol unified market symbol of the market trades were made in
649
+ * @param {int} [since] the earliest time in ms to fetch trades for
650
+ * @param {int} [limit] the maximum number of trade structures to retrieve
651
+ * @param {object} [params] extra parameters specific to the huobi api endpoint
652
+ * @returns {object[]} a list of [trade structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#trade-structure
653
+ */
654
+ this.checkRequiredCredentials();
655
+ await this.loadMarkets();
656
+ let type = undefined;
657
+ let marketId = '*'; // wildcard
658
+ let market = undefined;
659
+ let messageHash = undefined;
660
+ let channel = undefined;
661
+ let trades = undefined;
662
+ let subType = undefined;
663
+ if (symbol !== undefined) {
664
+ market = this.market(symbol);
665
+ symbol = market['symbol'];
666
+ type = market['type'];
667
+ subType = market['linear'] ? 'linear' : 'inverse';
668
+ marketId = market['lowercaseId'];
669
+ }
670
+ else {
671
+ type = this.safeString(this.options, 'defaultType', 'spot');
672
+ type = this.safeString(params, 'type', type);
673
+ subType = this.safeString2(this.options, 'subType', 'defaultSubType', 'linear');
674
+ subType = this.safeString(params, 'subType', subType);
675
+ params = this.omit(params, ['type', 'subType']);
676
+ }
677
+ if (type === 'spot') {
678
+ let mode = undefined;
679
+ if (mode === undefined) {
680
+ mode = this.safeString2(this.options, 'watchMyTrades', 'mode', '0');
681
+ mode = this.safeString(params, 'mode', mode);
682
+ params = this.omit(params, 'mode');
683
+ }
684
+ messageHash = 'trade.clearing' + '#' + marketId + '#' + mode;
685
+ channel = messageHash;
686
+ }
687
+ else {
688
+ const channelAndMessageHash = this.getOrderChannelAndMessageHash(type, subType, market, params);
689
+ channel = this.safeString(channelAndMessageHash, 0);
690
+ const orderMessageHash = this.safeString(channelAndMessageHash, 1);
691
+ // we will take advantage of the order messageHash because already handles stuff
692
+ // like symbol/margin/subtype/type variations
693
+ messageHash = orderMessageHash + ':' + 'trade';
694
+ }
695
+ trades = await this.subscribePrivate(channel, messageHash, type, subType, params);
696
+ if (this.newUpdates) {
697
+ limit = trades.getLimit(symbol, limit);
698
+ }
699
+ return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true);
700
+ }
701
+ getOrderChannelAndMessageHash(type, subType, market = undefined, params = {}) {
702
+ let messageHash = undefined;
703
+ let channel = undefined;
704
+ let orderType = this.safeString(this.options, 'orderType', 'orders'); // orders or matchOrders
705
+ orderType = this.safeString(params, 'orderType', orderType);
706
+ params = this.omit(params, 'orderType');
707
+ const marketCode = (market !== undefined) ? market['lowercaseId'] : undefined;
708
+ const baseId = (market !== undefined) ? market['lowercaseBaseId'] : undefined;
709
+ const prefix = orderType;
710
+ messageHash = prefix;
711
+ if (subType === 'linear') {
712
+ // USDT Margined Contracts Example: LTC/USDT:USDT
713
+ const marginMode = this.safeString(params, 'margin', 'cross');
714
+ const marginPrefix = (marginMode === 'cross') ? prefix + '_cross' : prefix;
715
+ messageHash = marginPrefix;
716
+ if (marketCode !== undefined) {
717
+ messageHash += '.' + marketCode;
718
+ channel = messageHash;
719
+ }
720
+ else {
721
+ channel = marginPrefix + '.' + '*';
722
+ }
723
+ }
724
+ else if (type === 'future') {
725
+ // inverse futures Example: BCH/USD:BCH-220408
726
+ if (baseId !== undefined) {
727
+ channel = prefix + '.' + baseId;
728
+ messageHash = channel;
729
+ }
730
+ else {
731
+ channel = prefix + '.' + '*';
732
+ }
733
+ }
734
+ else {
735
+ // inverse swaps: Example: BTC/USD:BTC
736
+ if (marketCode !== undefined) {
737
+ channel = prefix + '.' + marketCode;
738
+ messageHash = channel;
739
+ }
740
+ else {
741
+ channel = prefix + '.' + '*';
742
+ }
743
+ }
744
+ return [channel, messageHash];
745
+ }
746
+ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
747
+ /**
748
+ * @method
749
+ * @name huobi#watchOrders
750
+ * @description watches information on multiple orders made by the user
751
+ * @param {string} symbol unified market symbol of the market orders were made in
752
+ * @param {int} [since] the earliest time in ms to fetch orders for
753
+ * @param {int} [limit] the maximum number of orde structures to retrieve
754
+ * @param {object} [params] extra parameters specific to the huobi api endpoint
755
+ * @returns {object[]} a list of [order structures]{@link https://github.com/ccxt/ccxt/wiki/Manual#order-structure}
756
+ */
757
+ await this.loadMarkets();
758
+ let type = undefined;
759
+ let subType = undefined;
760
+ let market = undefined;
761
+ let suffix = '*'; // wildcard
762
+ if (symbol !== undefined) {
763
+ market = this.market(symbol);
764
+ symbol = market['symbol'];
765
+ type = market['type'];
766
+ suffix = market['lowercaseId'];
767
+ subType = market['linear'] ? 'linear' : 'inverse';
768
+ }
769
+ else {
770
+ type = this.safeString(this.options, 'defaultType', 'spot');
771
+ type = this.safeString(params, 'type', type);
772
+ subType = this.safeString2(this.options, 'subType', 'defaultSubType', 'linear');
773
+ subType = this.safeString(params, 'subType', subType);
774
+ params = this.omit(params, ['type', 'subType']);
775
+ }
776
+ let messageHash = undefined;
777
+ let channel = undefined;
778
+ if (type === 'spot') {
779
+ messageHash = 'orders' + '#' + suffix;
780
+ channel = messageHash;
781
+ }
782
+ else {
783
+ const channelAndMessageHash = this.getOrderChannelAndMessageHash(type, subType, market, params);
784
+ channel = this.safeString(channelAndMessageHash, 0);
785
+ messageHash = this.safeString(channelAndMessageHash, 1);
786
+ }
787
+ const orders = await this.subscribePrivate(channel, messageHash, type, subType, params);
788
+ if (this.newUpdates) {
789
+ limit = orders.getLimit(symbol, limit);
790
+ }
791
+ return this.filterBySinceLimit(orders, since, limit, 'timestamp', true);
792
+ }
793
+ handleOrder(client, message) {
794
+ //
795
+ // spot
796
+ //
797
+ // {
798
+ // "action":"push",
799
+ // "ch":"orders#btcusdt", // or "orders#*" for global subscriptions
800
+ // "data": {
801
+ // "orderSource": "spot-web",
802
+ // "orderCreateTime": 1645116048355,
803
+ // "accountId": 44234548,
804
+ // "orderPrice": "100",
805
+ // "orderSize": "0.05",
806
+ // "symbol": "ethusdt",
807
+ // "type": "buy-limit",
808
+ // "orderId": "478861479986886",
809
+ // "eventType": "creation",
810
+ // "clientOrderId": '',
811
+ // "orderStatus": "submitted"
812
+ // }
813
+ // }
814
+ //
815
+ // spot wrapped trade
816
+ //
817
+ // {
818
+ // "action": "push",
819
+ // "ch": "orders#ltcusdt",
820
+ // "data": {
821
+ // "tradePrice": "130.01",
822
+ // "tradeVolume": "0.0385",
823
+ // "tradeTime": 1648714741525,
824
+ // "aggressor": true,
825
+ // "execAmt": "0.0385",
826
+ // "orderSource": "spot-web",
827
+ // "orderSize": "0.0385",
828
+ // "remainAmt": "0",
829
+ // "tradeId": 101541578884,
830
+ // "symbol": "ltcusdt",
831
+ // "type": "sell-market",
832
+ // "eventType": "trade",
833
+ // "clientOrderId": '',
834
+ // "orderStatus": "filled",
835
+ // "orderId": 509835753860328
836
+ // }
837
+ // }
838
+ //
839
+ // non spot order
840
+ //
841
+ // {
842
+ // "contract_type": "swap",
843
+ // "pair": "LTC-USDT",
844
+ // "business_type": "swap",
845
+ // "op": "notify",
846
+ // "topic": "orders_cross.ltc-usdt",
847
+ // "ts": 1650354508696,
848
+ // "symbol": "LTC",
849
+ // "contract_code": "LTC-USDT",
850
+ // "volume": 1,
851
+ // "price": 110.34,
852
+ // "order_price_type": "lightning",
853
+ // "direction": "sell",
854
+ // "offset": "close",
855
+ // "status": 6,
856
+ // "lever_rate": 1,
857
+ // "order_id": "966002354015051776",
858
+ // "order_id_str": "966002354015051776",
859
+ // "client_order_id": null,
860
+ // "order_source": "web",
861
+ // "order_type": 1,
862
+ // "created_at": 1650354508649,
863
+ // "trade_volume": 1,
864
+ // "trade_turnover": 11.072,
865
+ // "fee": -0.005536,
866
+ // "trade_avg_price": 110.72,
867
+ // "margin_frozen": 0,
868
+ // "profit": -0.045,
869
+ // "trade": [
870
+ // {
871
+ // "trade_fee": -0.005536,
872
+ // "fee_asset": "USDT",
873
+ // "real_profit": 0.473,
874
+ // "profit": -0.045,
875
+ // "trade_id": 86678766507,
876
+ // "id": "86678766507-966002354015051776-1",
877
+ // "trade_volume": 1,
878
+ // "trade_price": 110.72,
879
+ // "trade_turnover": 11.072,
880
+ // "created_at": 1650354508656,
881
+ // "role": "taker"
882
+ // }
883
+ // ],
884
+ // "canceled_at": 0,
885
+ // "fee_asset": "USDT",
886
+ // "margin_asset": "USDT",
887
+ // "uid": "359305390",
888
+ // "liquidation_type": "0",
889
+ // "margin_mode": "cross",
890
+ // "margin_account": "USDT",
891
+ // "is_tpsl": 0,
892
+ // "real_profit": 0.473,
893
+ // "trade_partition": "USDT",
894
+ // "reduce_only": 1
895
+ // }
896
+ //
897
+ //
898
+ const messageHash = this.safeString2(message, 'ch', 'topic');
899
+ const data = this.safeValue(message, 'data');
900
+ let marketId = this.safeString(message, 'contract_code');
901
+ if (marketId === undefined) {
902
+ marketId = this.safeString(data, 'symbol');
903
+ }
904
+ const market = this.safeMarket(marketId);
905
+ let parsedOrder = undefined;
906
+ if (data !== undefined) {
907
+ // spot updates
908
+ const eventType = this.safeString(data, 'eventType');
909
+ if (eventType === 'trade') {
910
+ // when a spot order is filled we get an update message
911
+ // with the trade info
912
+ const parsedTrade = this.parseOrderTrade(data, market);
913
+ // inject trade in existing order by faking an order object
914
+ const orderId = this.safeString(parsedTrade, 'order');
915
+ const trades = [parsedTrade];
916
+ const order = {
917
+ 'id': orderId,
918
+ 'trades': trades,
919
+ 'status': 'closed',
920
+ 'symbol': market['symbol'],
921
+ };
922
+ parsedOrder = order;
923
+ }
924
+ else {
925
+ parsedOrder = this.parseWsOrder(data, market);
926
+ }
927
+ }
928
+ else {
929
+ // contract branch
930
+ parsedOrder = this.parseWsOrder(message, market);
931
+ const rawTrades = this.safeValue(message, 'trade', []);
932
+ const tradesLength = rawTrades.length;
933
+ if (tradesLength > 0) {
934
+ const tradesObject = {
935
+ 'trades': rawTrades,
936
+ 'ch': messageHash,
937
+ 'symbol': marketId,
938
+ };
939
+ // inject order params in every trade
940
+ const extendTradeParams = {
941
+ 'order': this.safeString(parsedOrder, 'id'),
942
+ 'type': this.safeString(parsedOrder, 'type'),
943
+ 'side': this.safeString(parsedOrder, 'side'),
944
+ };
945
+ // trades arrive inside an order update
946
+ // we're forwarding them to handleMyTrade
947
+ // so they can be properly resolved
948
+ this.handleMyTrade(client, tradesObject, extendTradeParams);
949
+ }
950
+ }
951
+ if (this.orders === undefined) {
952
+ const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
953
+ this.orders = new ArrayCacheBySymbolById(limit);
954
+ }
955
+ const cachedOrders = this.orders;
956
+ cachedOrders.append(parsedOrder);
957
+ client.resolve(this.orders, messageHash);
958
+ // when we make a global subscription (for contracts only) our message hash can't have a symbol/currency attached
959
+ // so we're removing it here
960
+ let genericMessageHash = messageHash.replace('.' + market['lowercaseId'], '');
961
+ genericMessageHash = genericMessageHash.replace('.' + market['lowercaseBaseId'], '');
962
+ client.resolve(this.orders, genericMessageHash);
963
+ }
964
+ parseWsOrder(order, market = undefined) {
965
+ //
966
+ // spot
967
+ //
968
+ // {
969
+ // "orderSource": "spot-web",
970
+ // "orderCreateTime": 1645116048355, // creating only
971
+ // "accountId": 44234548,
972
+ // "orderPrice": "100",
973
+ // "orderSize": "0.05",
974
+ // "orderValue": "3.71676361", // market-buy only
975
+ // "symbol": "ethusdt",
976
+ // "type": "buy-limit",
977
+ // "orderId": "478861479986886",
978
+ // "eventType": "creation",
979
+ // "clientOrderId": '',
980
+ // "orderStatus": "submitted"
981
+ // "lastActTime":1645118621810 // except creating
982
+ // "execAmt":"0"
983
+ // }
984
+ //
985
+ // swap order
986
+ //
987
+ // {
988
+ // "contract_type": "swap",
989
+ // "pair": "LTC-USDT",
990
+ // "business_type": "swap",
991
+ // "op": "notify",
992
+ // "topic": "orders_cross.ltc-usdt",
993
+ // "ts": 1648717911384,
994
+ // "symbol": "LTC",
995
+ // "contract_code": "LTC-USDT",
996
+ // "volume": 1,
997
+ // "price": 129.13,
998
+ // "order_price_type": "lightning",
999
+ // "direction": "sell",
1000
+ // "offset": "close",
1001
+ // "status": 6,
1002
+ // "lever_rate": 5,
1003
+ // "order_id": "959137967397068800",
1004
+ // "order_id_str": "959137967397068800",
1005
+ // "client_order_id": null,
1006
+ // "order_source": "web",
1007
+ // "order_type": 1,
1008
+ // "created_at": 1648717911344,
1009
+ // "trade_volume": 1,
1010
+ // "trade_turnover": 12.952,
1011
+ // "fee": -0.006476,
1012
+ // "trade_avg_price": 129.52,
1013
+ // "margin_frozen": 0,
1014
+ // "profit": -0.005,
1015
+ // "trade": [
1016
+ // {
1017
+ // "trade_fee": -0.006476,
1018
+ // "fee_asset": "USDT",
1019
+ // "real_profit": -0.005,
1020
+ // "profit": -0.005,
1021
+ // "trade_id": 83619995370,
1022
+ // "id": "83619995370-959137967397068800-1",
1023
+ // "trade_volume": 1,
1024
+ // "trade_price": 129.52,
1025
+ // "trade_turnover": 12.952,
1026
+ // "created_at": 1648717911352,
1027
+ // "role": "taker"
1028
+ // }
1029
+ // ],
1030
+ // "canceled_at": 0,
1031
+ // "fee_asset": "USDT",
1032
+ // "margin_asset": "USDT",
1033
+ // "uid": "359305390",
1034
+ // "liquidation_type": "0",
1035
+ // "margin_mode": "cross",
1036
+ // "margin_account": "USDT",
1037
+ // "is_tpsl": 0,
1038
+ // "real_profit": -0.005,
1039
+ // "trade_partition": "USDT",
1040
+ // "reduce_only": 1
1041
+ // }
1042
+ //
1043
+ // {
1044
+ // "op":"notify",
1045
+ // "topic":"orders.ada",
1046
+ // "ts":1604388667226,
1047
+ // "symbol":"ADA",
1048
+ // "contract_type":"quarter",
1049
+ // "contract_code":"ADA201225",
1050
+ // "volume":1,
1051
+ // "price":0.0905,
1052
+ // "order_price_type":"post_only",
1053
+ // "direction":"sell",
1054
+ // "offset":"open",
1055
+ // "status":6,
1056
+ // "lever_rate":20,
1057
+ // "order_id":773207641127878656,
1058
+ // "order_id_str":"773207641127878656",
1059
+ // "client_order_id":null,
1060
+ // "order_source":"web",
1061
+ // "order_type":1,
1062
+ // "created_at":1604388667146,
1063
+ // "trade_volume":1,
1064
+ // "trade_turnover":10,
1065
+ // "fee":-0.022099447513812154,
1066
+ // "trade_avg_price":0.0905,
1067
+ // "margin_frozen":0,
1068
+ // "profit":0,
1069
+ // "trade":[],
1070
+ // "canceled_at":0,
1071
+ // "fee_asset":"ADA",
1072
+ // "uid":"123456789",
1073
+ // "liquidation_type":"0",
1074
+ // "is_tpsl": 0,
1075
+ // "real_profit": 0
1076
+ // }
1077
+ //
1078
+ const lastTradeTimestamp = this.safeInteger2(order, 'lastActTime', 'ts');
1079
+ const created = this.safeInteger(order, 'orderCreateTime');
1080
+ const marketId = this.safeString2(order, 'contract_code', 'symbol');
1081
+ market = this.safeMarket(marketId, market);
1082
+ const symbol = this.safeSymbol(marketId, market);
1083
+ const amount = this.safeString2(order, 'orderSize', 'volume');
1084
+ const status = this.parseOrderStatus(this.safeString2(order, 'orderStatus', 'status'));
1085
+ const id = this.safeString2(order, 'orderId', 'order_id');
1086
+ const clientOrderId = this.safeString2(order, 'clientOrderId', 'client_order_id');
1087
+ const price = this.safeString2(order, 'orderPrice', 'price');
1088
+ const filled = this.safeString(order, 'execAmt');
1089
+ const typeSide = this.safeString(order, 'type');
1090
+ const feeCost = this.safeString(order, 'fee');
1091
+ let fee = undefined;
1092
+ if (feeCost !== undefined) {
1093
+ const feeCurrencyId = this.safeString(order, 'fee_asset');
1094
+ fee = {
1095
+ 'cost': feeCost,
1096
+ 'currency': this.safeCurrencyCode(feeCurrencyId),
1097
+ };
1098
+ }
1099
+ const avgPrice = this.safeString(order, 'trade_avg_price');
1100
+ const rawTrades = this.safeValue(order, 'trade');
1101
+ let typeSideParts = [];
1102
+ if (typeSide !== undefined) {
1103
+ typeSideParts = typeSide.split('-');
1104
+ }
1105
+ let type = this.safeStringLower(typeSideParts, 1);
1106
+ if (type === undefined) {
1107
+ type = this.safeString(order, 'order_price_type');
1108
+ }
1109
+ let side = this.safeStringLower(typeSideParts, 0);
1110
+ if (side === undefined) {
1111
+ side = this.safeString(order, 'direction');
1112
+ }
1113
+ const cost = this.safeString(order, 'orderValue');
1114
+ return this.safeOrder({
1115
+ 'info': order,
1116
+ 'id': id,
1117
+ 'clientOrderId': clientOrderId,
1118
+ 'timestamp': created,
1119
+ 'datetime': this.iso8601(created),
1120
+ 'lastTradeTimestamp': lastTradeTimestamp,
1121
+ 'status': status,
1122
+ 'symbol': symbol,
1123
+ 'type': type,
1124
+ 'timeInForce': undefined,
1125
+ 'postOnly': undefined,
1126
+ 'side': side,
1127
+ 'price': price,
1128
+ 'amount': amount,
1129
+ 'filled': filled,
1130
+ 'remaining': undefined,
1131
+ 'cost': cost,
1132
+ 'fee': fee,
1133
+ 'average': avgPrice,
1134
+ 'trades': rawTrades,
1135
+ }, market);
1136
+ }
1137
+ parseOrderTrade(trade, market = undefined) {
1138
+ // spot private wrapped trade
1139
+ //
1140
+ // {
1141
+ // "tradePrice": "130.01",
1142
+ // "tradeVolume": "0.0385",
1143
+ // "tradeTime": 1648714741525,
1144
+ // "aggressor": true,
1145
+ // "execAmt": "0.0385",
1146
+ // "orderSource": "spot-web",
1147
+ // "orderSize": "0.0385",
1148
+ // "remainAmt": "0",
1149
+ // "tradeId": 101541578884,
1150
+ // "symbol": "ltcusdt",
1151
+ // "type": "sell-market",
1152
+ // "eventType": "trade",
1153
+ // "clientOrderId": '',
1154
+ // "orderStatus": "filled",
1155
+ // "orderId": 509835753860328
1156
+ // }
1157
+ //
1158
+ market = this.safeMarket(undefined, market);
1159
+ const symbol = market['symbol'];
1160
+ const tradeId = this.safeString(trade, 'tradeId');
1161
+ const price = this.safeString(trade, 'tradePrice');
1162
+ const amount = this.safeString(trade, 'tradeVolume');
1163
+ const order = this.safeString(trade, 'orderId');
1164
+ const timestamp = this.safeInteger(trade, 'tradeTime');
1165
+ let type = this.safeString(trade, 'type');
1166
+ let side = undefined;
1167
+ if (type !== undefined) {
1168
+ const typeParts = type.split('-');
1169
+ side = typeParts[0];
1170
+ type = typeParts[1];
1171
+ }
1172
+ const aggressor = this.safeValue(trade, 'aggressor');
1173
+ let takerOrMaker = undefined;
1174
+ if (aggressor !== undefined) {
1175
+ takerOrMaker = aggressor ? 'taker' : 'maker';
1176
+ }
1177
+ return this.safeTrade({
1178
+ 'info': trade,
1179
+ 'timestamp': timestamp,
1180
+ 'datetime': this.iso8601(timestamp),
1181
+ 'symbol': symbol,
1182
+ 'id': tradeId,
1183
+ 'order': order,
1184
+ 'type': type,
1185
+ 'takerOrMaker': takerOrMaker,
1186
+ 'side': side,
1187
+ 'price': price,
1188
+ 'amount': amount,
1189
+ 'cost': undefined,
1190
+ 'fee': undefined,
1191
+ }, market);
1192
+ }
1193
+ async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) {
1194
+ /**
1195
+ * @method
1196
+ * @name huobi#watchPositions
1197
+ * @see https://www.huobi.com/en-in/opend/newApiPages/?id=8cb7de1c-77b5-11ed-9966-0242ac110003
1198
+ * @see https://www.huobi.com/en-in/opend/newApiPages/?id=8cb7df0f-77b5-11ed-9966-0242ac110003
1199
+ * @see https://www.huobi.com/en-in/opend/newApiPages/?id=28c34a7d-77ae-11ed-9966-0242ac110003
1200
+ * @see https://www.huobi.com/en-in/opend/newApiPages/?id=5d5156b5-77b6-11ed-9966-0242ac110003
1201
+ * @description watch all open positions. Note: huobi has one channel for each marginMode and type
1202
+ * @param {string[]|undefined} symbols list of unified market symbols
1203
+ * @param {object} params extra parameters specific to the huobi api endpoint
1204
+ * @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
1205
+ */
1206
+ await this.loadMarkets();
1207
+ let market = undefined;
1208
+ let messageHash = '';
1209
+ if (!this.isEmpty(symbols)) {
1210
+ market = this.getMarketFromSymbols(symbols);
1211
+ messageHash = '::' + symbols.join(',');
1212
+ }
1213
+ let type = undefined;
1214
+ let subType = undefined;
1215
+ if (market !== undefined) {
1216
+ type = market['type'];
1217
+ subType = market['linear'] ? 'linear' : 'inverse';
1218
+ }
1219
+ else {
1220
+ [type, params] = this.handleMarketTypeAndParams('watchPositions', market, params);
1221
+ if (type === 'spot') {
1222
+ type = 'future';
1223
+ }
1224
+ [subType, params] = this.handleOptionAndParams(params, 'watchPositions', 'subType', subType);
1225
+ }
1226
+ symbols = this.marketSymbols(symbols);
1227
+ let marginMode = undefined;
1228
+ [marginMode, params] = this.handleMarginModeAndParams('watchPositions', params, 'cross');
1229
+ const isLinear = (subType === 'linear');
1230
+ const url = this.getUrlByMarketType(type, isLinear, true);
1231
+ messageHash = marginMode + ':positions' + messageHash;
1232
+ const channel = (marginMode === 'cross') ? 'positions_cross.*' : 'positions.*';
1233
+ const newPositions = await this.subscribePrivate(channel, messageHash, type, subType, params);
1234
+ if (this.newUpdates) {
1235
+ return newPositions;
1236
+ }
1237
+ return this.filterBySymbolsSinceLimit(this.positions[url][marginMode], symbols, since, limit, false);
1238
+ }
1239
+ handlePositions(client, message) {
1240
+ //
1241
+ // {
1242
+ // op: 'notify',
1243
+ // topic: 'positions_cross',
1244
+ // ts: 1696767149650,
1245
+ // event: 'snapshot',
1246
+ // data: [
1247
+ // {
1248
+ // contract_type: 'swap',
1249
+ // pair: 'BTC-USDT',
1250
+ // business_type: 'swap',
1251
+ // liquidation_price: null,
1252
+ // symbol: 'BTC',
1253
+ // contract_code: 'BTC-USDT',
1254
+ // volume: 1,
1255
+ // available: 1,
1256
+ // frozen: 0,
1257
+ // cost_open: 27802.2,
1258
+ // cost_hold: 27802.2,
1259
+ // profit_unreal: 0.0175,
1260
+ // profit_rate: 0.000629446590557581,
1261
+ // profit: 0.0175,
1262
+ // margin_asset: 'USDT',
1263
+ // position_margin: 27.8197,
1264
+ // lever_rate: 1,
1265
+ // direction: 'buy',
1266
+ // last_price: 27819.7,
1267
+ // margin_mode: 'cross',
1268
+ // margin_account: 'USDT',
1269
+ // trade_partition: 'USDT',
1270
+ // position_mode: 'dual_side'
1271
+ // },
1272
+ // ]
1273
+ // }
1274
+ //
1275
+ const url = client.url;
1276
+ const topic = this.safeString(message, 'topic', '');
1277
+ const marginMode = (topic === 'positions_cross') ? 'cross' : 'isolated';
1278
+ if (this.positions === undefined) {
1279
+ this.positions = {};
1280
+ }
1281
+ const clientPositions = this.safeValue(this.positions, url);
1282
+ if (clientPositions === undefined) {
1283
+ this.positions[url] = {};
1284
+ }
1285
+ const clientMarginModePositions = this.safeValue(clientPositions, marginMode);
1286
+ if (clientMarginModePositions === undefined) {
1287
+ this.positions[url][marginMode] = new ArrayCacheBySymbolBySide();
1288
+ }
1289
+ const cache = this.positions[url][marginMode];
1290
+ const rawPositions = this.safeValue(message, 'data', []);
1291
+ const newPositions = [];
1292
+ const timestamp = this.safeInteger(message, 'ts');
1293
+ for (let i = 0; i < rawPositions.length; i++) {
1294
+ const rawPosition = rawPositions[i];
1295
+ const position = this.parsePosition(rawPosition);
1296
+ position['timestamp'] = timestamp;
1297
+ position['datetime'] = this.iso8601(timestamp);
1298
+ newPositions.push(position);
1299
+ cache.append(position);
1300
+ }
1301
+ const messageHashes = this.findMessageHashes(client, marginMode + ':positions::');
1302
+ for (let i = 0; i < messageHashes.length; i++) {
1303
+ const messageHash = messageHashes[i];
1304
+ const parts = messageHash.split('::');
1305
+ const symbolsString = parts[1];
1306
+ const symbols = symbolsString.split(',');
1307
+ const positions = this.filterByArray(newPositions, 'symbol', symbols, false);
1308
+ if (!this.isEmpty(positions)) {
1309
+ client.resolve(positions, messageHash);
1310
+ }
1311
+ }
1312
+ client.resolve(newPositions, marginMode + ':positions');
1313
+ }
1314
+ async watchBalance(params = {}) {
1315
+ /**
1316
+ * @method
1317
+ * @name huobi#watchBalance
1318
+ * @description watch balance and get the amount of funds available for trading or funds locked in orders
1319
+ * @param {object} [params] extra parameters specific to the huobi api endpoint
1320
+ * @returns {object} a [balance structure]{@link https://github.com/ccxt/ccxt/wiki/Manual#balance-structure}
1321
+ */
1322
+ let type = undefined;
1323
+ [type, params] = this.handleMarketTypeAndParams('watchBalance', undefined, params);
1324
+ let subType = undefined;
1325
+ [subType, params] = this.handleSubTypeAndParams('watchBalance', undefined, params, 'linear');
1326
+ const isUnifiedAccount = this.safeValue2(params, 'isUnifiedAccount', 'unified', false);
1327
+ params = this.omit(params, ['isUnifiedAccount', 'unified']);
1328
+ await this.loadMarkets();
1329
+ let messageHash = undefined;
1330
+ let channel = undefined;
1331
+ let marginMode = undefined;
1332
+ if (type === 'spot') {
1333
+ let mode = this.safeString2(this.options, 'watchBalance', 'mode', '2');
1334
+ mode = this.safeString(params, 'mode', mode);
1335
+ messageHash = 'accounts.update' + '#' + mode;
1336
+ channel = messageHash;
1337
+ }
1338
+ else {
1339
+ const symbol = this.safeString(params, 'symbol');
1340
+ const currency = this.safeString(params, 'currency');
1341
+ const market = (symbol !== undefined) ? this.market(symbol) : undefined;
1342
+ const currencyCode = (currency !== undefined) ? this.currency(currency) : undefined;
1343
+ marginMode = this.safeString(params, 'margin', 'cross');
1344
+ params = this.omit(params, ['currency', 'symbol', 'margin']);
1345
+ let prefix = 'accounts';
1346
+ messageHash = prefix;
1347
+ if (subType === 'linear') {
1348
+ if (isUnifiedAccount) {
1349
+ // usdt contracts account
1350
+ prefix = 'accounts_unify';
1351
+ messageHash = prefix;
1352
+ channel = prefix + '.' + 'usdt';
1353
+ }
1354
+ else {
1355
+ // usdt contracts account
1356
+ prefix = (marginMode === 'cross') ? prefix + '_cross' : prefix;
1357
+ messageHash = prefix;
1358
+ if (marginMode === 'isolated') {
1359
+ // isolated margin only allows filtering by symbol3
1360
+ if (symbol !== undefined) {
1361
+ messageHash += '.' + market['id'];
1362
+ channel = messageHash;
1363
+ }
1364
+ else {
1365
+ // subscribe to all
1366
+ channel = prefix + '.' + '*';
1367
+ }
1368
+ }
1369
+ else {
1370
+ // cross margin
1371
+ if (currencyCode !== undefined) {
1372
+ channel = prefix + '.' + currencyCode['id'];
1373
+ messageHash = channel;
1374
+ }
1375
+ else {
1376
+ // subscribe to all
1377
+ channel = prefix + '.' + '*';
1378
+ }
1379
+ }
1380
+ }
1381
+ }
1382
+ else if (type === 'future') {
1383
+ // inverse futures account
1384
+ if (currencyCode !== undefined) {
1385
+ messageHash += '.' + currencyCode['id'];
1386
+ channel = messageHash;
1387
+ }
1388
+ else {
1389
+ // subscribe to all
1390
+ channel = prefix + '.' + '*';
1391
+ }
1392
+ }
1393
+ else {
1394
+ // inverse swaps account
1395
+ if (market !== undefined) {
1396
+ messageHash += '.' + market['id'];
1397
+ channel = messageHash;
1398
+ }
1399
+ else {
1400
+ // subscribe to all
1401
+ channel = prefix + '.' + '*';
1402
+ }
1403
+ }
1404
+ }
1405
+ const subscriptionParams = {
1406
+ 'type': type,
1407
+ 'subType': subType,
1408
+ 'margin': marginMode,
1409
+ };
1410
+ // we are differentiating the channel from the messageHash for global subscriptions (*)
1411
+ // because huobi returns a different topic than the topic sent. Example: we send
1412
+ // "accounts.*" and "accounts" is returned so we're setting channel = "accounts.*" and
1413
+ // messageHash = "accounts" allowing handleBalance to freely resolve the topic in the message
1414
+ return await this.subscribePrivate(channel, messageHash, type, subType, params, subscriptionParams);
1415
+ }
1416
+ handleBalance(client, message) {
1417
+ // spot
1418
+ //
1419
+ // {
1420
+ // "action": "push",
1421
+ // "ch": "accounts.update#0",
1422
+ // "data": {
1423
+ // "currency": "btc",
1424
+ // "accountId": 123456,
1425
+ // "balance": "23.111",
1426
+ // "available": "2028.699426619837209087",
1427
+ // "changeType": "transfer",
1428
+ // "accountType":"trade",
1429
+ // "seqNum": "86872993928",
1430
+ // "changeTime": 1568601800000
1431
+ // }
1432
+ // }
1433
+ //
1434
+ // inverse future
1435
+ //
1436
+ // {
1437
+ // "op":"notify",
1438
+ // "topic":"accounts.ada",
1439
+ // "ts":1604388667226,
1440
+ // "event":"order.match",
1441
+ // "data":[
1442
+ // {
1443
+ // "symbol":"ADA",
1444
+ // "margin_balance":446.417641681222726716,
1445
+ // "margin_static":445.554085945257745136,
1446
+ // "margin_position":11.049723756906077348,
1447
+ // "margin_frozen":0,
1448
+ // "margin_available":435.367917924316649368,
1449
+ // "profit_real":21.627049781983019459,
1450
+ // "profit_unreal":0.86355573596498158,
1451
+ // "risk_rate":40.000796572150656768,
1452
+ // "liquidation_price":0.018674308027108984,
1453
+ // "withdraw_available":423.927036163274725677,
1454
+ // "lever_rate":20,
1455
+ // "adjust_factor":0.4
1456
+ // }
1457
+ // ],
1458
+ // "uid":"123456789"
1459
+ // }
1460
+ //
1461
+ // usdt / linear future, swap
1462
+ //
1463
+ // {
1464
+ // "op":"notify",
1465
+ // "topic":"accounts.btc-usdt", // or "accounts" for global subscriptions
1466
+ // "ts":1603711370689,
1467
+ // "event":"order.open",
1468
+ // "data":[
1469
+ // {
1470
+ // "margin_mode":"cross",
1471
+ // "margin_account":"USDT",
1472
+ // "margin_asset":"USDT",
1473
+ // "margin_balance":30.959342395,
1474
+ // "margin_static":30.959342395,
1475
+ // "margin_position":0,
1476
+ // "margin_frozen":10,
1477
+ // "profit_real":0,
1478
+ // "profit_unreal":0,
1479
+ // "withdraw_available":20.959342395,
1480
+ // "risk_rate":153.796711975,
1481
+ // "position_mode":"dual_side",
1482
+ // "contract_detail":[
1483
+ // {
1484
+ // "symbol":"LTC",
1485
+ // "contract_code":"LTC-USDT",
1486
+ // "margin_position":0,
1487
+ // "margin_frozen":0,
1488
+ // "margin_available":20.959342395,
1489
+ // "profit_unreal":0,
1490
+ // "liquidation_price":null,
1491
+ // "lever_rate":1,
1492
+ // "adjust_factor":0.01,
1493
+ // "contract_type":"swap",
1494
+ // "pair":"LTC-USDT",
1495
+ // "business_type":"swap",
1496
+ // "trade_partition":"USDT"
1497
+ // },
1498
+ // ],
1499
+ // "futures_contract_detail":[],
1500
+ // }
1501
+ // ]
1502
+ // }
1503
+ //
1504
+ // inverse future
1505
+ //
1506
+ // {
1507
+ // "op":"notify",
1508
+ // "topic":"accounts.ada",
1509
+ // "ts":1604388667226,
1510
+ // "event":"order.match",
1511
+ // "data":[
1512
+ // {
1513
+ // "symbol":"ADA",
1514
+ // "margin_balance":446.417641681222726716,
1515
+ // "margin_static":445.554085945257745136,
1516
+ // "margin_position":11.049723756906077348,
1517
+ // "margin_frozen":0,
1518
+ // "margin_available":435.367917924316649368,
1519
+ // "profit_real":21.627049781983019459,
1520
+ // "profit_unreal":0.86355573596498158,
1521
+ // "risk_rate":40.000796572150656768,
1522
+ // "liquidation_price":0.018674308027108984,
1523
+ // "withdraw_available":423.927036163274725677,
1524
+ // "lever_rate":20,
1525
+ // "adjust_factor":0.4
1526
+ // }
1527
+ // ],
1528
+ // "uid":"123456789"
1529
+ // }
1530
+ //
1531
+ const channel = this.safeString(message, 'ch');
1532
+ const data = this.safeValue(message, 'data', []);
1533
+ const timestamp = this.safeInteger(data, 'changeTime', this.safeInteger(message, 'ts'));
1534
+ this.balance['timestamp'] = timestamp;
1535
+ this.balance['datetime'] = this.iso8601(timestamp);
1536
+ this.balance['info'] = data;
1537
+ if (channel !== undefined) {
1538
+ // spot balance
1539
+ const currencyId = this.safeString(data, 'currency');
1540
+ const code = this.safeCurrencyCode(currencyId);
1541
+ const account = this.account();
1542
+ account['free'] = this.safeString(data, 'available');
1543
+ account['total'] = this.safeString(data, 'balance');
1544
+ this.balance[code] = account;
1545
+ this.balance = this.safeBalance(this.balance);
1546
+ client.resolve(this.balance, channel);
1547
+ }
1548
+ else {
1549
+ // contract balance
1550
+ const dataLength = data.length;
1551
+ if (dataLength === 0) {
1552
+ return;
1553
+ }
1554
+ const first = this.safeValue(data, 0, {});
1555
+ const topic = this.safeString(message, 'topic');
1556
+ const splitTopic = topic.split('.');
1557
+ let messageHash = this.safeString(splitTopic, 0);
1558
+ let subscription = this.safeValue2(client.subscriptions, messageHash, messageHash + '.*');
1559
+ if (subscription === undefined) {
1560
+ // if subscription not found means that we subscribed to a specific currency/symbol
1561
+ // and we use the first data entry to find it
1562
+ // Example: topic = 'accounts'
1563
+ // client.subscription hash = 'accounts.usdt'
1564
+ // we do 'accounts' + '.' + data[0]]['margin_asset'] to get it
1565
+ const currencyId = this.safeString2(first, 'margin_asset', 'symbol');
1566
+ messageHash += '.' + currencyId.toLowerCase();
1567
+ subscription = this.safeValue(client.subscriptions, messageHash);
1568
+ }
1569
+ const type = this.safeString(subscription, 'type');
1570
+ const subType = this.safeString(subscription, 'subType');
1571
+ if (topic === 'accounts_unify') {
1572
+ // {
1573
+ // "margin_asset": "USDT",
1574
+ // "margin_static": 10,
1575
+ // "cross_margin_static": 10,
1576
+ // "margin_balance": 10,
1577
+ // "cross_profit_unreal": 0,
1578
+ // "margin_frozen": 0,
1579
+ // "withdraw_available": 10,
1580
+ // "cross_risk_rate": null,
1581
+ // "cross_swap": [],
1582
+ // "cross_future": [],
1583
+ // "isolated_swap": []
1584
+ // }
1585
+ const marginAsset = this.safeString(first, 'margin_asset');
1586
+ const code = this.safeCurrencyCode(marginAsset);
1587
+ const marginFrozen = this.safeString(first, 'margin_frozen');
1588
+ const unifiedAccount = this.account();
1589
+ unifiedAccount['free'] = this.safeString(first, 'withdraw_available');
1590
+ unifiedAccount['used'] = marginFrozen;
1591
+ this.balance[code] = unifiedAccount;
1592
+ this.balance = this.safeBalance(this.balance);
1593
+ client.resolve(this.balance, 'accounts_unify');
1594
+ }
1595
+ else if (subType === 'linear') {
1596
+ const margin = this.safeString(subscription, 'margin');
1597
+ if (margin === 'cross') {
1598
+ const fieldName = (type === 'future') ? 'futures_contract_detail' : 'contract_detail';
1599
+ const balances = this.safeValue(first, fieldName, []);
1600
+ const balancesLength = balances.length;
1601
+ if (balancesLength > 0) {
1602
+ for (let i = 0; i < balances.length; i++) {
1603
+ const balance = balances[i];
1604
+ const marketId = this.safeString2(balance, 'contract_code', 'margin_account');
1605
+ const market = this.safeMarket(marketId);
1606
+ const currencyId = this.safeString(balance, 'margin_asset');
1607
+ const currency = this.safeCurrency(currencyId);
1608
+ const code = this.safeString(market, 'settle', currency['code']);
1609
+ // the exchange outputs positions for delisted markets
1610
+ // https://www.huobi.com/support/en-us/detail/74882968522337
1611
+ // we skip it if the market was delisted
1612
+ if (code !== undefined) {
1613
+ const account = this.account();
1614
+ account['free'] = this.safeString2(balance, 'margin_balance', 'margin_available');
1615
+ account['used'] = this.safeString(balance, 'margin_frozen');
1616
+ const accountsByCode = {};
1617
+ accountsByCode[code] = account;
1618
+ const symbol = market['symbol'];
1619
+ this.balance[symbol] = this.safeBalance(accountsByCode);
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1624
+ else {
1625
+ // isolated margin
1626
+ for (let i = 0; i < data.length; i++) {
1627
+ const isolatedBalance = data[i];
1628
+ const account = this.account();
1629
+ account['free'] = this.safeString(isolatedBalance, 'margin_balance', 'margin_available');
1630
+ account['used'] = this.safeString(isolatedBalance, 'margin_frozen');
1631
+ const currencyId = this.safeString2(isolatedBalance, 'margin_asset', 'symbol');
1632
+ const code = this.safeCurrencyCode(currencyId);
1633
+ this.balance[code] = account;
1634
+ this.balance = this.safeBalance(this.balance);
1635
+ }
1636
+ }
1637
+ }
1638
+ else {
1639
+ // inverse branch
1640
+ for (let i = 0; i < data.length; i++) {
1641
+ const balance = data[i];
1642
+ const currencyId = this.safeString(balance, 'symbol');
1643
+ const code = this.safeCurrencyCode(currencyId);
1644
+ const account = this.account();
1645
+ account['free'] = this.safeString(balance, 'margin_available');
1646
+ account['used'] = this.safeString(balance, 'margin_frozen');
1647
+ this.balance[code] = account;
1648
+ this.balance = this.safeBalance(this.balance);
1649
+ }
1650
+ }
1651
+ client.resolve(this.balance, messageHash);
1652
+ }
1653
+ }
1654
+ handleSubscriptionStatus(client, message) {
1655
+ //
1656
+ // {
1657
+ // "id": 1583414227,
1658
+ // "status": "ok",
1659
+ // "subbed": "market.btcusdt.mbp.150",
1660
+ // "ts": 1583414229143
1661
+ // }
1662
+ //
1663
+ const id = this.safeString(message, 'id');
1664
+ const subscriptionsById = this.indexBy(client.subscriptions, 'id');
1665
+ const subscription = this.safeValue(subscriptionsById, id);
1666
+ if (subscription !== undefined) {
1667
+ const method = this.safeValue(subscription, 'method');
1668
+ if (method !== undefined) {
1669
+ return method.call(this, client, message, subscription);
1670
+ }
1671
+ // clean up
1672
+ if (id in client.subscriptions) {
1673
+ delete client.subscriptions[id];
1674
+ }
1675
+ }
1676
+ return message;
1677
+ }
1678
+ handleSystemStatus(client, message) {
1679
+ //
1680
+ // todo: answer the question whether handleSystemStatus should be renamed
1681
+ // and unified as handleStatus for any usage pattern that
1682
+ // involves system status and maintenance updates
1683
+ //
1684
+ // {
1685
+ // "id": "1578090234088", // connectId
1686
+ // "type": "welcome",
1687
+ // }
1688
+ //
1689
+ return message;
1690
+ }
1691
+ handleSubject(client, message) {
1692
+ // spot
1693
+ // {
1694
+ // "ch": "market.btcusdt.mbp.150",
1695
+ // "ts": 1583472025885,
1696
+ // "tick": {
1697
+ // "seqNum": 104998984994,
1698
+ // "prevSeqNum": 104998984977,
1699
+ // "bids": [
1700
+ // [9058.27, 0],
1701
+ // [9058.43, 0],
1702
+ // [9058.99, 0],
1703
+ // ],
1704
+ // "asks": [
1705
+ // [9084.27, 0.2],
1706
+ // [9085.69, 0],
1707
+ // [9085.81, 0],
1708
+ // ]
1709
+ // }
1710
+ // }
1711
+ // non spot
1712
+ //
1713
+ // {
1714
+ // "ch":"market.BTC220218.depth.size_150.high_freq",
1715
+ // "tick":{
1716
+ // "asks":[],
1717
+ // "bids":[
1718
+ // [43445.74,1],
1719
+ // [43444.48,0 ],
1720
+ // [40593.92,9]
1721
+ // ],
1722
+ // "ch":"market.BTC220218.depth.size_150.high_freq",
1723
+ // "event":"update",
1724
+ // "id":152727500274,
1725
+ // "mrid":152727500274,
1726
+ // "ts":1645023376098,
1727
+ // "version":37536690
1728
+ // },
1729
+ // "ts":1645023376098
1730
+ // }
1731
+ //
1732
+ // spot private trade
1733
+ //
1734
+ // {
1735
+ // "action":"push",
1736
+ // "ch":"trade.clearing#ltcusdt#1",
1737
+ // "data":{
1738
+ // "eventType":"trade",
1739
+ // "symbol":"ltcusdt",
1740
+ // // ...
1741
+ // },
1742
+ // }
1743
+ //
1744
+ // spot order
1745
+ //
1746
+ // {
1747
+ // "action":"push",
1748
+ // "ch":"orders#btcusdt",
1749
+ // "data": {
1750
+ // "orderSide":"buy",
1751
+ // "lastActTime":1583853365586,
1752
+ // "clientOrderId":"abc123",
1753
+ // "orderStatus":"rejected",
1754
+ // "symbol":"btcusdt",
1755
+ // "eventType":"trigger",
1756
+ // "errCode": 2002,
1757
+ // "errMessage":"invalid.client.order.id (NT)"
1758
+ // }
1759
+ // }
1760
+ //
1761
+ // contract order
1762
+ //
1763
+ // {
1764
+ // "op":"notify",
1765
+ // "topic":"orders.ada",
1766
+ // "ts":1604388667226,
1767
+ // // ?
1768
+ // }
1769
+ //
1770
+ const ch = this.safeValue(message, 'ch', '');
1771
+ const parts = ch.split('.');
1772
+ const type = this.safeString(parts, 0);
1773
+ if (type === 'market') {
1774
+ const methodName = this.safeString(parts, 2);
1775
+ const methods = {
1776
+ 'depth': this.handleOrderBook,
1777
+ 'mbp': this.handleOrderBook,
1778
+ 'detail': this.handleTicker,
1779
+ 'bbo': this.handleTicker,
1780
+ 'ticker': this.handleTicker,
1781
+ 'trade': this.handleTrades,
1782
+ 'kline': this.handleOHLCV,
1783
+ };
1784
+ const method = this.safeValue(methods, methodName);
1785
+ if (method === undefined) {
1786
+ return message;
1787
+ }
1788
+ else {
1789
+ return method.call(this, client, message);
1790
+ }
1791
+ }
1792
+ // private spot subjects
1793
+ const privateParts = ch.split('#');
1794
+ const privateType = this.safeString(privateParts, 0, '');
1795
+ if (privateType === 'trade.clearing') {
1796
+ this.handleMyTrade(client, message);
1797
+ return;
1798
+ }
1799
+ if (privateType.indexOf('accounts.update') >= 0) {
1800
+ this.handleBalance(client, message);
1801
+ return;
1802
+ }
1803
+ if (privateType === 'orders') {
1804
+ this.handleOrder(client, message);
1805
+ return;
1806
+ }
1807
+ // private contract subjects
1808
+ const op = this.safeString(message, 'op');
1809
+ if (op === 'notify') {
1810
+ const topic = this.safeString(message, 'topic', '');
1811
+ if (topic.indexOf('orders') >= 0) {
1812
+ this.handleOrder(client, message);
1813
+ }
1814
+ if (topic.indexOf('account') >= 0) {
1815
+ this.handleBalance(client, message);
1816
+ }
1817
+ if (topic.indexOf('positions') >= 0) {
1818
+ this.handlePositions(client, message);
1819
+ }
1820
+ }
1821
+ }
1822
+ async pong(client, message) {
1823
+ //
1824
+ // { ping: 1583491673714 }
1825
+ // { action: "ping", data: { ts: 1645108204665 } }
1826
+ // { op: "ping", ts: "1645202800015" }
1827
+ //
1828
+ try {
1829
+ const ping = this.safeInteger(message, 'ping');
1830
+ if (ping !== undefined) {
1831
+ await client.send({ 'pong': ping });
1832
+ return;
1833
+ }
1834
+ const action = this.safeString(message, 'action');
1835
+ if (action === 'ping') {
1836
+ const data = this.safeValue(message, 'data');
1837
+ const pingTs = this.safeInteger(data, 'ts');
1838
+ await client.send({ 'action': 'pong', 'data': { 'ts': pingTs } });
1839
+ return;
1840
+ }
1841
+ const op = this.safeString(message, 'op');
1842
+ if (op === 'ping') {
1843
+ const pingTs = this.safeInteger(message, 'ts');
1844
+ await client.send({ 'op': 'pong', 'ts': pingTs });
1845
+ }
1846
+ }
1847
+ catch (e) {
1848
+ const error = new NetworkError(this.id + ' pong failed ' + this.json(e));
1849
+ client.reset(error);
1850
+ }
1851
+ }
1852
+ handlePing(client, message) {
1853
+ this.spawn(this.pong, client, message);
1854
+ }
1855
+ handleAuthenticate(client, message) {
1856
+ //
1857
+ // spot
1858
+ //
1859
+ // {
1860
+ // "action": "req",
1861
+ // "code": 200,
1862
+ // "ch": "auth",
1863
+ // "data": {}
1864
+ // }
1865
+ //
1866
+ // non spot
1867
+ //
1868
+ // {
1869
+ // "op": "auth",
1870
+ // "type": "api",
1871
+ // "err-code": 0,
1872
+ // "ts": 1645200307319,
1873
+ // "data": { "user-id": "35930539" }
1874
+ // }
1875
+ //
1876
+ const promise = client.futures['authenticated'];
1877
+ promise.resolve(message);
1878
+ }
1879
+ handleErrorMessage(client, message) {
1880
+ //
1881
+ // {
1882
+ // "action": "sub",
1883
+ // "code": 2002,
1884
+ // "ch": "accounts.update#2",
1885
+ // "message": "invalid.auth.state"
1886
+ // }
1887
+ //
1888
+ // {
1889
+ // "ts": 1586323747018,
1890
+ // "status": "error",
1891
+ // 'err-code': "bad-request",
1892
+ // 'err-msg': "invalid mbp.150.symbol linkusdt",
1893
+ // "id": "2"
1894
+ // }
1895
+ //
1896
+ // {
1897
+ // "op": "sub",
1898
+ // "cid": "1",
1899
+ // "topic": "accounts_unify.USDT",
1900
+ // "err-code": 4007,
1901
+ // 'err-msg': "Non - single account user is not available, please check through the cross and isolated account asset interface",
1902
+ // "ts": 1698419490189
1903
+ // }
1904
+ //
1905
+ const status = this.safeString(message, 'status');
1906
+ if (status === 'error') {
1907
+ const id = this.safeString(message, 'id');
1908
+ const subscriptionsById = this.indexBy(client.subscriptions, 'id');
1909
+ const subscription = this.safeValue(subscriptionsById, id);
1910
+ if (subscription !== undefined) {
1911
+ const errorCode = this.safeString(message, 'err-code');
1912
+ try {
1913
+ this.throwExactlyMatchedException(this.exceptions['ws']['exact'], errorCode, this.json(message));
1914
+ }
1915
+ catch (e) {
1916
+ const messageHash = this.safeString(subscription, 'messageHash');
1917
+ client.reject(e, messageHash);
1918
+ client.reject(e, id);
1919
+ if (id in client.subscriptions) {
1920
+ delete client.subscriptions[id];
1921
+ }
1922
+ }
1923
+ }
1924
+ return false;
1925
+ }
1926
+ const code = this.safeInteger2(message, 'code', 'err-code');
1927
+ if (code !== undefined && ((code !== 200) && (code !== 0))) {
1928
+ const feedback = this.id + ' ' + this.json(message);
1929
+ try {
1930
+ this.throwExactlyMatchedException(this.exceptions['ws']['exact'], code, feedback);
1931
+ }
1932
+ catch (e) {
1933
+ if (e instanceof AuthenticationError) {
1934
+ client.reject(e, 'auth');
1935
+ const method = 'auth';
1936
+ if (method in client.subscriptions) {
1937
+ delete client.subscriptions[method];
1938
+ }
1939
+ return false;
1940
+ }
1941
+ else {
1942
+ client.reject(e);
1943
+ }
1944
+ }
1945
+ }
1946
+ return message;
1947
+ }
1948
+ handleMessage(client, message) {
1949
+ if (this.handleErrorMessage(client, message)) {
1950
+ //
1951
+ // {"id":1583414227,"status":"ok","subbed":"market.btcusdt.mbp.150","ts":1583414229143}
1952
+ //
1953
+ // first ping format
1954
+ //
1955
+ // {"ping": 1645106821667 }
1956
+ //
1957
+ // second ping format
1958
+ //
1959
+ // {"action":"ping","data":{"ts":1645106821667}}
1960
+ //
1961
+ // third pong format
1962
+ //
1963
+ //
1964
+ // auth spot
1965
+ //
1966
+ // {
1967
+ // "action": "req",
1968
+ // "code": 200,
1969
+ // "ch": "auth",
1970
+ // "data": {}
1971
+ // }
1972
+ //
1973
+ // auth non spot
1974
+ //
1975
+ // {
1976
+ // "op": "auth",
1977
+ // "type": "api",
1978
+ // "err-code": 0,
1979
+ // "ts": 1645200307319,
1980
+ // "data": { "user-id": "35930539" }
1981
+ // }
1982
+ //
1983
+ // trade
1984
+ //
1985
+ // {
1986
+ // "action":"push",
1987
+ // "ch":"trade.clearing#ltcusdt#1",
1988
+ // "data":{
1989
+ // "eventType":"trade",
1990
+ // // ?
1991
+ // }
1992
+ // }
1993
+ //
1994
+ if ('id' in message) {
1995
+ this.handleSubscriptionStatus(client, message);
1996
+ return;
1997
+ }
1998
+ if ('action' in message) {
1999
+ const action = this.safeString(message, 'action');
2000
+ if (action === 'ping') {
2001
+ this.handlePing(client, message);
2002
+ return;
2003
+ }
2004
+ if (action === 'sub') {
2005
+ this.handleSubscriptionStatus(client, message);
2006
+ return;
2007
+ }
2008
+ }
2009
+ if ('ch' in message) {
2010
+ if (message['ch'] === 'auth') {
2011
+ this.handleAuthenticate(client, message);
2012
+ return;
2013
+ }
2014
+ else {
2015
+ // route by channel aka topic aka subject
2016
+ this.handleSubject(client, message);
2017
+ return;
2018
+ }
2019
+ }
2020
+ if ('op' in message) {
2021
+ const op = this.safeString(message, 'op');
2022
+ if (op === 'ping') {
2023
+ this.handlePing(client, message);
2024
+ return;
2025
+ }
2026
+ if (op === 'auth') {
2027
+ this.handleAuthenticate(client, message);
2028
+ return;
2029
+ }
2030
+ if (op === 'sub') {
2031
+ this.handleSubscriptionStatus(client, message);
2032
+ return;
2033
+ }
2034
+ if (op === 'notify') {
2035
+ this.handleSubject(client, message);
2036
+ return;
2037
+ }
2038
+ }
2039
+ if ('ping' in message) {
2040
+ this.handlePing(client, message);
2041
+ }
2042
+ }
2043
+ }
2044
+ handleMyTrade(client, message, extendParams = {}) {
2045
+ //
2046
+ // spot
2047
+ //
2048
+ // {
2049
+ // "action":"push",
2050
+ // "ch":"trade.clearing#ltcusdt#1",
2051
+ // "data":{
2052
+ // "eventType":"trade",
2053
+ // "symbol":"ltcusdt",
2054
+ // "orderId":"478862728954426",
2055
+ // "orderSide":"buy",
2056
+ // "orderType":"buy-market",
2057
+ // "accountId":44234548,
2058
+ // "source":"spot-web",
2059
+ // "orderValue":"5.01724137",
2060
+ // "orderCreateTime":1645124660365,
2061
+ // "orderStatus":"filled",
2062
+ // "feeCurrency":"ltc",
2063
+ // "tradePrice":"118.89",
2064
+ // "tradeVolume":"0.042200701236437042",
2065
+ // "aggressor":true,
2066
+ // "tradeId":101539740584,
2067
+ // "tradeTime":1645124660368,
2068
+ // "transactFee":"0.000041778694224073",
2069
+ // "feeDeduct":"0",
2070
+ // "feeDeductType":""
2071
+ // }
2072
+ // }
2073
+ //
2074
+ // contract
2075
+ //
2076
+ // {
2077
+ // "symbol": "ADA/USDT:USDT"
2078
+ // "ch": "orders_cross.ada-usdt"
2079
+ // "trades": [
2080
+ // {
2081
+ // "trade_fee":-0.022099447513812154,
2082
+ // "fee_asset":"ADA",
2083
+ // "trade_id":113913755890,
2084
+ // "id":"113913755890-773207641127878656-1",
2085
+ // "trade_volume":1,
2086
+ // "trade_price":0.0905,
2087
+ // "trade_turnover":10,
2088
+ // "created_at":1604388667194,
2089
+ // "profit":0,
2090
+ // "real_profit": 0,
2091
+ // "role":"maker"
2092
+ // }
2093
+ // ],
2094
+ // }
2095
+ //
2096
+ if (this.myTrades === undefined) {
2097
+ const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
2098
+ this.myTrades = new ArrayCacheBySymbolById(limit);
2099
+ }
2100
+ const cachedTrades = this.myTrades;
2101
+ const messageHash = this.safeString(message, 'ch');
2102
+ if (messageHash !== undefined) {
2103
+ const data = this.safeValue(message, 'data');
2104
+ if (data !== undefined) {
2105
+ const parsed = this.parseWsTrade(data);
2106
+ const symbol = this.safeString(parsed, 'symbol');
2107
+ if (symbol !== undefined) {
2108
+ cachedTrades.append(parsed);
2109
+ client.resolve(this.myTrades, messageHash);
2110
+ }
2111
+ }
2112
+ else {
2113
+ // this trades object is artificially created
2114
+ // in handleOrder
2115
+ const rawTrades = this.safeValue(message, 'trades', []);
2116
+ const marketId = this.safeValue(message, 'symbol');
2117
+ const market = this.market(marketId);
2118
+ for (let i = 0; i < rawTrades.length; i++) {
2119
+ const trade = rawTrades[i];
2120
+ let parsedTrade = this.parseTrade(trade, market);
2121
+ // add extra params (side, type, ...) coming from the order
2122
+ parsedTrade = this.extend(parsedTrade, extendParams);
2123
+ cachedTrades.append(parsedTrade);
2124
+ }
2125
+ // messageHash here is the orders one, so
2126
+ // we have to recreate the trades messageHash = orderMessageHash + ':' + 'trade'
2127
+ const tradesHash = messageHash + ':' + 'trade';
2128
+ client.resolve(this.myTrades, tradesHash);
2129
+ // when we make an global order sub we have to send the channel like this
2130
+ // ch = orders_cross.* and we store messageHash = 'orders_cross'
2131
+ // however it is returned with the specific order update symbol: ch = orders_cross.btc-usd
2132
+ // since this is a global sub, our messageHash does not specify any symbol (ex: orders_cross:trade)
2133
+ // so we must remove it
2134
+ let genericOrderHash = messageHash.replace('.' + market['lowercaseId'], '');
2135
+ genericOrderHash = genericOrderHash.replace('.' + market['lowercaseBaseId'], '');
2136
+ const genericTradesHash = genericOrderHash + ':' + 'trade';
2137
+ client.resolve(this.myTrades, genericTradesHash);
2138
+ }
2139
+ }
2140
+ }
2141
+ parseWsTrade(trade, market = undefined) {
2142
+ // spot private
2143
+ //
2144
+ // {
2145
+ // "eventType":"trade",
2146
+ // "symbol":"ltcusdt",
2147
+ // "orderId":"478862728954426",
2148
+ // "orderSide":"buy",
2149
+ // "orderType":"buy-market",
2150
+ // "accountId":44234548,
2151
+ // "source":"spot-web",
2152
+ // "orderValue":"5.01724137",
2153
+ // "orderCreateTime":1645124660365,
2154
+ // "orderStatus":"filled",
2155
+ // "feeCurrency":"ltc",
2156
+ // "tradePrice":"118.89",
2157
+ // "tradeVolume":"0.042200701236437042",
2158
+ // "aggressor":true,
2159
+ // "tradeId":101539740584,
2160
+ // "tradeTime":1645124660368,
2161
+ // "transactFee":"0.000041778694224073",
2162
+ // "feeDeduct":"0",
2163
+ // "feeDeductType":""
2164
+ // }
2165
+ //
2166
+ const symbol = this.safeSymbol(this.safeString(trade, 'symbol'));
2167
+ const side = this.safeString2(trade, 'side', 'orderSide');
2168
+ const tradeId = this.safeString(trade, 'tradeId');
2169
+ const price = this.safeString(trade, 'tradePrice');
2170
+ const amount = this.safeString(trade, 'tradeVolume');
2171
+ const order = this.safeString(trade, 'orderId');
2172
+ const timestamp = this.safeInteger(trade, 'tradeTime');
2173
+ market = this.market(symbol);
2174
+ const orderType = this.safeString(trade, 'orderType');
2175
+ const aggressor = this.safeValue(trade, 'aggressor');
2176
+ let takerOrMaker = undefined;
2177
+ if (aggressor !== undefined) {
2178
+ takerOrMaker = aggressor ? 'taker' : 'maker';
2179
+ }
2180
+ let type = undefined;
2181
+ let orderTypeParts = [];
2182
+ if (orderType !== undefined) {
2183
+ orderTypeParts = orderType.split('-');
2184
+ type = this.safeString(orderTypeParts, 1);
2185
+ }
2186
+ let fee = undefined;
2187
+ const feeCurrency = this.safeCurrencyCode(this.safeString(trade, 'feeCurrency'));
2188
+ if (feeCurrency !== undefined) {
2189
+ fee = {
2190
+ 'cost': this.safeString(trade, 'transactFee'),
2191
+ 'currency': feeCurrency,
2192
+ };
2193
+ }
2194
+ return this.safeTrade({
2195
+ 'info': trade,
2196
+ 'timestamp': timestamp,
2197
+ 'datetime': this.iso8601(timestamp),
2198
+ 'symbol': symbol,
2199
+ 'id': tradeId,
2200
+ 'order': order,
2201
+ 'type': type,
2202
+ 'takerOrMaker': takerOrMaker,
2203
+ 'side': side,
2204
+ 'price': price,
2205
+ 'amount': amount,
2206
+ 'cost': undefined,
2207
+ 'fee': fee,
2208
+ }, market);
2209
+ }
2210
+ getUrlByMarketType(type, isLinear = true, isPrivate = false) {
2211
+ const api = this.safeString(this.options, 'api', 'api');
2212
+ const hostname = { 'hostname': this.hostname };
2213
+ let hostnameURL = undefined;
2214
+ let url = undefined;
2215
+ if (type === 'spot') {
2216
+ if (isPrivate) {
2217
+ hostnameURL = this.urls['api']['ws'][api]['spot']['private'];
2218
+ }
2219
+ else {
2220
+ hostnameURL = this.urls['api']['ws'][api]['spot']['public'];
2221
+ }
2222
+ url = this.implodeParams(hostnameURL, hostname);
2223
+ }
2224
+ else {
2225
+ const baseUrl = this.urls['api']['ws'][api][type];
2226
+ const subTypeUrl = isLinear ? baseUrl['linear'] : baseUrl['inverse'];
2227
+ url = isPrivate ? subTypeUrl['private'] : subTypeUrl['public'];
2228
+ }
2229
+ return url;
2230
+ }
2231
+ async subscribePublic(url, symbol, messageHash, method = undefined, params = {}) {
2232
+ const requestId = this.requestId();
2233
+ const request = {
2234
+ 'sub': messageHash,
2235
+ 'id': requestId,
2236
+ };
2237
+ const subscription = {
2238
+ 'id': requestId,
2239
+ 'messageHash': messageHash,
2240
+ 'symbol': symbol,
2241
+ 'params': params,
2242
+ };
2243
+ if (method !== undefined) {
2244
+ subscription['method'] = method;
2245
+ }
2246
+ return await this.watch(url, messageHash, this.extend(request, params), messageHash, subscription);
2247
+ }
2248
+ async subscribePrivate(channel, messageHash, type, subtype, params = {}, subscriptionParams = {}) {
2249
+ const requestId = this.requestId();
2250
+ const subscription = {
2251
+ 'id': requestId,
2252
+ 'messageHash': messageHash,
2253
+ 'params': params,
2254
+ };
2255
+ const extendedSubsription = this.extend(subscription, subscriptionParams);
2256
+ let request = undefined;
2257
+ if (type === 'spot') {
2258
+ request = {
2259
+ 'action': 'sub',
2260
+ 'ch': channel,
2261
+ };
2262
+ }
2263
+ else {
2264
+ request = {
2265
+ 'op': 'sub',
2266
+ 'topic': channel,
2267
+ 'cid': requestId,
2268
+ };
2269
+ }
2270
+ const isLinear = subtype === 'linear';
2271
+ const url = this.getUrlByMarketType(type, isLinear, true);
2272
+ const hostname = (type === 'spot') ? this.urls['hostnames']['spot'] : this.urls['hostnames']['contract'];
2273
+ const authParams = {
2274
+ 'type': type,
2275
+ 'url': url,
2276
+ 'hostname': hostname,
2277
+ };
2278
+ if (type === 'spot') {
2279
+ this.options['ws']['gunzip'] = false;
2280
+ }
2281
+ await this.authenticate(authParams);
2282
+ return await this.watch(url, messageHash, this.extend(request, params), channel, extendedSubsription);
2283
+ }
2284
+ async authenticate(params = {}) {
2285
+ const url = this.safeString(params, 'url');
2286
+ const hostname = this.safeString(params, 'hostname');
2287
+ const type = this.safeString(params, 'type');
2288
+ if (url === undefined || hostname === undefined || type === undefined) {
2289
+ throw new ArgumentsRequired(this.id + ' authenticate requires a url, hostname and type argument');
2290
+ }
2291
+ this.checkRequiredCredentials();
2292
+ const messageHash = 'authenticated';
2293
+ const relativePath = url.replace('wss://' + hostname, '');
2294
+ const client = this.client(url);
2295
+ const future = client.future(messageHash);
2296
+ const authenticated = this.safeValue(client.subscriptions, messageHash);
2297
+ if (authenticated === undefined) {
2298
+ const timestamp = this.ymdhms(this.milliseconds(), 'T');
2299
+ let signatureParams = undefined;
2300
+ if (type === 'spot') {
2301
+ signatureParams = {
2302
+ 'accessKey': this.apiKey,
2303
+ 'signatureMethod': 'HmacSHA256',
2304
+ 'signatureVersion': '2.1',
2305
+ 'timestamp': timestamp,
2306
+ };
2307
+ }
2308
+ else {
2309
+ signatureParams = {
2310
+ 'AccessKeyId': this.apiKey,
2311
+ 'SignatureMethod': 'HmacSHA256',
2312
+ 'SignatureVersion': '2',
2313
+ 'Timestamp': timestamp,
2314
+ };
2315
+ }
2316
+ signatureParams = this.keysort(signatureParams);
2317
+ const auth = this.urlencode(signatureParams);
2318
+ const payload = ['GET', hostname, relativePath, auth].join("\n"); // eslint-disable-line quotes
2319
+ const signature = this.hmac(this.encode(payload), this.encode(this.secret), sha256, 'base64');
2320
+ let request = undefined;
2321
+ if (type === 'spot') {
2322
+ const newParams = {
2323
+ 'authType': 'api',
2324
+ 'accessKey': this.apiKey,
2325
+ 'signatureMethod': 'HmacSHA256',
2326
+ 'signatureVersion': '2.1',
2327
+ 'timestamp': timestamp,
2328
+ 'signature': signature,
2329
+ };
2330
+ request = {
2331
+ 'params': newParams,
2332
+ 'action': 'req',
2333
+ 'ch': 'auth',
2334
+ };
2335
+ }
2336
+ else {
2337
+ request = {
2338
+ 'op': 'auth',
2339
+ 'type': 'api',
2340
+ 'AccessKeyId': this.apiKey,
2341
+ 'SignatureMethod': 'HmacSHA256',
2342
+ 'SignatureVersion': '2',
2343
+ 'Timestamp': timestamp,
2344
+ 'Signature': signature,
2345
+ };
2346
+ }
2347
+ const requestId = this.requestId();
2348
+ const subscription = {
2349
+ 'id': requestId,
2350
+ 'messageHash': messageHash,
2351
+ 'params': params,
2352
+ };
2353
+ this.watch(url, messageHash, request, messageHash, subscription);
2354
+ }
2355
+ return future;
2356
+ }
2357
+ }