ccxt 4.1.83 → 4.1.85

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.
@@ -4,6 +4,7 @@ var bitmart$1 = require('../bitmart.js');
4
4
  var errors = require('../base/errors.js');
5
5
  var Cache = require('../base/ws/Cache.js');
6
6
  var sha256 = require('../static_dependencies/noble-hashes/sha256.js');
7
+ var OrderBookSide = require('../base/ws/OrderBookSide.js');
7
8
 
8
9
  // ---------------------------------------------------------------------------
9
10
  // ---------------------------------------------------------------------------
@@ -19,24 +20,38 @@ class bitmart extends bitmart$1 {
19
20
  'cancelOrdersWs': false,
20
21
  'cancelAllOrdersWs': false,
21
22
  'ws': true,
23
+ 'watchBalance': true,
22
24
  'watchTicker': true,
25
+ 'watchTickers': true,
23
26
  'watchOrderBook': true,
24
27
  'watchOrders': true,
25
28
  'watchTrades': true,
26
29
  'watchOHLCV': true,
30
+ 'watchPosition': 'emulated',
31
+ 'watchPositions': true,
27
32
  },
28
33
  'urls': {
29
34
  'api': {
30
35
  'ws': {
31
- 'public': 'wss://ws-manager-compress.{hostname}/api?protocol=1.1',
32
- 'private': 'wss://ws-manager-compress.{hostname}/user?protocol=1.1',
36
+ 'spot': {
37
+ 'public': 'wss://ws-manager-compress.{hostname}/api?protocol=1.1',
38
+ 'private': 'wss://ws-manager-compress.{hostname}/user?protocol=1.1',
39
+ },
40
+ 'swap': {
41
+ 'public': 'wss://openapi-ws.{hostname}/api?protocol=1.1',
42
+ 'private': 'wss://openapi-ws.{hostname}/user?protocol=1.1',
43
+ },
33
44
  },
34
45
  },
35
46
  },
36
47
  'options': {
37
48
  'defaultType': 'spot',
49
+ 'watchBalance': {
50
+ 'fetchBalanceSnapshot': true,
51
+ 'awaitBalanceSnapshot': true, // whether to wait for the balance snapshot before providing updates
52
+ },
38
53
  'watchOrderBook': {
39
- 'depth': 'depth5', // depth5, depth20, depth50
54
+ 'depth': 'depth50', // depth5, depth20, depth50
40
55
  },
41
56
  'ws': {
42
57
  'inflate': true,
@@ -62,33 +77,166 @@ class bitmart extends bitmart$1 {
62
77
  },
63
78
  });
64
79
  }
65
- async subscribe(channel, symbol, params = {}) {
66
- await this.loadMarkets();
80
+ async subscribe(channel, symbol, type, params = {}) {
67
81
  const market = this.market(symbol);
68
- const url = this.implodeHostname(this.urls['api']['ws']['public']);
69
- const messageHash = market['type'] + '/' + channel + ':' + market['id'];
70
- const request = {
71
- 'op': 'subscribe',
72
- 'args': [messageHash],
73
- };
82
+ const url = this.implodeHostname(this.urls['api']['ws'][type]['public']);
83
+ let request = {};
84
+ let messageHash = undefined;
85
+ if (type === 'spot') {
86
+ messageHash = 'spot/' + channel + ':' + market['id'];
87
+ request = {
88
+ 'op': 'subscribe',
89
+ 'args': [messageHash],
90
+ };
91
+ }
92
+ else {
93
+ messageHash = 'futures/' + channel + ':' + market['id'];
94
+ request = {
95
+ 'action': 'subscribe',
96
+ 'args': [messageHash],
97
+ };
98
+ }
74
99
  return await this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
75
100
  }
76
- async subscribePrivate(channel, symbol, params = {}) {
101
+ async watchBalance(params = {}) {
102
+ /**
103
+ * @method
104
+ * @name bitmart#watchBalance
105
+ * @see https://developer-pro.bitmart.com/en/spot/#private-balance-change
106
+ * @see https://developer-pro.bitmart.com/en/futures/#private-assets-channel
107
+ * @description watch balance and get the amount of funds available for trading or funds locked in orders
108
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
109
+ * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
110
+ */
77
111
  await this.loadMarkets();
78
- const market = this.market(symbol);
79
- const url = this.implodeHostname(this.urls['api']['ws']['private']);
80
- const messageHash = channel + ':' + market['id'];
81
- await this.authenticate();
82
- const request = {
83
- 'op': 'subscribe',
84
- 'args': [messageHash],
85
- };
112
+ let type = 'spot';
113
+ [type, params] = this.handleMarketTypeAndParams('watchBalance', undefined, params);
114
+ await this.authenticate(type, params);
115
+ let request = {};
116
+ if (type === 'spot') {
117
+ request = {
118
+ 'op': 'subscribe',
119
+ 'args': ['spot/user/balance:BALANCE_UPDATE'],
120
+ };
121
+ }
122
+ else {
123
+ request = {
124
+ 'action': 'subscribe',
125
+ 'args': ['futures/asset:USDT', 'futures/asset:BTC', 'futures/asset:ETH'],
126
+ };
127
+ }
128
+ const messageHash = 'balance:' + type;
129
+ const url = this.implodeHostname(this.urls['api']['ws'][type]['private']);
130
+ const client = this.client(url);
131
+ this.setBalanceCache(client, type);
132
+ const fetchBalanceSnapshot = this.handleOptionAndParams(this.options, 'watchBalance', 'fetchBalanceSnapshot', true);
133
+ const awaitBalanceSnapshot = this.handleOptionAndParams(this.options, 'watchBalance', 'awaitBalanceSnapshot', false);
134
+ if (fetchBalanceSnapshot && awaitBalanceSnapshot) {
135
+ await client.future(type + ':fetchBalanceSnapshot');
136
+ }
86
137
  return await this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
87
138
  }
139
+ setBalanceCache(client, type) {
140
+ if (type in client.subscriptions) {
141
+ return undefined;
142
+ }
143
+ const options = this.safeValue(this.options, 'watchBalance');
144
+ const fetchBalanceSnapshot = this.handleOptionAndParams(options, 'watchBalance', 'fetchBalanceSnapshot', true);
145
+ if (fetchBalanceSnapshot) {
146
+ const messageHash = type + ':fetchBalanceSnapshot';
147
+ if (!(messageHash in client.futures)) {
148
+ client.future(messageHash);
149
+ this.spawn(this.loadBalanceSnapshot, client, messageHash, type);
150
+ }
151
+ }
152
+ else {
153
+ this.balance[type] = {};
154
+ }
155
+ }
156
+ async loadBalanceSnapshot(client, messageHash, type) {
157
+ const response = await this.fetchBalance({ 'type': type });
158
+ this.balance[type] = this.extend(response, this.safeValue(this.balance, type, {}));
159
+ // don't remove the future from the .futures cache
160
+ const future = client.futures[messageHash];
161
+ future.resolve();
162
+ client.resolve(this.balance[type], 'balance:' + type);
163
+ }
164
+ handleBalance(client, message) {
165
+ //
166
+ // spot
167
+ // {
168
+ // "data":[
169
+ // {
170
+ // "balance_details":[
171
+ // {
172
+ // "av_bal":"0.206000000000000000000000000000",
173
+ // "ccy":"LTC",
174
+ // "fz_bal":"0.100000000000000000000000000000"
175
+ // }
176
+ // ],
177
+ // "event_time":"1701632345415",
178
+ // "event_type":"TRANSACTION_COMPLETED"
179
+ // }
180
+ // ],
181
+ // "table":"spot/user/balance"
182
+ // }
183
+ // swap
184
+ // {
185
+ // group: 'futures/asset:USDT',
186
+ // data: {
187
+ // currency: 'USDT',
188
+ // available_balance: '37.19688649135',
189
+ // position_deposit: '0.788687546',
190
+ // frozen_balance: '0'
191
+ // }
192
+ // }
193
+ //
194
+ const channel = this.safeString2(message, 'table', 'group');
195
+ const data = this.safeValue(message, 'data');
196
+ if (data === undefined) {
197
+ return;
198
+ }
199
+ const isSpot = (channel.indexOf('spot') >= 0);
200
+ const type = isSpot ? 'spot' : 'swap';
201
+ this.balance['info'] = message;
202
+ if (isSpot) {
203
+ if (!Array.isArray(data)) {
204
+ return;
205
+ }
206
+ for (let i = 0; i < data.length; i++) {
207
+ const timestamp = this.safeInteger(message, 'event_time');
208
+ this.balance['timestamp'] = timestamp;
209
+ this.balance['datetime'] = this.iso8601(timestamp);
210
+ const balanceDetails = this.safeValue(data[i], 'balance_details', []);
211
+ for (let ii = 0; ii < balanceDetails.length; ii++) {
212
+ const rawBalance = balanceDetails[i];
213
+ const account = this.account();
214
+ const currencyId = this.safeString(rawBalance, 'ccy');
215
+ const code = this.safeCurrencyCode(currencyId);
216
+ account['free'] = this.safeString(rawBalance, 'av_bal');
217
+ account['total'] = this.safeString(rawBalance, 'fz_bal');
218
+ this.balance[code] = account;
219
+ }
220
+ }
221
+ }
222
+ else {
223
+ const currencyId = this.safeString(data, 'currency');
224
+ const code = this.safeCurrencyCode(currencyId);
225
+ const account = this.account();
226
+ account['free'] = this.safeString(data, 'available_balance');
227
+ account['used'] = this.safeString(data, 'frozen_balance');
228
+ this.balance[type][code] = account;
229
+ }
230
+ this.balance[type] = this.safeBalance(this.balance[type]);
231
+ const messageHash = 'balance:' + type;
232
+ client.resolve(this.balance[type], messageHash);
233
+ }
88
234
  async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
89
235
  /**
90
236
  * @method
91
237
  * @name bitmart#watchTrades
238
+ * @see https://developer-pro.bitmart.com/en/spot/#public-trade-channel
239
+ * @see https://developer-pro.bitmart.com/en/futures/#public-trade-channel
92
240
  * @description get the list of most recent trades for a particular symbol
93
241
  * @param {string} symbol unified symbol of the market to fetch trades for
94
242
  * @param {int} [since] timestamp in ms of the earliest trade to fetch
@@ -98,7 +246,10 @@ class bitmart extends bitmart$1 {
98
246
  */
99
247
  await this.loadMarkets();
100
248
  symbol = this.symbol(symbol);
101
- const trades = await this.subscribe('trade', symbol, params);
249
+ const market = this.market(symbol);
250
+ let type = 'spot';
251
+ [type, params] = this.handleMarketTypeAndParams('watchTrades', market, params);
252
+ const trades = await this.subscribe('trade', symbol, type, params);
102
253
  if (this.newUpdates) {
103
254
  limit = trades.getLimit(symbol, limit);
104
255
  }
@@ -108,17 +259,64 @@ class bitmart extends bitmart$1 {
108
259
  /**
109
260
  * @method
110
261
  * @name bitmart#watchTicker
262
+ * @see https://developer-pro.bitmart.com/en/spot/#public-ticker-channel
111
263
  * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
112
264
  * @param {string} symbol unified symbol of the market to fetch the ticker for
113
265
  * @param {object} [params] extra parameters specific to the exchange API endpoint
114
266
  * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
115
267
  */
116
- return await this.subscribe('ticker', symbol, params);
268
+ await this.loadMarkets();
269
+ symbol = this.symbol(symbol);
270
+ const market = this.market(symbol);
271
+ let type = 'spot';
272
+ [type, params] = this.handleMarketTypeAndParams('watchTicker', market, params);
273
+ if (type === 'swap') {
274
+ throw new errors.NotSupported(this.id + ' watchTicker() does not support ' + type + ' markets. Use watchTickers() instead');
275
+ }
276
+ return await this.subscribe('ticker', symbol, type, params);
277
+ }
278
+ async watchTickers(symbols = undefined, params = {}) {
279
+ /**
280
+ * @method
281
+ * @name bitmart#watchTickers
282
+ * @see https://developer-pro.bitmart.com/en/futures/#overview
283
+ * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
284
+ * @param {string[]} symbols unified symbol of the market to fetch the ticker for
285
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
286
+ * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
287
+ */
288
+ await this.loadMarkets();
289
+ const market = this.getMarketFromSymbols(symbols);
290
+ let type = 'spot';
291
+ [type, params] = this.handleMarketTypeAndParams('watchTickers', market, params);
292
+ symbols = this.marketSymbols(symbols);
293
+ if (type === 'spot') {
294
+ throw new errors.NotSupported(this.id + ' watchTickers() does not support ' + type + ' markets. Use watchTicker() instead');
295
+ }
296
+ const url = this.implodeHostname(this.urls['api']['ws'][type]['public']);
297
+ if (type === 'swap') {
298
+ type = 'futures';
299
+ }
300
+ let messageHash = 'tickers';
301
+ if (symbols !== undefined) {
302
+ messageHash += '::' + symbols.join(',');
303
+ }
304
+ const request = {
305
+ 'action': 'subscribe',
306
+ 'args': ['futures/ticker'],
307
+ };
308
+ const newTickers = await this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
309
+ if (this.newUpdates) {
310
+ return newTickers;
311
+ }
312
+ return this.filterByArray(this.tickers, 'symbol', symbols);
117
313
  }
118
314
  async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
119
315
  /**
120
316
  * @method
121
317
  * @name bitmart#watchOrders
318
+ * @see https://developer-pro.bitmart.com/en/spot/#private-order-channel
319
+ * @see https://developer-pro.bitmart.com/en/futures/#private-order-channel
122
320
  * @description watches information on multiple orders made by the user
123
321
  * @param {string} symbol unified market symbol of the market orders were made in
124
322
  * @param {int} [since] the earliest time in ms to fetch orders for
@@ -126,196 +324,626 @@ class bitmart extends bitmart$1 {
126
324
  * @param {object} [params] extra parameters specific to the exchange API endpoint
127
325
  * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
128
326
  */
129
- if (symbol === undefined) {
130
- throw new errors.ArgumentsRequired(this.id + ' watchOrders() requires a symbol argument');
131
- }
132
327
  await this.loadMarkets();
133
- const market = this.market(symbol);
134
- symbol = market['symbol'];
135
- if (market['type'] !== 'spot') {
136
- throw new errors.ArgumentsRequired(this.id + ' watchOrders supports spot markets only');
328
+ let market = undefined;
329
+ let messageHash = 'orders';
330
+ if (symbol !== undefined) {
331
+ symbol = this.symbol(symbol);
332
+ market = this.market(symbol);
333
+ messageHash = 'orders::' + symbol;
334
+ }
335
+ let type = 'spot';
336
+ [type, params] = this.handleMarketTypeAndParams('watchOrders', market, params);
337
+ await this.authenticate(type, params);
338
+ let request = undefined;
339
+ if (type === 'spot') {
340
+ if (symbol === undefined) {
341
+ throw new errors.ArgumentsRequired(this.id + ' watchOrders() requires a symbol argument for spot markets');
342
+ }
343
+ request = {
344
+ 'op': 'subscribe',
345
+ 'args': ['spot/user/order:' + market['id']],
346
+ };
137
347
  }
138
- const channel = 'spot/user/order';
139
- const orders = await this.subscribePrivate(channel, symbol, params);
348
+ else {
349
+ request = {
350
+ 'action': 'subscribe',
351
+ 'args': ['futures/order'],
352
+ };
353
+ }
354
+ const url = this.implodeHostname(this.urls['api']['ws'][type]['private']);
355
+ const newOrders = await this.watch(url, messageHash, this.deepExtend(request, params), messageHash);
140
356
  if (this.newUpdates) {
141
- limit = orders.getLimit(symbol, limit);
357
+ return newOrders;
142
358
  }
143
- return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
359
+ return this.filterBySymbolSinceLimit(this.orders, symbol, since, limit, true);
144
360
  }
145
361
  handleOrders(client, message) {
146
362
  //
147
- // {
148
- // "data":[
149
- // {
150
- // "symbol": "LTC_USDT",
151
- // "notional": '',
152
- // "side": "buy",
153
- // "last_fill_time": "0",
154
- // "ms_t": "1646216634000",
155
- // "type": "limit",
156
- // "filled_notional": "0.000000000000000000000000000000",
157
- // "last_fill_price": "0",
158
- // "size": "0.500000000000000000000000000000",
159
- // "price": "50.000000000000000000000000000000",
160
- // "last_fill_count": "0",
161
- // "filled_size": "0.000000000000000000000000000000",
162
- // "margin_trading": "0",
163
- // "state": "8",
164
- // "order_id": "24807076628",
165
- // "order_type": "0"
363
+ // spot
364
+ // {
365
+ // "data":[
366
+ // {
367
+ // "symbol": "LTC_USDT",
368
+ // "notional": '',
369
+ // "side": "buy",
370
+ // "last_fill_time": "0",
371
+ // "ms_t": "1646216634000",
372
+ // "type": "limit",
373
+ // "filled_notional": "0.000000000000000000000000000000",
374
+ // "last_fill_price": "0",
375
+ // "size": "0.500000000000000000000000000000",
376
+ // "price": "50.000000000000000000000000000000",
377
+ // "last_fill_count": "0",
378
+ // "filled_size": "0.000000000000000000000000000000",
379
+ // "margin_trading": "0",
380
+ // "state": "8",
381
+ // "order_id": "24807076628",
382
+ // "order_type": "0"
383
+ // }
384
+ // ],
385
+ // "table":"spot/user/order"
386
+ // }
387
+ // swap
388
+ // {
389
+ // "group":"futures/order",
390
+ // "data":[
391
+ // {
392
+ // "action":2,
393
+ // "order":{
394
+ // "order_id":"2312045036986775",
395
+ // "client_order_id":"",
396
+ // "price":"71.61707928",
397
+ // "size":"1",
398
+ // "symbol":"LTCUSDT",
399
+ // "state":1,
400
+ // "side":4,
401
+ // "type":"market",
402
+ // "leverage":"1",
403
+ // "open_type":"cross",
404
+ // "deal_avg_price":"0",
405
+ // "deal_size":"0",
406
+ // "create_time":1701625324646,
407
+ // "update_time":1701625324640,
408
+ // "plan_order_id":"",
409
+ // "last_trade":null
410
+ // }
166
411
  // }
167
- // ],
168
- // "table":"spot/user/order"
169
- // }
412
+ // ]
413
+ // }
170
414
  //
171
- const channel = this.safeString(message, 'table');
172
- const orders = this.safeValue(message, 'data', []);
415
+ const orders = this.safeValue(message, 'data');
416
+ if (orders === undefined) {
417
+ return;
418
+ }
173
419
  const ordersLength = orders.length;
420
+ const newOrders = [];
421
+ const symbols = {};
174
422
  if (ordersLength > 0) {
175
423
  const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
176
424
  if (this.orders === undefined) {
177
425
  this.orders = new Cache.ArrayCacheBySymbolById(limit);
178
426
  }
179
427
  const stored = this.orders;
180
- const marketIds = [];
181
428
  for (let i = 0; i < orders.length; i++) {
182
429
  const order = this.parseWsOrder(orders[i]);
183
430
  stored.append(order);
431
+ newOrders.push(order);
184
432
  const symbol = order['symbol'];
185
- const market = this.market(symbol);
186
- marketIds.push(market['id']);
187
- }
188
- for (let i = 0; i < marketIds.length; i++) {
189
- const messageHash = channel + ':' + marketIds[i];
190
- client.resolve(this.orders, messageHash);
433
+ symbols[symbol] = true;
191
434
  }
192
435
  }
436
+ const newOrderSymbols = Object.keys(symbols);
437
+ for (let i = 0; i < newOrderSymbols.length; i++) {
438
+ const symbol = newOrderSymbols[i];
439
+ this.resolvePromiseIfMessagehashMatches(client, 'orders::', symbol, newOrders);
440
+ }
441
+ client.resolve(newOrders, 'orders');
193
442
  }
194
443
  parseWsOrder(order, market = undefined) {
195
444
  //
196
- // {
197
- // "symbol": "LTC_USDT",
198
- // "notional": '',
199
- // "side": "buy",
200
- // "last_fill_time": "0",
201
- // "ms_t": "1646216634000",
202
- // "type": "limit",
203
- // "filled_notional": "0.000000000000000000000000000000",
204
- // "last_fill_price": "0",
205
- // "size": "0.500000000000000000000000000000",
206
- // "price": "50.000000000000000000000000000000",
207
- // "last_fill_count": "0",
208
- // "filled_size": "0.000000000000000000000000000000",
209
- // "margin_trading": "0",
210
- // "state": "8",
211
- // "order_id": "24807076628",
212
- // "order_type": "0"
213
- // }
214
- //
215
- const marketId = this.safeString(order, 'symbol');
216
- market = this.safeMarket(marketId, market);
217
- const id = this.safeString(order, 'order_id');
218
- const clientOrderId = this.safeString(order, 'clientOid');
219
- const price = this.safeString(order, 'price');
220
- const filled = this.safeString(order, 'filled_size');
221
- const amount = this.safeString(order, 'size');
222
- const type = this.safeString(order, 'type');
223
- const rawState = this.safeString(order, 'state');
224
- const status = this.parseOrderStatusByType(market['type'], rawState);
225
- const timestamp = this.safeInteger(order, 'ms_t');
445
+ // spot
446
+ // {
447
+ // "symbol": "LTC_USDT",
448
+ // "notional": '',
449
+ // "side": "buy",
450
+ // "last_fill_time": "0",
451
+ // "ms_t": "1646216634000",
452
+ // "type": "limit",
453
+ // "filled_notional": "0.000000000000000000000000000000",
454
+ // "last_fill_price": "0",
455
+ // "size": "0.500000000000000000000000000000",
456
+ // "price": "50.000000000000000000000000000000",
457
+ // "last_fill_count": "0",
458
+ // "filled_size": "0.000000000000000000000000000000",
459
+ // "margin_trading": "0",
460
+ // "state": "8",
461
+ // "order_id": "24807076628",
462
+ // "order_type": "0"
463
+ // }
464
+ // swap
465
+ // {
466
+ // "action":2,
467
+ // "order":{
468
+ // "order_id":"2312045036986775",
469
+ // "client_order_id":"",
470
+ // "price":"71.61707928",
471
+ // "size":"1",
472
+ // "symbol":"LTCUSDT",
473
+ // "state":1,
474
+ // "side":4,
475
+ // "type":"market",
476
+ // "leverage":"1",
477
+ // "open_type":"cross",
478
+ // "deal_avg_price":"0",
479
+ // "deal_size":"0",
480
+ // "create_time":1701625324646,
481
+ // "update_time":1701625324640,
482
+ // "plan_order_id":"",
483
+ // "last_trade":null
484
+ // }
485
+ // }
486
+ //
487
+ const action = this.safeNumber(order, 'action');
488
+ const isSpot = (action === undefined);
489
+ if (isSpot) {
490
+ const marketId = this.safeString(order, 'symbol');
491
+ market = this.safeMarket(marketId, market, '_', 'spot');
492
+ const id = this.safeString(order, 'order_id');
493
+ const clientOrderId = this.safeString(order, 'clientOid');
494
+ const price = this.safeString(order, 'price');
495
+ const filled = this.safeString(order, 'filled_size');
496
+ const amount = this.safeString(order, 'size');
497
+ const type = this.safeString(order, 'type');
498
+ const rawState = this.safeString(order, 'state');
499
+ const status = this.parseOrderStatusByType(market['type'], rawState);
500
+ const timestamp = this.safeInteger(order, 'ms_t');
501
+ const symbol = market['symbol'];
502
+ const side = this.safeStringLower(order, 'side');
503
+ return this.safeOrder({
504
+ 'info': order,
505
+ 'symbol': symbol,
506
+ 'id': id,
507
+ 'clientOrderId': clientOrderId,
508
+ 'timestamp': undefined,
509
+ 'datetime': undefined,
510
+ 'lastTradeTimestamp': timestamp,
511
+ 'type': type,
512
+ 'timeInForce': undefined,
513
+ 'postOnly': undefined,
514
+ 'side': side,
515
+ 'price': price,
516
+ 'stopPrice': undefined,
517
+ 'triggerPrice': undefined,
518
+ 'amount': amount,
519
+ 'cost': undefined,
520
+ 'average': undefined,
521
+ 'filled': filled,
522
+ 'remaining': undefined,
523
+ 'status': status,
524
+ 'fee': undefined,
525
+ 'trades': undefined,
526
+ }, market);
527
+ }
528
+ else {
529
+ const orderInfo = this.safeValue(order, 'order');
530
+ const marketId = this.safeString(orderInfo, 'symbol');
531
+ const symbol = this.safeSymbol(marketId, market, '', 'swap');
532
+ const orderId = this.safeString(orderInfo, 'order_id');
533
+ const timestamp = this.safeInteger(orderInfo, 'create_time');
534
+ const updatedTimestamp = this.safeInteger(orderInfo, 'update_time');
535
+ const lastTrade = this.safeValue(orderInfo, 'last_trade');
536
+ const cachedOrders = this.orders;
537
+ const orders = this.safeValue(cachedOrders.hashmap, symbol, {});
538
+ const cachedOrder = this.safeValue(orders, orderId);
539
+ let trades = undefined;
540
+ if (cachedOrder !== undefined) {
541
+ trades = this.safeValue(order, 'trades');
542
+ }
543
+ if (lastTrade !== undefined) {
544
+ if (trades === undefined) {
545
+ trades = [];
546
+ }
547
+ trades.push(lastTrade);
548
+ }
549
+ return this.safeOrder({
550
+ 'info': order,
551
+ 'symbol': symbol,
552
+ 'id': orderId,
553
+ 'clientOrderId': this.safeString(orderInfo, 'client_order_id'),
554
+ 'timestamp': timestamp,
555
+ 'datetime': this.iso8601(timestamp),
556
+ 'lastTradeTimestamp': updatedTimestamp,
557
+ 'type': this.safeString(orderInfo, 'type'),
558
+ 'timeInForce': undefined,
559
+ 'postOnly': undefined,
560
+ 'side': this.parseWsOrderSide(this.safeString(orderInfo, 'side')),
561
+ 'price': this.safeString(orderInfo, 'price'),
562
+ 'stopPrice': undefined,
563
+ 'triggerPrice': undefined,
564
+ 'amount': this.safeString(orderInfo, 'size'),
565
+ 'cost': undefined,
566
+ 'average': this.safeString(orderInfo, 'deal_avg_price'),
567
+ 'filled': this.safeString(orderInfo, 'deal_size'),
568
+ 'remaining': undefined,
569
+ 'status': this.parseWsOrderStatus(this.safeString(order, 'action')),
570
+ 'fee': undefined,
571
+ 'trades': trades,
572
+ }, market);
573
+ }
574
+ }
575
+ parseWsOrderStatus(statusId) {
576
+ const statuses = {
577
+ '1': 'closed',
578
+ '2': 'open',
579
+ '3': 'canceled',
580
+ '4': 'closed',
581
+ '5': 'canceled',
582
+ '6': 'open',
583
+ '7': 'open',
584
+ '8': 'closed',
585
+ '9': 'closed', // active adl match deal
586
+ };
587
+ return this.safeString(statuses, statusId, statusId);
588
+ }
589
+ parseWsOrderSide(sideId) {
590
+ const sides = {
591
+ '1': 'buy',
592
+ '2': 'buy',
593
+ '3': 'sell',
594
+ '4': 'sell', // sell_open_short
595
+ };
596
+ return this.safeString(sides, sideId, sideId);
597
+ }
598
+ async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) {
599
+ /**
600
+ * @method
601
+ * @name bitmart#watchPositions
602
+ * @see https://developer-pro.bitmart.com/en/futures/#private-position-channel
603
+ * @description watch all open positions
604
+ * @param {string[]|undefined} symbols list of unified market symbols
605
+ * @param {object} params extra parameters specific to the exchange API endpoint
606
+ * @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
607
+ */
608
+ await this.loadMarkets();
609
+ const type = 'swap';
610
+ await this.authenticate(type, params);
611
+ symbols = this.marketSymbols(symbols, 'swap', true, true, false);
612
+ let messageHash = 'positions';
613
+ if (symbols !== undefined) {
614
+ messageHash += '::' + symbols.join(',');
615
+ }
616
+ const subscriptionHash = 'futures/position';
617
+ const request = {
618
+ 'action': 'subscribe',
619
+ 'args': ['futures/position'],
620
+ };
621
+ const url = this.implodeHostname(this.urls['api']['ws'][type]['private']);
622
+ const newPositions = await this.watch(url, messageHash, this.deepExtend(request, params), subscriptionHash);
623
+ if (this.newUpdates) {
624
+ return newPositions;
625
+ }
626
+ return this.filterBySymbolsSinceLimit(this.positions, symbols, since, limit);
627
+ }
628
+ handlePositions(client, message) {
629
+ //
630
+ // {
631
+ // "group":"futures/position",
632
+ // "data":[
633
+ // {
634
+ // "symbol":"LTCUSDT",
635
+ // "hold_volume":"5",
636
+ // "position_type":2,
637
+ // "open_type":2,
638
+ // "frozen_volume":"0",
639
+ // "close_volume":"0",
640
+ // "hold_avg_price":"71.582",
641
+ // "close_avg_price":"0",
642
+ // "open_avg_price":"71.582",
643
+ // "liquidate_price":"0",
644
+ // "create_time":1701623327513,
645
+ // "update_time":1701627620439
646
+ // },
647
+ // {
648
+ // "symbol":"LTCUSDT",
649
+ // "hold_volume":"6",
650
+ // "position_type":1,
651
+ // "open_type":2,
652
+ // "frozen_volume":"0",
653
+ // "close_volume":"0",
654
+ // "hold_avg_price":"71.681666666666666667",
655
+ // "close_avg_price":"0",
656
+ // "open_avg_price":"71.681666666666666667",
657
+ // "liquidate_price":"0",
658
+ // "create_time":1701621167225,
659
+ // "update_time":1701628152614
660
+ // }
661
+ // ]
662
+ // }
663
+ //
664
+ const data = this.safeValue(message, 'data', []);
665
+ const cache = this.positions;
666
+ if (this.positions === undefined) {
667
+ this.positions = new Cache.ArrayCacheBySymbolBySide();
668
+ }
669
+ const newPositions = [];
670
+ for (let i = 0; i < data.length; i++) {
671
+ const rawPosition = data[i];
672
+ const position = this.parseWsPosition(rawPosition);
673
+ newPositions.push(position);
674
+ cache.append(position);
675
+ }
676
+ const messageHashes = this.findMessageHashes(client, 'positions::');
677
+ for (let i = 0; i < messageHashes.length; i++) {
678
+ const messageHash = messageHashes[i];
679
+ const parts = messageHash.split('::');
680
+ const symbolsString = parts[1];
681
+ const symbols = symbolsString.split(',');
682
+ const positions = this.filterByArray(newPositions, 'symbol', symbols, false);
683
+ if (!this.isEmpty(positions)) {
684
+ client.resolve(positions, messageHash);
685
+ }
686
+ }
687
+ client.resolve(newPositions, 'positions');
688
+ }
689
+ parseWsPosition(position, market = undefined) {
690
+ //
691
+ // {
692
+ // "symbol":"LTCUSDT",
693
+ // "hold_volume":"6",
694
+ // "position_type":1,
695
+ // "open_type":2,
696
+ // "frozen_volume":"0",
697
+ // "close_volume":"0",
698
+ // "hold_avg_price":"71.681666666666666667",
699
+ // "close_avg_price":"0",
700
+ // "open_avg_price":"71.681666666666666667",
701
+ // "liquidate_price":"0",
702
+ // "create_time":1701621167225,
703
+ // "update_time":1701628152614
704
+ // }
705
+ //
706
+ const marketId = this.safeString(position, 'symbol');
707
+ market = this.safeMarket(marketId, market, '', 'swap');
226
708
  const symbol = market['symbol'];
227
- const side = this.safeStringLower(order, 'side');
228
- return this.safeOrder({
229
- 'info': order,
709
+ const openTimestamp = this.safeInteger(position, 'create_time');
710
+ const timestamp = this.safeInteger(position, 'update_time');
711
+ const side = this.safeNumber(position, 'position_type');
712
+ const marginModeId = this.safeNumber(position, 'open_type');
713
+ return this.safePosition({
714
+ 'info': position,
715
+ 'id': undefined,
230
716
  'symbol': symbol,
231
- 'id': id,
232
- 'clientOrderId': clientOrderId,
233
- 'timestamp': undefined,
234
- 'datetime': undefined,
235
- 'lastTradeTimestamp': timestamp,
236
- 'type': type,
237
- 'timeInForce': undefined,
238
- 'postOnly': undefined,
239
- 'side': side,
240
- 'price': price,
241
- 'stopPrice': undefined,
242
- 'triggerPrice': undefined,
243
- 'amount': amount,
244
- 'cost': undefined,
245
- 'average': undefined,
246
- 'filled': filled,
247
- 'remaining': undefined,
248
- 'status': status,
249
- 'fee': undefined,
250
- 'trades': undefined,
251
- }, market);
717
+ 'timestamp': openTimestamp,
718
+ 'datetime': this.iso8601(openTimestamp),
719
+ 'lastUpdateTimestamp': timestamp,
720
+ 'hedged': undefined,
721
+ 'side': (side === 1) ? 'long' : 'short',
722
+ 'contracts': this.safeNumber(position, 'hold_volume'),
723
+ 'contractSize': this.safeNumber(market, 'contractSize'),
724
+ 'entryPrice': this.safeNumber(position, 'open_avg_price'),
725
+ 'markPrice': this.safeNumber(position, 'hold_avg_price'),
726
+ 'lastPrice': undefined,
727
+ 'notional': undefined,
728
+ 'leverage': undefined,
729
+ 'collateral': undefined,
730
+ 'initialMargin': undefined,
731
+ 'initialMarginPercentage': undefined,
732
+ 'maintenanceMargin': undefined,
733
+ 'maintenanceMarginPercentage': undefined,
734
+ 'unrealizedPnl': undefined,
735
+ 'realizedPnl': undefined,
736
+ 'liquidationPrice': this.safeNumber(position, 'liquidate_price'),
737
+ 'marginMode': (marginModeId === 1) ? 'isolated' : 'cross',
738
+ 'percentage': undefined,
739
+ 'marginRatio': undefined,
740
+ 'stopLossPrice': undefined,
741
+ 'takeProfitPrice': undefined,
742
+ });
252
743
  }
253
744
  handleTrade(client, message) {
254
745
  //
255
- // {
256
- // "table": "spot/trade",
257
- // "data": [
258
- // {
259
- // "price": "52700.50",
260
- // "s_t": 1630982050,
261
- // "side": "buy",
262
- // "size": "0.00112",
263
- // "symbol": "BTC_USDT"
264
- // },
265
- // ]
266
- // }
746
+ // spot
747
+ // {
748
+ // "table": "spot/trade",
749
+ // "data": [
750
+ // {
751
+ // "price": "52700.50",
752
+ // "s_t": 1630982050,
753
+ // "side": "buy",
754
+ // "size": "0.00112",
755
+ // "symbol": "BTC_USDT"
756
+ // },
757
+ // ]
758
+ // }
267
759
  //
268
- const table = this.safeString(message, 'table');
269
- const data = this.safeValue(message, 'data', []);
270
- const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000);
760
+ // swap
761
+ // {
762
+ // "group":"futures/trade:BTCUSDT",
763
+ // "data":[
764
+ // {
765
+ // "trade_id":6798697637,
766
+ // "contract_id":1,
767
+ // "symbol":"BTCUSDT",
768
+ // "deal_price":"39735.8",
769
+ // "deal_vol":"2",
770
+ // "type":0,
771
+ // "way":1,
772
+ // "create_time":1701618503,
773
+ // "create_time_mill":1701618503517,
774
+ // "created_at":"2023-12-03T15:48:23.517518538Z"
775
+ // }
776
+ // ]
777
+ // }
778
+ //
779
+ const channel = this.safeString2(message, 'table', 'group');
780
+ const isSpot = (channel.indexOf('spot') >= 0);
781
+ const data = this.safeValue(message, 'data');
782
+ if (data === undefined) {
783
+ return;
784
+ }
785
+ let stored = undefined;
271
786
  for (let i = 0; i < data.length; i++) {
272
- const trade = this.parseTrade(data[i]);
787
+ const trade = this.parseWsTrade(data[i]);
273
788
  const symbol = trade['symbol'];
274
- const marketId = this.safeString(trade['info'], 'symbol');
275
- const messageHash = table + ':' + marketId;
276
- let stored = this.safeValue(this.trades, symbol);
789
+ const tradesLimit = this.safeInteger(this.options, 'tradesLimit', 1000);
790
+ stored = this.safeValue(this.trades, symbol);
277
791
  if (stored === undefined) {
278
792
  stored = new Cache.ArrayCache(tradesLimit);
279
793
  this.trades[symbol] = stored;
280
794
  }
281
795
  stored.append(trade);
282
- client.resolve(stored, messageHash);
283
796
  }
797
+ let messageHash = channel;
798
+ if (isSpot) {
799
+ messageHash += ':' + this.safeString(data[0], 'symbol');
800
+ }
801
+ client.resolve(stored, messageHash);
284
802
  return message;
285
803
  }
804
+ parseWsTrade(trade, market = undefined) {
805
+ // spot
806
+ // {
807
+ // "price": "52700.50",
808
+ // "s_t": 1630982050,
809
+ // "side": "buy",
810
+ // "size": "0.00112",
811
+ // "symbol": "BTC_USDT"
812
+ // }
813
+ // swap
814
+ // {
815
+ // "trade_id":6798697637,
816
+ // "contract_id":1,
817
+ // "symbol":"BTCUSDT",
818
+ // "deal_price":"39735.8",
819
+ // "deal_vol":"2",
820
+ // "type":0,
821
+ // "way":1,
822
+ // "create_time":1701618503,
823
+ // "create_time_mill":1701618503517,
824
+ // "created_at":"2023-12-03T15:48:23.517518538Z"
825
+ // }
826
+ //
827
+ const contractId = this.safeString(trade, 'contract_id');
828
+ const marketType = (contractId === undefined) ? 'spot' : 'swap';
829
+ const marketDelimiter = (marketType === 'spot') ? '_' : '';
830
+ const timestamp = this.safeInteger(trade, 'create_time_mill', this.safeTimestamp(trade, 's_t'));
831
+ const marketId = this.safeString(trade, 'symbol');
832
+ return this.safeTrade({
833
+ 'info': trade,
834
+ 'id': this.safeString(trade, 'trade_id'),
835
+ 'order': undefined,
836
+ 'timestamp': timestamp,
837
+ 'datetime': this.iso8601(timestamp),
838
+ 'symbol': this.safeSymbol(marketId, market, marketDelimiter, marketType),
839
+ 'type': undefined,
840
+ 'side': this.safeString(trade, 'side'),
841
+ 'price': this.safeString2(trade, 'price', 'deal_price'),
842
+ 'amount': this.safeString2(trade, 'size', 'deal_vol'),
843
+ 'cost': undefined,
844
+ 'takerOrMaker': undefined,
845
+ 'fee': undefined,
846
+ }, market);
847
+ }
286
848
  handleTicker(client, message) {
287
849
  //
288
- // {
289
- // "data": [
290
- // {
291
- // "base_volume_24h": "78615593.81",
292
- // "high_24h": "52756.97",
293
- // "last_price": "52638.31",
294
- // "low_24h": "50991.35",
295
- // "open_24h": "51692.03",
296
- // "s_t": 1630981727,
297
- // "symbol": "BTC_USDT"
298
- // }
299
- // ],
300
- // "table": "spot/ticker"
301
- // }
850
+ // {
851
+ // "data": [
852
+ // {
853
+ // "base_volume_24h": "78615593.81",
854
+ // "high_24h": "52756.97",
855
+ // "last_price": "52638.31",
856
+ // "low_24h": "50991.35",
857
+ // "open_24h": "51692.03",
858
+ // "s_t": 1630981727,
859
+ // "symbol": "BTC_USDT"
860
+ // }
861
+ // ],
862
+ // "table": "spot/ticker"
863
+ // }
864
+ // {
865
+ // "group":"futures/ticker",
866
+ // "data":{
867
+ // "symbol":"BTCUSDT",
868
+ // "volume_24":"117387.58",
869
+ // "fair_price":"146.24",
870
+ // "last_price":"146.24",
871
+ // "range":"147.17",
872
+ // "ask_price": "147.11",
873
+ // "ask_vol": "1",
874
+ // "bid_price": "142.11",
875
+ // "bid_vol": "1"
876
+ // }
877
+ // }
302
878
  //
303
879
  const table = this.safeString(message, 'table');
304
- const data = this.safeValue(message, 'data', []);
305
- for (let i = 0; i < data.length; i++) {
306
- const ticker = this.parseTicker(data[i]);
307
- const symbol = ticker['symbol'];
308
- const marketId = this.safeString(ticker['info'], 'symbol');
309
- const messageHash = table + ':' + marketId;
880
+ const isSpot = (table !== undefined);
881
+ const data = this.safeValue(message, 'data');
882
+ if (data === undefined) {
883
+ return;
884
+ }
885
+ if (isSpot) {
886
+ for (let i = 0; i < data.length; i++) {
887
+ const ticker = this.parseTicker(data[i]);
888
+ const symbol = ticker['symbol'];
889
+ const marketId = this.safeString(ticker['info'], 'symbol');
890
+ const messageHash = table + ':' + marketId;
891
+ this.tickers[symbol] = ticker;
892
+ client.resolve(ticker, messageHash);
893
+ }
894
+ }
895
+ else {
896
+ const ticker = this.parseWsSwapTicker(data);
897
+ const symbol = this.safeString(ticker, 'symbol');
310
898
  this.tickers[symbol] = ticker;
311
- client.resolve(ticker, messageHash);
899
+ client.resolve(ticker, 'tickers');
900
+ this.resolvePromiseIfMessagehashMatches(client, 'tickers::', symbol, ticker);
312
901
  }
313
902
  return message;
314
903
  }
904
+ parseWsSwapTicker(ticker, market = undefined) {
905
+ //
906
+ // {
907
+ // "symbol":"BTCUSDT",
908
+ // "volume_24":"117387.58",
909
+ // "fair_price":"146.24",
910
+ // "last_price":"146.24",
911
+ // "range":"147.17",
912
+ // "ask_price": "147.11",
913
+ // "ask_vol": "1",
914
+ // "bid_price": "142.11",
915
+ // "bid_vol": "1"
916
+ // }
917
+ const marketId = this.safeString(ticker, 'symbol');
918
+ return this.safeTicker({
919
+ 'symbol': this.safeSymbol(marketId, market, '', 'swap'),
920
+ 'timestamp': undefined,
921
+ 'datetime': undefined,
922
+ 'high': undefined,
923
+ 'low': undefined,
924
+ 'bid': this.safeString(ticker, 'bid_price'),
925
+ 'bidVolume': this.safeString(ticker, 'bid_vol'),
926
+ 'ask': this.safeString(ticker, 'ask_price'),
927
+ 'askVolume': this.safeString(ticker, 'ask_vol'),
928
+ 'vwap': undefined,
929
+ 'open': undefined,
930
+ 'close': undefined,
931
+ 'last': this.safeString(ticker, 'last_price'),
932
+ 'previousClose': undefined,
933
+ 'change': undefined,
934
+ 'percentage': undefined,
935
+ 'average': this.safeString(ticker, 'fair_price'),
936
+ 'baseVolume': undefined,
937
+ 'quoteVolume': this.safeString(ticker, 'volume_24'),
938
+ 'info': ticker,
939
+ }, market);
940
+ }
315
941
  async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
316
942
  /**
317
943
  * @method
318
944
  * @name bitmart#watchOHLCV
945
+ * @see https://developer-pro.bitmart.com/en/spot/#public-kline-channel
946
+ * @see https://developer-pro.bitmart.com/en/futures/#public-klinebin-channel
319
947
  * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
320
948
  * @param {string} symbol unified symbol of the market to fetch OHLCV data for
321
949
  * @param {string} timeframe the length of time each candle represents
@@ -326,10 +954,19 @@ class bitmart extends bitmart$1 {
326
954
  */
327
955
  await this.loadMarkets();
328
956
  symbol = this.symbol(symbol);
957
+ const market = this.market(symbol);
958
+ let type = 'spot';
959
+ [type, params] = this.handleMarketTypeAndParams('watchOrderBook', market, params);
329
960
  const timeframes = this.safeValue(this.options, 'timeframes', {});
330
961
  const interval = this.safeString(timeframes, timeframe);
331
- const name = 'kline' + interval;
332
- const ohlcv = await this.subscribe(name, symbol, params);
962
+ let name = undefined;
963
+ if (type === 'spot') {
964
+ name = 'kline' + interval;
965
+ }
966
+ else {
967
+ name = 'klineBin' + interval;
968
+ }
969
+ const ohlcv = await this.subscribe(name, symbol, type, params);
333
970
  if (this.newUpdates) {
334
971
  limit = ohlcv.getLimit(symbol, limit);
335
972
  }
@@ -337,40 +974,82 @@ class bitmart extends bitmart$1 {
337
974
  }
338
975
  handleOHLCV(client, message) {
339
976
  //
340
- // {
341
- // "data": [
342
- // {
343
- // "candle": [
344
- // 1631056350,
345
- // "46532.83",
346
- // "46555.71",
347
- // "46511.41",
348
- // "46555.71",
349
- // "0.25"
350
- // ],
351
- // "symbol": "BTC_USDT"
352
- // }
353
- // ],
354
- // "table": "spot/kline1m"
355
- // }
977
+ // {
978
+ // "data": [
979
+ // {
980
+ // "candle": [
981
+ // 1631056350,
982
+ // "46532.83",
983
+ // "46555.71",
984
+ // "46511.41",
985
+ // "46555.71",
986
+ // "0.25"
987
+ // ],
988
+ // "symbol": "BTC_USDT"
989
+ // }
990
+ // ],
991
+ // "table": "spot/kline1m"
992
+ // }
993
+ // swap
994
+ // {
995
+ // "group":"futures/klineBin1m:BTCUSDT",
996
+ // "data":{
997
+ // "symbol":"BTCUSDT",
998
+ // "items":[
999
+ // {
1000
+ // "o":"39635.8",
1001
+ // "h":"39636",
1002
+ // "l":"39614.4",
1003
+ // "c":"39629.7",
1004
+ // "v":"31852",
1005
+ // "ts":1701617761
1006
+ // }
1007
+ // ]
1008
+ // }
1009
+ // }
356
1010
  //
357
- const table = this.safeString(message, 'table');
358
- const data = this.safeValue(message, 'data', []);
359
- const parts = table.split('/');
360
- const part1 = this.safeString(parts, 1);
361
- const interval = part1.replace('kline', '');
1011
+ const channel = this.safeString2(message, 'table', 'group');
1012
+ const isSpot = (channel.indexOf('spot') >= 0);
1013
+ const data = this.safeValue(message, 'data');
1014
+ if (data === undefined) {
1015
+ return;
1016
+ }
1017
+ const parts = channel.split('/');
1018
+ const part1 = this.safeString(parts, 1, '');
1019
+ let interval = part1.replace('kline', '');
1020
+ interval = interval.replace('Bin', '');
1021
+ const intervalParts = interval.split(':');
1022
+ interval = this.safeString(intervalParts, 0);
362
1023
  // use a reverse lookup in a static map instead
363
1024
  const timeframes = this.safeValue(this.options, 'timeframes', {});
364
1025
  const timeframe = this.findTimeframe(interval, timeframes);
365
1026
  const duration = this.parseTimeframe(timeframe);
366
1027
  const durationInMs = duration * 1000;
367
- for (let i = 0; i < data.length; i++) {
368
- const marketId = this.safeString(data[i], 'symbol');
369
- const candle = this.safeValue(data[i], 'candle');
370
- const market = this.safeMarket(marketId);
1028
+ if (isSpot) {
1029
+ for (let i = 0; i < data.length; i++) {
1030
+ const marketId = this.safeString(data[i], 'symbol');
1031
+ const market = this.safeMarket(marketId);
1032
+ const symbol = market['symbol'];
1033
+ const rawOHLCV = this.safeValue(data[i], 'candle');
1034
+ const parsed = this.parseOHLCV(rawOHLCV, market);
1035
+ parsed[0] = this.parseToInt(parsed[0] / durationInMs) * durationInMs;
1036
+ this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {});
1037
+ let stored = this.safeValue(this.ohlcvs[symbol], timeframe);
1038
+ if (stored === undefined) {
1039
+ const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
1040
+ stored = new Cache.ArrayCacheByTimestamp(limit);
1041
+ this.ohlcvs[symbol][timeframe] = stored;
1042
+ }
1043
+ stored.append(parsed);
1044
+ const messageHash = channel + ':' + marketId;
1045
+ client.resolve(stored, messageHash);
1046
+ }
1047
+ }
1048
+ else {
1049
+ const marketId = this.safeString(data, 'symbol');
1050
+ const market = this.safeMarket(marketId, undefined, '', 'swap');
371
1051
  const symbol = market['symbol'];
372
- const parsed = this.parseOHLCV(candle, market);
373
- parsed[0] = this.parseToInt(parsed[0] / durationInMs) * durationInMs;
1052
+ const items = this.safeValue(data, 'items', []);
374
1053
  this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {});
375
1054
  let stored = this.safeValue(this.ohlcvs[symbol], timeframe);
376
1055
  if (stored === undefined) {
@@ -378,24 +1057,34 @@ class bitmart extends bitmart$1 {
378
1057
  stored = new Cache.ArrayCacheByTimestamp(limit);
379
1058
  this.ohlcvs[symbol][timeframe] = stored;
380
1059
  }
381
- stored.append(parsed);
382
- const messageHash = table + ':' + marketId;
383
- client.resolve(stored, messageHash);
1060
+ for (let i = 0; i < items.length; i++) {
1061
+ const candle = items[i];
1062
+ const parsed = this.parseOHLCV(candle, market);
1063
+ stored.append(parsed);
1064
+ }
1065
+ client.resolve(stored, channel);
384
1066
  }
385
1067
  }
386
1068
  async watchOrderBook(symbol, limit = undefined, params = {}) {
387
1069
  /**
388
1070
  * @method
389
1071
  * @name bitmart#watchOrderBook
1072
+ * @see https://developer-pro.bitmart.com/en/spot/#public-depth-all-channel
1073
+ * @see https://developer-pro.bitmart.com/en/futures/#public-depth-channel
390
1074
  * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
391
1075
  * @param {string} symbol unified symbol of the market to fetch the order book for
392
1076
  * @param {int} [limit] the maximum amount of order book entries to return
393
1077
  * @param {object} [params] extra parameters specific to the exchange API endpoint
394
1078
  * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
395
1079
  */
1080
+ await this.loadMarkets();
396
1081
  const options = this.safeValue(this.options, 'watchOrderBook', {});
397
1082
  const depth = this.safeString(options, 'depth', 'depth50');
398
- const orderbook = await this.subscribe(depth, symbol, params);
1083
+ symbol = this.symbol(symbol);
1084
+ const market = this.market(symbol);
1085
+ let type = 'spot';
1086
+ [type, params] = this.handleMarketTypeAndParams('watchOrderBook', market, params);
1087
+ const orderbook = await this.subscribe(depth, symbol, type, params);
399
1088
  return orderbook.limit();
400
1089
  }
401
1090
  handleDelta(bookside, delta) {
@@ -443,22 +1132,19 @@ class bitmart extends bitmart$1 {
443
1132
  }
444
1133
  handleOrderBook(client, message) {
445
1134
  //
1135
+ // spot
446
1136
  // {
447
1137
  // "data": [
448
1138
  // {
449
1139
  // "asks": [
450
1140
  // [ '46828.38', "0.21847" ],
451
1141
  // [ '46830.68', "0.08232" ],
452
- // [ '46832.08', "0.09285" ],
453
- // [ '46837.82', "0.02028" ],
454
- // [ '46839.43', "0.15068" ]
1142
+ // ...
455
1143
  // ],
456
1144
  // "bids": [
457
1145
  // [ '46820.78', "0.00444" ],
458
1146
  // [ '46814.33', "0.00234" ],
459
- // [ '46813.50', "0.05021" ],
460
- // [ '46808.14', "0.00217" ],
461
- // [ '46808.04', "0.00013" ]
1147
+ // ...
462
1148
  // ],
463
1149
  // "ms_t": 1631044962431,
464
1150
  // "symbol": "BTC_USDT"
@@ -466,32 +1152,99 @@ class bitmart extends bitmart$1 {
466
1152
  // ],
467
1153
  // "table": "spot/depth5"
468
1154
  // }
1155
+ // swap
1156
+ // {
1157
+ // "group":"futures/depth50:BTCUSDT",
1158
+ // "data":{
1159
+ // "symbol":"BTCUSDT",
1160
+ // "way":1,
1161
+ // "depths":[
1162
+ // {
1163
+ // "price":"39509.8",
1164
+ // "vol":"2379"
1165
+ // },
1166
+ // {
1167
+ // "price":"39509.6",
1168
+ // "vol":"6815"
1169
+ // },
1170
+ // ...
1171
+ // ],
1172
+ // "ms_t":1701566021194
1173
+ // }
1174
+ // }
469
1175
  //
470
- const data = this.safeValue(message, 'data', []);
471
- const table = this.safeString(message, 'table');
1176
+ const data = this.safeValue(message, 'data');
1177
+ if (data === undefined) {
1178
+ return;
1179
+ }
1180
+ const depths = this.safeValue(data, 'depths');
1181
+ const isSpot = (depths === undefined);
1182
+ const table = this.safeString2(message, 'table', 'group');
472
1183
  const parts = table.split('/');
473
1184
  const lastPart = this.safeString(parts, 1);
474
- const limitString = lastPart.replace('depth', '');
475
- const limit = parseInt(limitString);
476
- for (let i = 0; i < data.length; i++) {
477
- const update = data[i];
478
- const marketId = this.safeString(update, 'symbol');
1185
+ let limitString = lastPart.replace('depth', '');
1186
+ const dotsIndex = limitString.indexOf(':');
1187
+ limitString = limitString.slice(0, dotsIndex);
1188
+ const limit = this.parseToInt(limitString);
1189
+ if (isSpot) {
1190
+ for (let i = 0; i < data.length; i++) {
1191
+ const update = data[i];
1192
+ const marketId = this.safeString(update, 'symbol');
1193
+ const symbol = this.safeSymbol(marketId);
1194
+ let orderbook = this.safeValue(this.orderbooks, symbol);
1195
+ if (orderbook === undefined) {
1196
+ orderbook = this.orderBook({}, limit);
1197
+ orderbook['symbol'] = symbol;
1198
+ this.orderbooks[symbol] = orderbook;
1199
+ }
1200
+ orderbook.reset({});
1201
+ this.handleOrderBookMessage(client, update, orderbook);
1202
+ const timestamp = this.safeInteger(update, 'ms_t');
1203
+ orderbook['timestamp'] = timestamp;
1204
+ orderbook['datetime'] = this.iso8601(timestamp);
1205
+ const messageHash = table + ':' + marketId;
1206
+ client.resolve(orderbook, messageHash);
1207
+ }
1208
+ }
1209
+ else {
1210
+ const marketId = this.safeString(data, 'symbol');
479
1211
  const symbol = this.safeSymbol(marketId);
480
1212
  let orderbook = this.safeValue(this.orderbooks, symbol);
481
1213
  if (orderbook === undefined) {
482
1214
  orderbook = this.orderBook({}, limit);
1215
+ orderbook['symbol'] = symbol;
483
1216
  this.orderbooks[symbol] = orderbook;
484
1217
  }
485
- orderbook.reset({});
486
- this.handleOrderBookMessage(client, update, orderbook);
487
- const messageHash = table + ':' + marketId;
1218
+ const way = this.safeNumber(data, 'way');
1219
+ const side = (way === 1) ? 'bids' : 'asks';
1220
+ if (way === 1) {
1221
+ orderbook[side] = new OrderBookSide.Bids([], limit);
1222
+ }
1223
+ else {
1224
+ orderbook[side] = new OrderBookSide.Asks([], limit);
1225
+ }
1226
+ for (let i = 0; i < depths.length; i++) {
1227
+ const depth = depths[i];
1228
+ const price = this.safeNumber(depth, 'price');
1229
+ const amount = this.safeNumber(depth, 'vol');
1230
+ const orderbookSide = this.safeValue(orderbook, side);
1231
+ orderbookSide.store(price, amount);
1232
+ }
1233
+ const bidsLength = orderbook['bids'].length;
1234
+ const asksLength = orderbook['asks'].length;
1235
+ if ((bidsLength === 0) || (asksLength === 0)) {
1236
+ return;
1237
+ }
1238
+ const timestamp = this.safeInteger(data, 'ms_t');
1239
+ orderbook['timestamp'] = timestamp;
1240
+ orderbook['datetime'] = this.iso8601(timestamp);
1241
+ const messageHash = table;
488
1242
  client.resolve(orderbook, messageHash);
489
1243
  }
490
- return message;
491
1244
  }
492
- async authenticate(params = {}) {
1245
+ async authenticate(type, params = {}) {
493
1246
  this.checkRequiredCredentials();
494
- const url = this.implodeHostname(this.urls['api']['ws']['private']);
1247
+ const url = this.implodeHostname(this.urls['api']['ws'][type]['private']);
495
1248
  const messageHash = 'authenticated';
496
1249
  const client = this.client(url);
497
1250
  const future = client.future(messageHash);
@@ -502,15 +1255,28 @@ class bitmart extends bitmart$1 {
502
1255
  const path = 'bitmart.WebSocket';
503
1256
  const auth = timestamp + '#' + memo + '#' + path;
504
1257
  const signature = this.hmac(this.encode(auth), this.encode(this.secret), sha256.sha256);
505
- const operation = 'login';
506
- const request = {
507
- 'op': operation,
508
- 'args': [
509
- this.apiKey,
510
- timestamp,
511
- signature,
512
- ],
513
- };
1258
+ let request = undefined;
1259
+ if (type === 'spot') {
1260
+ request = {
1261
+ 'op': 'login',
1262
+ 'args': [
1263
+ this.apiKey,
1264
+ timestamp,
1265
+ signature,
1266
+ ],
1267
+ };
1268
+ }
1269
+ else {
1270
+ request = {
1271
+ 'action': 'access',
1272
+ 'args': [
1273
+ this.apiKey,
1274
+ timestamp,
1275
+ signature,
1276
+ 'web',
1277
+ ],
1278
+ };
1279
+ }
514
1280
  const message = this.extend(request, params);
515
1281
  this.watch(url, messageHash, message, messageHash);
516
1282
  }
@@ -518,13 +1284,16 @@ class bitmart extends bitmart$1 {
518
1284
  }
519
1285
  handleSubscriptionStatus(client, message) {
520
1286
  //
521
- // {"event":"subscribe","channel":"spot/depth:BTC-USDT"}
1287
+ // {"event":"subscribe","channel":"spot/depth:BTC-USDT"}
522
1288
  //
523
1289
  return message;
524
1290
  }
525
1291
  handleAuthenticate(client, message) {
526
1292
  //
527
- // { event: "login" }
1293
+ // spot
1294
+ // { event: "login" }
1295
+ // swap
1296
+ // { action: 'access', success: true }
528
1297
  //
529
1298
  const messageHash = 'authenticated';
530
1299
  const future = this.safeValue(client.futures, messageHash);
@@ -532,29 +1301,41 @@ class bitmart extends bitmart$1 {
532
1301
  }
533
1302
  handleErrorMessage(client, message) {
534
1303
  //
535
- // { event: "error", message: "Invalid sign", errorCode: 30013 }
536
- // {"event":"error","message":"Unrecognized request: {\"event\":\"subscribe\",\"channel\":\"spot/depth:BTC-USDT\"}","errorCode":30039}
1304
+ // { event: "error", message: "Invalid sign", errorCode: 30013 }
1305
+ // {"event":"error","message":"Unrecognized request: {\"event\":\"subscribe\",\"channel\":\"spot/depth:BTC-USDT\"}","errorCode":30039}
1306
+ // {
1307
+ // action: '',
1308
+ // group: 'futures/trade:BTCUSDT',
1309
+ // success: false,
1310
+ // request: { action: '', args: [ 'futures/trade:BTCUSDT' ] },
1311
+ // error: 'Invalid action [] for group [futures/trade:BTCUSDT]'
1312
+ // }
537
1313
  //
538
1314
  const errorCode = this.safeString(message, 'errorCode');
1315
+ const error = this.safeString(message, 'error');
539
1316
  try {
540
- if (errorCode !== undefined) {
1317
+ if (errorCode !== undefined || error !== undefined) {
541
1318
  const feedback = this.id + ' ' + this.json(message);
542
1319
  this.throwExactlyMatchedException(this.exceptions['exact'], errorCode, feedback);
543
- const messageString = this.safeValue(message, 'message');
544
- if (messageString !== undefined) {
545
- this.throwBroadlyMatchedException(this.exceptions['broad'], messageString, feedback);
1320
+ const messageString = this.safeValue(message, 'message', error);
1321
+ this.throwBroadlyMatchedException(this.exceptions['broad'], messageString, feedback);
1322
+ const action = this.safeString(message, 'action');
1323
+ if (action === 'access') {
1324
+ throw new errors.AuthenticationError(feedback);
546
1325
  }
1326
+ throw new errors.ExchangeError(feedback);
547
1327
  }
548
1328
  return false;
549
1329
  }
550
1330
  catch (e) {
551
- if (e instanceof errors.AuthenticationError) {
1331
+ if ((e instanceof errors.AuthenticationError)) {
552
1332
  const messageHash = 'authenticated';
553
1333
  client.reject(e, messageHash);
554
1334
  if (messageHash in client.subscriptions) {
555
1335
  delete client.subscriptions[messageHash];
556
1336
  }
557
1337
  }
1338
+ client.reject(e);
558
1339
  return true;
559
1340
  }
560
1341
  }
@@ -587,14 +1368,14 @@ class bitmart extends bitmart$1 {
587
1368
  //
588
1369
  // { data: '', table: "spot/user/order" }
589
1370
  //
590
- const table = this.safeString(message, 'table');
591
- if (table === undefined) {
592
- const event = this.safeString(message, 'event');
1371
+ const channel = this.safeString2(message, 'table', 'group');
1372
+ if (channel === undefined) {
1373
+ const event = this.safeString2(message, 'event', 'action');
593
1374
  if (event !== undefined) {
594
1375
  const methods = {
595
1376
  // 'info': this.handleSystemStatus,
596
- // 'book': 'handleOrderBook',
597
1377
  'login': this.handleAuthenticate,
1378
+ 'access': this.handleAuthenticate,
598
1379
  'subscribe': this.handleSubscriptionStatus,
599
1380
  };
600
1381
  const method = this.safeValue(methods, event);
@@ -607,30 +1388,25 @@ class bitmart extends bitmart$1 {
607
1388
  }
608
1389
  }
609
1390
  else {
610
- const parts = table.split('/');
611
- const name = this.safeString(parts, 1);
612
1391
  const methods = {
613
- 'depth': this.handleOrderBook,
614
1392
  'depth5': this.handleOrderBook,
615
1393
  'depth20': this.handleOrderBook,
616
1394
  'depth50': this.handleOrderBook,
617
1395
  'ticker': this.handleTicker,
618
1396
  'trade': this.handleTrade,
619
- // ...
1397
+ 'kline': this.handleOHLCV,
1398
+ 'order': this.handleOrders,
1399
+ 'position': this.handlePositions,
1400
+ 'balance': this.handleBalance,
1401
+ 'asset': this.handleBalance,
620
1402
  };
621
- let method = this.safeValue(methods, name);
622
- if (name.indexOf('kline') >= 0) {
623
- method = this.handleOHLCV;
624
- }
625
- const privateName = this.safeString(parts, 2);
626
- if (privateName === 'order') {
627
- method = this.handleOrders;
628
- }
629
- if (method === undefined) {
630
- return message;
631
- }
632
- else {
633
- return method.call(this, client, message);
1403
+ const keys = Object.keys(methods);
1404
+ for (let i = 0; i < keys.length; i++) {
1405
+ const key = keys[i];
1406
+ if (channel.indexOf(key) >= 0) {
1407
+ const method = this.safeValue(methods, key);
1408
+ return method.call(this, client, message);
1409
+ }
634
1410
  }
635
1411
  }
636
1412
  }