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