ccxt 4.5.44 → 4.5.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +9 -12
  2. package/dist/ccxt.browser.min.js +3 -3
  3. package/dist/cjs/ccxt.js +1 -12
  4. package/dist/cjs/src/abstract/kucoinfutures.js +1 -1
  5. package/dist/cjs/src/base/Exchange.js +36 -3
  6. package/dist/cjs/src/base/functions/encode.js +2 -2
  7. package/dist/cjs/src/base/functions/generic.js +8 -2
  8. package/dist/cjs/src/bitrue.js +1 -1
  9. package/dist/cjs/src/bitteam.js +1 -1
  10. package/dist/cjs/src/btcbox.js +1 -1
  11. package/dist/cjs/src/cex.js +1 -0
  12. package/dist/cjs/src/gate.js +227 -168
  13. package/dist/cjs/src/grvt.js +3 -2
  14. package/dist/cjs/src/hyperliquid.js +16 -5
  15. package/dist/cjs/src/kraken.js +2 -2
  16. package/dist/cjs/src/krakenfutures.js +1 -5
  17. package/dist/cjs/src/kucoin.js +4729 -970
  18. package/dist/cjs/src/kucoinfutures.js +14 -3434
  19. package/dist/cjs/src/lbank.js +1 -1
  20. package/dist/cjs/src/poloniex.js +1 -1
  21. package/dist/cjs/src/pro/gate.js +37 -1
  22. package/dist/cjs/src/pro/kucoin.js +819 -178
  23. package/dist/cjs/src/pro/kucoinfutures.js +95 -1261
  24. package/dist/cjs/src/pro/mexc.js +10 -5
  25. package/dist/cjs/src/pro/okx.js +84 -39
  26. package/js/ccxt.d.ts +2 -14
  27. package/js/ccxt.js +2 -10
  28. package/js/src/abstract/kucoin.d.ts +46 -3
  29. package/js/src/abstract/kucoinfutures.d.ts +27 -12
  30. package/js/src/base/Exchange.d.ts +12 -1
  31. package/js/src/base/Exchange.js +36 -3
  32. package/js/src/base/functions/encode.js +2 -2
  33. package/js/src/base/functions/generic.js +9 -3
  34. package/js/src/bitrue.js +1 -1
  35. package/js/src/bitteam.js +1 -1
  36. package/js/src/btcbox.js +1 -1
  37. package/js/src/cex.js +2 -1
  38. package/js/src/gate.d.ts +125 -119
  39. package/js/src/gate.js +227 -168
  40. package/js/src/grvt.js +3 -2
  41. package/js/src/hyperliquid.d.ts +3 -1
  42. package/js/src/hyperliquid.js +16 -5
  43. package/js/src/kraken.js +2 -2
  44. package/js/src/krakenfutures.js +1 -5
  45. package/js/src/kucoin.d.ts +696 -100
  46. package/js/src/kucoin.js +4730 -971
  47. package/js/src/kucoinfutures.d.ts +4 -522
  48. package/js/src/kucoinfutures.js +14 -3434
  49. package/js/src/lbank.js +1 -1
  50. package/js/src/poloniex.js +1 -1
  51. package/js/src/pro/gate.d.ts +30 -1
  52. package/js/src/pro/gate.js +37 -1
  53. package/js/src/pro/kucoin.d.ts +70 -30
  54. package/js/src/pro/kucoin.js +821 -180
  55. package/js/src/pro/kucoinfutures.d.ts +17 -195
  56. package/js/src/pro/kucoinfutures.js +96 -1262
  57. package/js/src/pro/mexc.js +10 -5
  58. package/js/src/pro/okx.d.ts +1 -0
  59. package/js/src/pro/okx.js +84 -39
  60. package/package.json +1 -1
  61. package/dist/cjs/src/abstract/alp.js +0 -11
  62. package/dist/cjs/src/abstract/defx.js +0 -11
  63. package/dist/cjs/src/abstract/timex.js +0 -11
  64. package/dist/cjs/src/alp.js +0 -1059
  65. package/dist/cjs/src/defx.js +0 -2142
  66. package/dist/cjs/src/pro/defx.js +0 -866
  67. package/dist/cjs/src/timex.js +0 -1793
  68. package/js/src/abstract/alp.d.ts +0 -21
  69. package/js/src/abstract/alp.js +0 -5
  70. package/js/src/abstract/defx.d.ts +0 -72
  71. package/js/src/abstract/defx.js +0 -5
  72. package/js/src/abstract/timex.d.ts +0 -65
  73. package/js/src/abstract/timex.js +0 -5
  74. package/js/src/alp.d.ts +0 -209
  75. package/js/src/alp.js +0 -1052
  76. package/js/src/defx.d.ts +0 -348
  77. package/js/src/defx.js +0 -2135
  78. package/js/src/pro/defx.d.ts +0 -236
  79. package/js/src/pro/defx.js +0 -859
  80. package/js/src/timex.d.ts +0 -247
  81. package/js/src/timex.js +0 -1786
@@ -1,7 +1,8 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  import kucoinRest from '../kucoin.js';
3
- import { ExchangeError, ArgumentsRequired } from '../base/errors.js';
4
- import { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById } from '../base/ws/Cache.js';
3
+ import { ArgumentsRequired, ExchangeError } from '../base/errors.js';
4
+ import { Precise } from '../base/Precise.js';
5
+ import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
5
6
  // ---------------------------------------------------------------------------
6
7
  export default class kucoin extends kucoinRest {
7
8
  describe() {
@@ -18,6 +19,8 @@ export default class kucoin extends kucoinRest {
18
19
  'watchBidsAsks': true,
19
20
  'watchOrderBook': true,
20
21
  'watchOrders': true,
22
+ 'watchPosition': true,
23
+ 'watchPositions': false,
21
24
  'watchMyTrades': true,
22
25
  'watchTickers': true,
23
26
  'watchTicker': true,
@@ -35,15 +38,24 @@ export default class kucoin extends kucoinRest {
35
38
  'options': {
36
39
  'tradesLimit': 1000,
37
40
  'watchTicker': {
38
- 'name': 'market/snapshot', // market/ticker
41
+ 'spotMethod': '/market/snapshot', // '/market/ticker'
39
42
  },
40
43
  'watchOrderBook': {
41
44
  'snapshotDelay': 5,
42
45
  'snapshotMaxRetries': 3,
43
- 'method': '/market/level2', // '/spotMarket/level2Depth5' or '/spotMarket/level2Depth50'
46
+ 'spotMethod': '/market/level2',
47
+ 'contractMethod': '/contractMarket/level2', // '/contractMarket/level2Depth5' or '/contractMarket/level2Depth20'
44
48
  },
45
49
  'watchMyTrades': {
46
- 'method': '/spotMarket/tradeOrders', // or '/spot/tradeFills'
50
+ 'spotMethod': '/spotMarket/tradeOrders', // or '/spot/tradeFills'
51
+ },
52
+ 'watchBalance': {
53
+ 'fetchBalanceSnapshot': true,
54
+ 'awaitBalanceSnapshot': true, // whether to wait for the balance snapshot before providing updates
55
+ },
56
+ 'watchPosition': {
57
+ 'fetchPositionSnapshot': true,
58
+ 'awaitPositionSnapshot': true, // whether to wait for the position snapshot before providing updates
47
59
  },
48
60
  },
49
61
  'streaming': {
@@ -54,9 +66,12 @@ export default class kucoin extends kucoinRest {
54
66
  },
55
67
  });
56
68
  }
57
- async negotiate(privateChannel, params = {}) {
58
- const connectId = privateChannel ? 'private' : 'public';
59
- const urls = this.safeValue(this.options, 'urls', {});
69
+ async negotiate(privateChannel, isFuturesMethod = false, params = {}) {
70
+ let connectId = privateChannel ? 'private' : 'public';
71
+ if (isFuturesMethod) {
72
+ connectId += 'Futures';
73
+ }
74
+ const urls = this.safeDict(this.options, 'urls', {});
60
75
  let future = this.safeValue(urls, connectId);
61
76
  if (future !== undefined) {
62
77
  return await future;
@@ -64,16 +79,15 @@ export default class kucoin extends kucoinRest {
64
79
  // we store an awaitable to the url
65
80
  // so that multiple calls don't asynchronously
66
81
  // fetch different urls and overwrite each other
67
- urls[connectId] = this.spawn(this.negotiateHelper, privateChannel, params);
82
+ urls[connectId] = this.spawn(this.negotiateHelper, privateChannel, connectId, params);
68
83
  this.options['urls'] = urls;
69
84
  future = urls[connectId];
70
85
  return await future;
71
86
  }
72
- async negotiateHelper(privateChannel, params = {}) {
87
+ async negotiateHelper(privateChannel, connectId, params = {}) {
73
88
  let response = undefined;
74
- const connectId = privateChannel ? 'private' : 'public';
75
89
  try {
76
- if (privateChannel) {
90
+ if (connectId === 'private') {
77
91
  response = await this.privatePostBulletPrivate(params);
78
92
  //
79
93
  // {
@@ -93,12 +107,18 @@ export default class kucoin extends kucoinRest {
93
107
  // }
94
108
  //
95
109
  }
96
- else {
110
+ else if (connectId === 'public') {
97
111
  response = await this.publicPostBulletPublic(params);
98
112
  }
99
- const data = this.safeValue(response, 'data', {});
100
- const instanceServers = this.safeValue(data, 'instanceServers', []);
101
- const firstInstanceServer = this.safeValue(instanceServers, 0);
113
+ else if (connectId === 'privateFutures') {
114
+ response = await this.futuresPrivatePostBulletPrivate(params);
115
+ }
116
+ else {
117
+ response = await this.futuresPublicPostBulletPublic(params);
118
+ }
119
+ const data = this.safeDict(response, 'data', {});
120
+ const instanceServers = this.safeList(data, 'instanceServers', []);
121
+ const firstInstanceServer = this.safeDict(instanceServers, 0);
102
122
  const pingInterval = this.safeInteger(firstInstanceServer, 'pingInterval');
103
123
  const endpoint = this.safeString(firstInstanceServer, 'endpoint');
104
124
  const token = this.safeString(data, 'token');
@@ -186,7 +206,8 @@ export default class kucoin extends kucoinRest {
186
206
  * @method
187
207
  * @name kucoin#watchTicker
188
208
  * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
189
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/market-snapshot
209
+ * @see https://www.kucoin.com/docs-new/3470063w0
210
+ * @see https://www.kucoin.com/docs-new/3470081w0
190
211
  * @param {string} symbol unified symbol of the market to fetch the ticker for
191
212
  * @param {object} [params] extra parameters specific to the exchange API endpoint
192
213
  * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure}
@@ -195,17 +216,25 @@ export default class kucoin extends kucoinRest {
195
216
  await this.loadMarkets();
196
217
  const market = this.market(symbol);
197
218
  symbol = market['symbol'];
198
- const url = await this.negotiate(false);
199
- const [method, query] = this.handleOptionAndParams(params, 'watchTicker', 'method', '/market/snapshot');
219
+ const isFuturesMethod = market['contract'];
220
+ const url = await this.negotiate(false, isFuturesMethod);
221
+ let method = '/market/snapshot';
222
+ if (isFuturesMethod) {
223
+ method = '/contractMarket/ticker';
224
+ }
225
+ else {
226
+ [method, params] = this.handleOptionAndParams(params, 'watchTicker', 'spotMethod', method);
227
+ }
200
228
  const topic = method + ':' + market['id'];
201
229
  const messageHash = 'ticker:' + symbol;
202
- return await this.subscribe(url, messageHash, topic, query);
230
+ return await this.subscribe(url, messageHash, topic, params);
203
231
  }
204
232
  /**
205
233
  * @method
206
234
  * @name kucoin#unWatchTicker
207
235
  * @description unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
208
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/market-snapshot
236
+ * @see https://www.kucoin.com/docs-new/3470063w0
237
+ * @see https://www.kucoin.com/docs-new/3470081w0
209
238
  * @param {string} symbol unified symbol of the market to fetch the ticker for
210
239
  * @param {object} [params] extra parameters specific to the exchange API endpoint
211
240
  * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure}
@@ -214,16 +243,25 @@ export default class kucoin extends kucoinRest {
214
243
  await this.loadMarkets();
215
244
  const market = this.market(symbol);
216
245
  symbol = market['symbol'];
217
- const url = await this.negotiate(false);
218
- let method = undefined;
219
- [method, params] = this.handleOptionAndParams(params, 'watchTicker', 'method', '/market/snapshot');
246
+ const isFuturesMethod = market['contract'];
247
+ const url = await this.negotiate(false, isFuturesMethod);
248
+ let method = '/market/snapshot';
249
+ if (isFuturesMethod) {
250
+ method = '/contractMarket/ticker';
251
+ }
252
+ else {
253
+ [method, params] = this.handleOptionAndParams(params, 'watchTicker', 'spotMethod', method);
254
+ }
220
255
  const topic = method + ':' + market['id'];
221
256
  const messageHash = 'unsubscribe:ticker:' + symbol;
222
257
  const subMessageHash = 'ticker:' + symbol;
223
258
  const subscription = {
224
- 'messageHashes': [messageHash],
225
- 'subMessageHashes': [subMessageHash],
226
- 'topic': 'trades',
259
+ // we have to add the topic to the messageHashes and subMessageHashes
260
+ // because handleSubscriptionStatus needs them to remove the subscription from the client
261
+ // without them subscription would never be removed and re-subscribe would fail because of duplicate subscriptionHash
262
+ 'messageHashes': [messageHash, topic],
263
+ 'subMessageHashes': [subMessageHash, topic],
264
+ 'topic': 'ticker',
227
265
  'unsubscribe': true,
228
266
  'symbols': [symbol],
229
267
  };
@@ -232,19 +270,33 @@ export default class kucoin extends kucoinRest {
232
270
  /**
233
271
  * @method
234
272
  * @name kucoin#watchTickers
235
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/ticker
273
+ * @see https://www.kucoin.com/docs-new/3470063w0
274
+ * @see https://www.kucoin.com/docs-new/3470064w0
275
+ * @see https://www.kucoin.com/docs-new/3470081w0
236
276
  * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
237
277
  * @param {string[]} symbols unified symbol of the market to fetch the ticker for
238
278
  * @param {object} [params] extra parameters specific to the exchange API endpoint
239
- * @param {string} [params.method] either '/market/snapshot' or '/market/ticker' default is '/market/ticker'
279
+ * @param {string} [params.method] *spot markets only* either '/market/snapshot' or '/market/ticker' default is '/market/ticker'
240
280
  * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure}
241
281
  */
242
282
  async watchTickers(symbols = undefined, params = {}) {
243
283
  await this.loadMarkets();
244
- symbols = this.marketSymbols(symbols);
284
+ symbols = this.marketSymbols(symbols, undefined, true, true);
285
+ const firstMarket = this.getMarketFromSymbols(symbols);
286
+ let marketType = undefined;
287
+ [marketType, params] = this.handleMarketTypeAndParams('watchTickers', firstMarket, params);
288
+ const isFuturesMethod = (marketType !== 'spot') && (marketType !== 'margin');
289
+ if (isFuturesMethod && symbols === undefined) {
290
+ throw new ArgumentsRequired(this.id + ' watchTickers() requires a list of symbols for ' + marketType + ' markets');
291
+ }
245
292
  const messageHash = 'tickers';
246
- let method = undefined;
247
- [method, params] = this.handleOptionAndParams(params, 'watchTickers', 'method', '/market/ticker');
293
+ let method = '/market/ticker';
294
+ if (isFuturesMethod) {
295
+ method = '/contractMarket/ticker';
296
+ }
297
+ else {
298
+ [method, params] = this.handleOptionAndParams2(params, 'watchTickers', 'method', 'spotMethod', method);
299
+ }
248
300
  const messageHashes = [];
249
301
  const topics = [];
250
302
  if (symbols !== undefined) {
@@ -255,7 +307,7 @@ export default class kucoin extends kucoinRest {
255
307
  topics.push(method + ':' + market['id']);
256
308
  }
257
309
  }
258
- const url = await this.negotiate(false);
310
+ const url = await this.negotiate(false, isFuturesMethod);
259
311
  let tickers = undefined;
260
312
  if (symbols === undefined) {
261
313
  const allTopic = method + ':all';
@@ -331,43 +383,106 @@ export default class kucoin extends kucoinRest {
331
383
  // }
332
384
  // }
333
385
  //
386
+ // futures
387
+ // {
388
+ // "subject": "ticker",
389
+ // "topic": "/contractMarket/ticker:XBTUSDM",
390
+ // "data": {
391
+ // "symbol": "XBTUSDM", //Market of the symbol
392
+ // "sequence": 45, //Sequence number which is used to judge the continuity of the pushed messages
393
+ // "side": "sell", //Transaction side of the last traded taker order
394
+ // "price": "3600.0", //Filled price
395
+ // "size": 16, //Filled quantity
396
+ // "tradeId": "5c9dcf4170744d6f5a3d32fb", //Order ID
397
+ // "bestBidSize": 795, //Best bid size
398
+ // "bestBidPrice": "3200.0", //Best bid
399
+ // "bestAskPrice": "3600.0", //Best ask size
400
+ // "bestAskSize": 284, //Best ask
401
+ // "ts": 1553846081210004941 //Filled time - nanosecond
402
+ // }
403
+ // }
404
+ //
334
405
  const topic = this.safeString(message, 'topic');
335
- let market = undefined;
336
- if (topic !== undefined) {
337
- const parts = topic.split(':');
338
- const first = this.safeString(parts, 1);
339
- let marketId = undefined;
340
- if (first === 'all') {
341
- marketId = this.safeString(message, 'subject');
342
- }
343
- else {
344
- marketId = first;
406
+ if (topic.indexOf('contractMarket') < 0) {
407
+ let market = undefined;
408
+ if (topic !== undefined) {
409
+ const parts = topic.split(':');
410
+ const first = this.safeString(parts, 1);
411
+ let marketId = undefined;
412
+ if (first === 'all') {
413
+ marketId = this.safeString(message, 'subject');
414
+ }
415
+ else {
416
+ marketId = first;
417
+ }
418
+ market = this.safeMarket(marketId, market, '-');
345
419
  }
346
- market = this.safeMarket(marketId, market, '-');
420
+ const data = this.safeDict(message, 'data', {});
421
+ const rawTicker = this.safeDict(data, 'data', data);
422
+ const ticker = this.parseTicker(rawTicker, market);
423
+ const symbol = ticker['symbol'];
424
+ this.tickers[symbol] = ticker;
425
+ const messageHash = 'ticker:' + symbol;
426
+ client.resolve(ticker, messageHash);
427
+ // watchTickers
428
+ const allTickers = {};
429
+ allTickers[symbol] = ticker;
430
+ client.resolve(allTickers, 'tickers');
347
431
  }
348
- const data = this.safeValue(message, 'data', {});
349
- const rawTicker = this.safeValue(data, 'data', data);
350
- const ticker = this.parseTicker(rawTicker, market);
351
- const symbol = ticker['symbol'];
352
- this.tickers[symbol] = ticker;
353
- const messageHash = 'ticker:' + symbol;
432
+ else {
433
+ this.handleContractTicker(client, message);
434
+ }
435
+ }
436
+ handleContractTicker(client, message) {
437
+ //
438
+ // ticker (v1)
439
+ //
440
+ // {
441
+ // "subject": "ticker",
442
+ // "topic": "/contractMarket/ticker:XBTUSDM",
443
+ // "data": {
444
+ // "symbol": "XBTUSDM", //Market of the symbol
445
+ // "sequence": 45, //Sequence number which is used to judge the continuity of the pushed messages
446
+ // "side": "sell", //Transaction side of the last traded taker order
447
+ // "price": "3600.0", //Filled price
448
+ // "size": 16, //Filled quantity
449
+ // "tradeId": "5c9dcf4170744d6f5a3d32fb", //Order ID
450
+ // "bestBidSize": 795, //Best bid size
451
+ // "bestBidPrice": "3200.0", //Best bid
452
+ // "bestAskPrice": "3600.0", //Best ask size
453
+ // "bestAskSize": 284, //Best ask
454
+ // "ts": 1553846081210004941 //Filled time - nanosecond
455
+ // }
456
+ // }
457
+ //
458
+ const data = this.safeDict(message, 'data', {});
459
+ const marketId = this.safeString(data, 'symbol');
460
+ const market = this.safeMarket(marketId, undefined, '-');
461
+ const ticker = this.parseTicker(data, market);
462
+ this.tickers[market['symbol']] = ticker;
463
+ const messageHash = 'ticker:' + market['symbol'];
354
464
  client.resolve(ticker, messageHash);
355
- // watchTickers
356
- const allTickers = {};
357
- allTickers[symbol] = ticker;
358
- client.resolve(allTickers, 'tickers');
359
465
  }
360
466
  /**
361
467
  * @method
362
468
  * @name kucoin#watchBidsAsks
363
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level1-bbo-market-data
469
+ * @see https://www.kucoin.com/docs-new/3470067w0
470
+ * @see https://www.kucoin.com/docs-new/3470080w0
364
471
  * @description watches best bid & ask for symbols
365
472
  * @param {string[]} symbols unified symbol of the market to fetch the ticker for
366
473
  * @param {object} [params] extra parameters specific to the exchange API endpoint
367
474
  * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure}
368
475
  */
369
476
  async watchBidsAsks(symbols = undefined, params = {}) {
370
- const ticker = await this.watchMultiHelper('watchBidsAsks', '/spotMarket/level1:', symbols, params);
477
+ await this.loadMarkets();
478
+ symbols = this.marketSymbols(symbols, undefined, false, true, false);
479
+ const firstMarket = this.getMarketFromSymbols(symbols);
480
+ const isFuturesMethod = firstMarket['contract'];
481
+ let channelName = '/spotMarket/level1:';
482
+ if (isFuturesMethod) {
483
+ channelName = '/contractMarket/tickerV2:';
484
+ }
485
+ const ticker = await this.watchMultiHelper('watchBidsAsks', channelName, symbols, params);
371
486
  if (this.newUpdates) {
372
487
  const tickers = {};
373
488
  tickers[ticker['symbol']] = ticker;
@@ -416,6 +531,20 @@ export default class kucoin extends kucoinRest {
416
531
  // subject: 'level1'
417
532
  // }
418
533
  //
534
+ // futures
535
+ // {
536
+ // "subject": "tickerV2",
537
+ // "topic": "/contractMarket/tickerV2:XBTUSDM",
538
+ // "data": {
539
+ // "symbol": "XBTUSDM", //Market of the symbol
540
+ // "bestBidSize": 795, // Best bid size
541
+ // "bestBidPrice": 3200.0, // Best bid
542
+ // "bestAskPrice": 3600.0, // Best ask
543
+ // "bestAskSize": 284, // Best ask size
544
+ // "ts": 1553846081210004941 // Filled time - nanosecond
545
+ // }
546
+ // }
547
+ //
419
548
  const parsedTicker = this.parseWsBidAsk(message);
420
549
  const symbol = parsedTicker['symbol'];
421
550
  this.bidsasks[symbol] = parsedTicker;
@@ -424,30 +553,51 @@ export default class kucoin extends kucoinRest {
424
553
  }
425
554
  parseWsBidAsk(ticker, market = undefined) {
426
555
  const topic = this.safeString(ticker, 'topic');
427
- const parts = topic.split(':');
428
- const marketId = parts[1];
429
- market = this.safeMarket(marketId, market);
430
- const symbol = this.safeString(market, 'symbol');
431
- const data = this.safeDict(ticker, 'data', {});
432
- const ask = this.safeList(data, 'asks', []);
433
- const bid = this.safeList(data, 'bids', []);
434
- const timestamp = this.safeInteger(data, 'timestamp');
435
- return this.safeTicker({
436
- 'symbol': symbol,
437
- 'timestamp': timestamp,
438
- 'datetime': this.iso8601(timestamp),
439
- 'ask': this.safeNumber(ask, 0),
440
- 'askVolume': this.safeNumber(ask, 1),
441
- 'bid': this.safeNumber(bid, 0),
442
- 'bidVolume': this.safeNumber(bid, 1),
443
- 'info': ticker,
444
- }, market);
556
+ if (topic.indexOf('contractMarket') < 0) {
557
+ const parts = topic.split(':');
558
+ const marketId = parts[1];
559
+ market = this.safeMarket(marketId, market);
560
+ const symbol = this.safeString(market, 'symbol');
561
+ const data = this.safeDict(ticker, 'data', {});
562
+ const ask = this.safeList(data, 'asks', []);
563
+ const bid = this.safeList(data, 'bids', []);
564
+ const timestamp = this.safeInteger(data, 'timestamp');
565
+ return this.safeTicker({
566
+ 'symbol': symbol,
567
+ 'timestamp': timestamp,
568
+ 'datetime': this.iso8601(timestamp),
569
+ 'ask': this.safeNumber(ask, 0),
570
+ 'askVolume': this.safeNumber(ask, 1),
571
+ 'bid': this.safeNumber(bid, 0),
572
+ 'bidVolume': this.safeNumber(bid, 1),
573
+ 'info': ticker,
574
+ }, market);
575
+ }
576
+ else {
577
+ // futures
578
+ const data = this.safeDict(ticker, 'data', {});
579
+ const marketId = this.safeString(data, 'symbol');
580
+ market = this.safeMarket(marketId, market);
581
+ const symbol = this.safeString(market, 'symbol');
582
+ const timestamp = this.safeIntegerProduct(data, 'ts', 0.000001);
583
+ return this.safeTicker({
584
+ 'symbol': symbol,
585
+ 'timestamp': timestamp,
586
+ 'datetime': this.iso8601(timestamp),
587
+ 'ask': this.safeNumber(data, 'bestAskPrice'),
588
+ 'askVolume': this.safeNumber(data, 'bestAskSize'),
589
+ 'bid': this.safeNumber(data, 'bestBidPrice'),
590
+ 'bidVolume': this.safeNumber(data, 'bestBidSize'),
591
+ 'info': ticker,
592
+ }, market);
593
+ }
445
594
  }
446
595
  /**
447
596
  * @method
448
597
  * @name kucoin#watchOHLCV
449
598
  * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
450
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/klines
599
+ * @see https://www.kucoin.com/docs-new/3470071w0
600
+ * @see https://www.kucoin.com/docs-new/3470086w0
451
601
  * @param {string} symbol unified symbol of the market to fetch OHLCV data for
452
602
  * @param {string} timeframe the length of time each candle represents
453
603
  * @param {int} [since] timestamp in ms of the earliest candle to fetch
@@ -457,11 +607,16 @@ export default class kucoin extends kucoinRest {
457
607
  */
458
608
  async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
459
609
  await this.loadMarkets();
460
- const url = await this.negotiate(false);
461
610
  const market = this.market(symbol);
462
611
  symbol = market['symbol'];
612
+ const isFuturesMethod = market['contract'];
613
+ const url = await this.negotiate(false, isFuturesMethod);
463
614
  const period = this.safeString(this.timeframes, timeframe, timeframe);
464
- const topic = '/market/candles:' + market['id'] + '_' + period;
615
+ let channelName = '/market/candles:';
616
+ if (isFuturesMethod) {
617
+ channelName = '/contractMarket/limitCandle:';
618
+ }
619
+ const topic = channelName + market['id'] + '_' + period;
465
620
  const messageHash = 'candles:' + symbol + ':' + timeframe;
466
621
  const ohlcv = await this.subscribe(url, messageHash, topic, params);
467
622
  if (this.newUpdates) {
@@ -473,7 +628,8 @@ export default class kucoin extends kucoinRest {
473
628
  * @method
474
629
  * @name kucoin#unWatchOHLCV
475
630
  * @description unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market
476
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/klines
631
+ * @see https://www.kucoin.com/docs-new/3470071w0
632
+ * @see https://www.kucoin.com/docs-new/3470086w0
477
633
  * @param {string} symbol unified symbol of the market to fetch OHLCV data for
478
634
  * @param {string} timeframe the length of time each candle represents
479
635
  * @param {object} [params] extra parameters specific to the exchange API endpoint
@@ -481,19 +637,29 @@ export default class kucoin extends kucoinRest {
481
637
  */
482
638
  async unWatchOHLCV(symbol, timeframe = '1m', params = {}) {
483
639
  await this.loadMarkets();
484
- const url = await this.negotiate(false);
485
640
  const market = this.market(symbol);
486
641
  symbol = market['symbol'];
642
+ const isFuturesMethod = market['contract'];
643
+ const url = await this.negotiate(false, isFuturesMethod);
644
+ let channelName = '/market/candles:';
645
+ if (isFuturesMethod) {
646
+ channelName = '/contractMarket/limitCandle:';
647
+ }
487
648
  const period = this.safeString(this.timeframes, timeframe, timeframe);
488
- const topic = '/market/candles:' + market['id'] + '_' + period;
649
+ const topic = channelName + market['id'] + '_' + period;
489
650
  const messageHash = 'unsubscribe:candles:' + symbol + ':' + timeframe;
490
651
  const subMessageHash = 'candles:' + symbol + ':' + timeframe;
652
+ const symbolAndTimeframe = [symbol, timeframe];
491
653
  const subscription = {
492
- 'messageHashes': [messageHash],
493
- 'subMessageHashes': [subMessageHash],
654
+ // we have to add the topic to the messageHashes and subMessageHashes
655
+ // because handleSubscriptionStatus needs them to remove the subscription from the client
656
+ // without them subscription would never be removed and re-subscribe would fail because of duplicate subscriptionHash
657
+ 'messageHashes': [messageHash, topic],
658
+ 'subMessageHashes': [subMessageHash, topic],
494
659
  'topic': 'ohlcv',
495
660
  'unsubscribe': true,
496
661
  'symbols': [symbol],
662
+ 'symbolsAndTimeframes': [symbolAndTimeframe],
497
663
  };
498
664
  return await this.unSubscribe(url, messageHash, topic, messageHash, params, subscription);
499
665
  }
@@ -518,9 +684,29 @@ export default class kucoin extends kucoinRest {
518
684
  // "type": "message"
519
685
  // }
520
686
  //
521
- const data = this.safeValue(message, 'data', {});
687
+ // futures
688
+ // {
689
+ // "topic":"/contractMarket/limitCandle:LTCUSDTM_1min",
690
+ // "type":"message",
691
+ // "data":{
692
+ // "symbol":"LTCUSDTM",
693
+ // "candles":[
694
+ // "1715470980",
695
+ // "81.38",
696
+ // "81.38",
697
+ // "81.38",
698
+ // "81.38",
699
+ // "61.0", - Note value 5 is incorrect and will be fixed in subsequent versions of kucoin
700
+ // "61"
701
+ // ],
702
+ // "time":1715470994801
703
+ // },
704
+ // "subject":"candle.stick"
705
+ // }
706
+ //
707
+ const data = this.safeDict(message, 'data', {});
522
708
  const marketId = this.safeString(data, 'symbol');
523
- const candles = this.safeValue(data, 'candles', []);
709
+ const candles = this.safeList(data, 'candles', []);
524
710
  const topic = this.safeString(message, 'topic');
525
711
  const parts = topic.split('_');
526
712
  const interval = this.safeString(parts, 1);
@@ -536,15 +722,25 @@ export default class kucoin extends kucoinRest {
536
722
  stored = new ArrayCacheByTimestamp(limit);
537
723
  this.ohlcvs[symbol][timeframe] = stored;
538
724
  }
539
- const ohlcv = this.parseOHLCV(candles, market);
540
- stored.append(ohlcv);
725
+ const isContractMarket = (topic.indexOf('contractMarket') >= 0);
726
+ const baseVolumeIndex = isContractMarket ? 6 : 5; // Note value 5 is incorrect and will be fixed in subsequent versions of kucoin
727
+ const parsed = [
728
+ this.safeTimestamp(candles, 0),
729
+ this.safeNumber(candles, 1),
730
+ this.safeNumber(candles, 3),
731
+ this.safeNumber(candles, 4),
732
+ this.safeNumber(candles, 2),
733
+ this.safeNumber(candles, baseVolumeIndex),
734
+ ];
735
+ stored.append(parsed);
541
736
  client.resolve(stored, messageHash);
542
737
  }
543
738
  /**
544
739
  * @method
545
740
  * @name kucoin#watchTrades
546
741
  * @description get the list of most recent trades for a particular symbol
547
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/match-execution-data
742
+ * @see https://www.kucoin.com/docs-new/3470072w0
743
+ * @see https://www.kucoin.com/docs-new/3470084w0
548
744
  * @param {string} symbol unified symbol of the market to fetch trades for
549
745
  * @param {int} [since] timestamp in ms of the earliest trade to fetch
550
746
  * @param {int} [limit] the maximum amount of trades to fetch
@@ -558,7 +754,8 @@ export default class kucoin extends kucoinRest {
558
754
  * @method
559
755
  * @name kucoin#watchTradesForSymbols
560
756
  * @description get the list of most recent trades for a particular symbol
561
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/match-execution-data
757
+ * @see https://www.kucoin.com/docs-new/3470072w0
758
+ * @see https://www.kucoin.com/docs-new/3470084w0
562
759
  * @param {string[]} symbols
563
760
  * @param {int} [since] timestamp in ms of the earliest trade to fetch
564
761
  * @param {int} [limit] the maximum amount of trades to fetch
@@ -571,17 +768,23 @@ export default class kucoin extends kucoinRest {
571
768
  throw new ArgumentsRequired(this.id + ' watchTradesForSymbols() requires a non-empty array of symbols');
572
769
  }
573
770
  await this.loadMarkets();
574
- symbols = this.marketSymbols(symbols);
771
+ symbols = this.marketSymbols(symbols, undefined, false, true);
772
+ const firstMarket = this.getMarketFromSymbols(symbols);
773
+ const isFuturesMethod = firstMarket['contract'];
575
774
  const marketIds = this.marketIds(symbols);
576
- const url = await this.negotiate(false);
775
+ const url = await this.negotiate(false, isFuturesMethod);
577
776
  const messageHashes = [];
578
777
  const subscriptionHashes = [];
579
- const topic = '/market/match:' + marketIds.join(',');
778
+ let channelName = '/market/match:';
779
+ if (isFuturesMethod) {
780
+ channelName = '/contractMarket/execution:';
781
+ }
782
+ const topic = channelName + marketIds.join(',');
580
783
  for (let i = 0; i < symbols.length; i++) {
581
784
  const symbol = symbols[i];
582
785
  messageHashes.push('trades:' + symbol);
583
786
  const marketId = marketIds[i];
584
- subscriptionHashes.push('/market/match:' + marketId);
787
+ subscriptionHashes.push(channelName + marketId);
585
788
  }
586
789
  const trades = await this.subscribeMultiple(url, messageHashes, topic, subscriptionHashes, params);
587
790
  if (this.newUpdates) {
@@ -595,24 +798,36 @@ export default class kucoin extends kucoinRest {
595
798
  * @method
596
799
  * @name kucoin#unWatchTradesForSymbols
597
800
  * @description unWatches trades stream
598
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/match-execution-data
801
+ * @see https://www.kucoin.com/docs-new/3470072w0
802
+ * @see https://www.kucoin.com/docs-new/3470084w0
599
803
  * @param {string} symbols
600
804
  * @param {object} [params] extra parameters specific to the exchange API endpoint
601
805
  * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=public-trades}
602
806
  */
603
807
  async unWatchTradesForSymbols(symbols, params = {}) {
604
808
  await this.loadMarkets();
605
- symbols = this.marketSymbols(symbols, undefined, false);
809
+ symbols = this.marketSymbols(symbols, undefined, false, true);
606
810
  const marketIds = this.marketIds(symbols);
607
- const url = await this.negotiate(false);
811
+ const firstMarket = this.getMarketFromSymbols(symbols);
812
+ const isFuturesMethod = firstMarket['contract'];
813
+ const url = await this.negotiate(false, isFuturesMethod);
608
814
  const messageHashes = [];
609
815
  const subscriptionHashes = [];
610
- const topic = '/market/match:' + marketIds.join(',');
816
+ let channelName = '/market/match:';
817
+ if (isFuturesMethod) {
818
+ channelName = '/contractMarket/execution:';
819
+ }
820
+ const topic = channelName + marketIds.join(',');
611
821
  for (let i = 0; i < symbols.length; i++) {
612
822
  const symbol = symbols[i];
613
823
  messageHashes.push('unsubscribe:trades:' + symbol);
614
824
  subscriptionHashes.push('trades:' + symbol);
615
825
  }
826
+ // we have to add the topic to the messageHashes and subMessageHashes
827
+ // because handleSubscriptionStatus needs them to remove the subscription from the client
828
+ // without them subscription would never be removed and re-subscribe would fail because of duplicate subscriptionHash
829
+ messageHashes.push(topic);
830
+ subscriptionHashes.push(topic);
616
831
  const subscription = {
617
832
  'messageHashes': messageHashes,
618
833
  'subMessageHashes': subscriptionHashes,
@@ -626,7 +841,8 @@ export default class kucoin extends kucoinRest {
626
841
  * @method
627
842
  * @name kucoin#unWatchTrades
628
843
  * @description unWatches trades stream
629
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/match-execution-data
844
+ * @see https://www.kucoin.com/docs-new/3470072w0
845
+ * @see https://www.kucoin.com/docs-new/3470084w0
630
846
  * @param {string} symbol unified symbol of the market to fetch trades for
631
847
  * @param {object} [params] extra parameters specific to the exchange API endpoint
632
848
  * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=public-trades}
@@ -654,18 +870,20 @@ export default class kucoin extends kucoinRest {
654
870
  // "type": "message"
655
871
  // }
656
872
  //
657
- const data = this.safeValue(message, 'data', {});
658
- const trade = this.parseTrade(data);
873
+ const data = this.safeDict(message, 'data', {});
874
+ const marketId = this.safeString(data, 'symbol');
875
+ const market = this.safeMarket(marketId);
876
+ const trade = this.parseTrade(data, market);
659
877
  const symbol = trade['symbol'];
660
878
  const messageHash = 'trades:' + symbol;
661
- let trades = this.safeValue(this.trades, symbol);
662
- if (trades === undefined) {
879
+ if (!(symbol in this.trades)) {
663
880
  const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
664
- trades = new ArrayCache(limit);
665
- this.trades[symbol] = trades;
881
+ const stored = new ArrayCache(limit);
882
+ this.trades[symbol] = stored;
666
883
  }
667
- trades.append(trade);
668
- client.resolve(trades, messageHash);
884
+ const cache = this.trades[symbol];
885
+ cache.append(trade);
886
+ client.resolve(cache, messageHash);
669
887
  }
670
888
  /**
671
889
  * @method
@@ -718,15 +936,16 @@ export default class kucoin extends kucoinRest {
718
936
  /**
719
937
  * @method
720
938
  * @name kucoin#watchOrderBookForSymbols
721
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level1-bbo-market-data
722
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level2-market-data
723
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level2-5-best-ask-bid-orders
724
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level2-50-best-ask-bid-orders
939
+ * @see https://www.kucoin.com/docs-new/3470069w0 // spot level 5
940
+ * @see https://www.kucoin.com/docs-new/3470070w0 // spot level 50
941
+ * @see https://www.kucoin.com/docs-new/3470068w0 // spot incremental
942
+ * @see https://www.kucoin.com/docs-new/3470083w0 // futures level 5
943
+ * @see https://www.kucoin.com/docs-new/3470097w0 // futures level 50
944
+ * @see https://www.kucoin.com/docs-new/3470082w0 // futures incremental
725
945
  * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
726
946
  * @param {string[]} symbols unified array of symbols
727
947
  * @param {int} [limit] the maximum amount of order book entries to return
728
948
  * @param {object} [params] extra parameters specific to the exchange API endpoint
729
- * @param {string} [params.method] either '/market/level2' or '/spotMarket/level2Depth5' or '/spotMarket/level2Depth50' default is '/market/level2'
730
949
  * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/?id=order-book-structure} indexed by market symbols
731
950
  */
732
951
  async watchOrderBookForSymbols(symbols, limit = undefined, params = {}) {
@@ -742,11 +961,19 @@ export default class kucoin extends kucoinRest {
742
961
  await this.loadMarkets();
743
962
  symbols = this.marketSymbols(symbols);
744
963
  const marketIds = this.marketIds(symbols);
745
- const url = await this.negotiate(false);
746
- let method = undefined;
747
- [method, params] = this.handleOptionAndParams(params, 'watchOrderBook', 'method', '/market/level2');
748
- if ((limit === 5) || (limit === 50)) {
749
- method = '/spotMarket/level2Depth' + limit.toString();
964
+ const firstMarket = this.getMarketFromSymbols(symbols);
965
+ const isFuturesMethod = firstMarket['contract'];
966
+ const url = await this.negotiate(false, isFuturesMethod);
967
+ let method = isFuturesMethod ? '/contractMarket/level2' : '/market/level2';
968
+ const optionName = isFuturesMethod ? 'contractMethod' : 'spotMethod';
969
+ [method, params] = this.handleOptionAndParams2(params, 'watchOrderBook', optionName, 'method', method);
970
+ if (method.indexOf('Depth') === -1) {
971
+ if ((limit === 5) || (limit === 50)) {
972
+ if (!isFuturesMethod) {
973
+ method = '/spotMarket/level2';
974
+ }
975
+ method += 'Depth' + limit.toString();
976
+ }
750
977
  }
751
978
  const topic = method + ':' + marketIds.join(',');
752
979
  const messageHashes = [];
@@ -758,7 +985,7 @@ export default class kucoin extends kucoinRest {
758
985
  subscriptionHashes.push(method + ':' + marketId);
759
986
  }
760
987
  let subscription = {};
761
- if (method === '/market/level2') { // other streams return the entire orderbook, so we don't need to fetch the snapshot through REST
988
+ if ((method === '/market/level2') || (method === '/contractMarket/level2')) { // other streams return the entire orderbook, so we don't need to fetch the snapshot through REST
762
989
  subscription = {
763
990
  'method': this.handleOrderBookSubscription,
764
991
  'symbols': symbols,
@@ -771,27 +998,37 @@ export default class kucoin extends kucoinRest {
771
998
  /**
772
999
  * @method
773
1000
  * @name kucoin#unWatchOrderBookForSymbols
774
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level1-bbo-market-data
775
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level2-market-data
776
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level2-5-best-ask-bid-orders
777
- * @see https://www.kucoin.com/docs/websocket/spot-trading/public-channels/level2-50-best-ask-bid-orders
1001
+ * @see https://www.kucoin.com/docs-new/3470069w0 // spot level 5
1002
+ * @see https://www.kucoin.com/docs-new/3470070w0 // spot level 50
1003
+ * @see https://www.kucoin.com/docs-new/3470068w0 // spot incremental
1004
+ * @see https://www.kucoin.com/docs-new/3470083w0 // futures level 5
1005
+ * @see https://www.kucoin.com/docs-new/3470097w0 // futures level 50
1006
+ * @see https://www.kucoin.com/docs-new/3470082w0 // futures incremental
778
1007
  * @description unWatches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
779
1008
  * @param {string[]} symbols unified array of symbols
780
1009
  * @param {object} [params] extra parameters specific to the exchange API endpoint
781
- * @param {string} [params.method] either '/market/level2' or '/spotMarket/level2Depth5' or '/spotMarket/level2Depth50' default is '/market/level2'
1010
+ * @param {string} [params.method] either '/market/level2' or '/spotMarket/level2Depth5' or '/spotMarket/level2Depth50' or '/contractMarket/level2' or '/contractMarket/level2Depth5' or '/contractMarket/level2Depth50' default is '/market/level2' for spot and '/contractMarket/level2' for futures
782
1011
  * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/?id=order-book-structure} indexed by market symbols
783
1012
  */
784
1013
  async unWatchOrderBookForSymbols(symbols, params = {}) {
785
1014
  const limit = this.safeInteger(params, 'limit');
786
1015
  params = this.omit(params, 'limit');
787
1016
  await this.loadMarkets();
788
- symbols = this.marketSymbols(symbols, undefined, false);
1017
+ symbols = this.marketSymbols(symbols, undefined, false, true);
789
1018
  const marketIds = this.marketIds(symbols);
790
- const url = await this.negotiate(false);
791
- let method = undefined;
792
- [method, params] = this.handleOptionAndParams(params, 'watchOrderBook', 'method', '/market/level2');
793
- if ((limit === 5) || (limit === 50)) {
794
- method = '/spotMarket/level2Depth' + limit.toString();
1019
+ const firstMarket = this.getMarketFromSymbols(symbols);
1020
+ const isFuturesMethod = firstMarket['contract'];
1021
+ const url = await this.negotiate(false, isFuturesMethod);
1022
+ let method = isFuturesMethod ? '/contractMarket/level2' : '/market/level2';
1023
+ const optionName = isFuturesMethod ? 'contractMethod' : 'spotMethod';
1024
+ [method, params] = this.handleOptionAndParams2(params, 'watchOrderBook', optionName, 'method', method);
1025
+ if (method.indexOf('Depth') === -1) {
1026
+ if ((limit === 5) || (limit === 50)) {
1027
+ if (!isFuturesMethod) {
1028
+ method = '/spotMarket/level2';
1029
+ }
1030
+ method += 'Depth' + limit.toString();
1031
+ }
795
1032
  }
796
1033
  const topic = method + ':' + marketIds.join(',');
797
1034
  const messageHashes = [];
@@ -801,6 +1038,11 @@ export default class kucoin extends kucoinRest {
801
1038
  messageHashes.push('unsubscribe:orderbook:' + symbol);
802
1039
  subscriptionHashes.push('orderbook:' + symbol);
803
1040
  }
1041
+ // we have to add the topic to the messageHashes and subMessageHashes
1042
+ // because handleSubscriptionStatus needs them to remove the subscription from the client
1043
+ // without them subscription would never be removed and re-subscribe would fail because of duplicate subscriptionHash
1044
+ messageHashes.push(topic);
1045
+ subscriptionHashes.push(topic);
804
1046
  const subscription = {
805
1047
  'messageHashes': messageHashes,
806
1048
  'symbols': symbols,
@@ -851,8 +1093,7 @@ export default class kucoin extends kucoinRest {
851
1093
  // "subject": "level2"
852
1094
  // }
853
1095
  //
854
- const data = this.safeValue(message, 'data');
855
- const subject = this.safeString(message, 'subject');
1096
+ const data = this.safeDict(message, 'data');
856
1097
  const topic = this.safeString(message, 'topic');
857
1098
  const topicParts = topic.split(':');
858
1099
  const topicSymbol = this.safeString(topicParts, 1);
@@ -861,7 +1102,7 @@ export default class kucoin extends kucoinRest {
861
1102
  const symbol = this.safeSymbol(marketId, undefined, '-');
862
1103
  const messageHash = 'orderbook:' + symbol;
863
1104
  // let orderbook = this.safeDict (this.orderbooks, symbol);
864
- if (subject === 'level2') {
1105
+ if (topic.indexOf('Depth') >= 0) {
865
1106
  if (!(symbol in this.orderbooks)) {
866
1107
  this.orderbooks[symbol] = this.orderBook();
867
1108
  }
@@ -907,14 +1148,14 @@ export default class kucoin extends kucoinRest {
907
1148
  getCacheIndex(orderbook, cache) {
908
1149
  const firstDelta = this.safeValue(cache, 0);
909
1150
  const nonce = this.safeInteger(orderbook, 'nonce');
910
- const firstDeltaStart = this.safeInteger(firstDelta, 'sequenceStart');
1151
+ const firstDeltaStart = this.safeInteger2(firstDelta, 'sequenceStart', 'sequence');
911
1152
  if (nonce < firstDeltaStart - 1) {
912
1153
  return -1;
913
1154
  }
914
1155
  for (let i = 0; i < cache.length; i++) {
915
1156
  const delta = cache[i];
916
- const deltaStart = this.safeInteger(delta, 'sequenceStart');
917
- const deltaEnd = this.safeInteger(delta, 'sequenceEnd');
1157
+ const deltaStart = this.safeInteger2(delta, 'sequenceStart', 'sequence');
1158
+ const deltaEnd = this.safeInteger2(delta, 'sequenceEnd', 'timestamp'); // todo check
918
1159
  if ((nonce >= deltaStart - 1) && (nonce < deltaEnd)) {
919
1160
  return i;
920
1161
  }
@@ -923,16 +1164,34 @@ export default class kucoin extends kucoinRest {
923
1164
  }
924
1165
  handleDelta(orderbook, delta) {
925
1166
  const timestamp = this.safeInteger2(delta, 'time', 'timestamp');
926
- orderbook['nonce'] = this.safeInteger(delta, 'sequenceEnd', timestamp);
1167
+ orderbook['nonce'] = this.safeInteger2(delta, 'sequenceEnd', 'sequence', timestamp);
927
1168
  orderbook['timestamp'] = timestamp;
928
1169
  orderbook['datetime'] = this.iso8601(timestamp);
929
- const changes = this.safeValue(delta, 'changes', delta);
930
- const bids = this.safeValue(changes, 'bids', []);
931
- const asks = this.safeValue(changes, 'asks', []);
1170
+ const change = this.safeString(delta, 'change');
932
1171
  const storedBids = orderbook['bids'];
933
1172
  const storedAsks = orderbook['asks'];
934
- this.handleBidAsks(storedBids, bids);
935
- this.handleBidAsks(storedAsks, asks);
1173
+ if (change !== undefined) {
1174
+ // handling futures orderbook update
1175
+ const splitChange = change.split(',');
1176
+ const price = this.safeNumber(splitChange, 0);
1177
+ const side = this.safeString(splitChange, 1);
1178
+ const quantity = this.safeNumber(splitChange, 2);
1179
+ const type = (side === 'buy') ? 'bids' : 'asks';
1180
+ const value = [price, quantity];
1181
+ if (type === 'bids') {
1182
+ storedBids.storeArray(value);
1183
+ }
1184
+ else {
1185
+ storedAsks.storeArray(value);
1186
+ }
1187
+ }
1188
+ else {
1189
+ const changes = this.safeDict(delta, 'changes', delta);
1190
+ const bids = this.safeList(changes, 'bids', []);
1191
+ const asks = this.safeList(changes, 'asks', []);
1192
+ this.handleBidAsks(storedBids, bids);
1193
+ this.handleBidAsks(storedAsks, asks);
1194
+ }
936
1195
  }
937
1196
  handleBidAsks(bookSide, bidAsks) {
938
1197
  for (let i = 0; i < bidAsks.length; i++) {
@@ -942,7 +1201,7 @@ export default class kucoin extends kucoinRest {
942
1201
  }
943
1202
  handleOrderBookSubscription(client, message, subscription) {
944
1203
  const limit = this.safeInteger(subscription, 'limit');
945
- const symbols = this.safeValue(subscription, 'symbols');
1204
+ const symbols = this.safeList(subscription, 'symbols');
946
1205
  if (symbols === undefined) {
947
1206
  const symbol = this.safeString(subscription, 'symbol');
948
1207
  this.orderbooks[symbol] = this.orderBook({}, limit);
@@ -1005,36 +1264,63 @@ export default class kucoin extends kucoinRest {
1005
1264
  * @method
1006
1265
  * @name kucoin#watchOrders
1007
1266
  * @description watches information on multiple orders made by the user
1008
- * @see https://www.kucoin.com/docs/websocket/spot-trading/private-channels/private-order-change
1009
- * @see https://www.kucoin.com/docs/websocket/spot-trading/private-channels/stop-order-event
1267
+ * @see https://www.kucoin.com/docs-new/3470074w0 // spot regular orders
1268
+ * @see https://www.kucoin.com/docs-new/3470139w0 // spot trigger orders
1269
+ * @see https://www.kucoin.com/docs-new/3470090w0 // contract regular orders
1270
+ * @see https://www.kucoin.com/docs-new/3470091w0 // contract trigger orders
1010
1271
  * @param {string} symbol unified market symbol of the market orders were made in
1011
1272
  * @param {int} [since] the earliest time in ms to fetch orders for
1012
1273
  * @param {int} [limit] the maximum number of order structures to retrieve
1013
1274
  * @param {object} [params] extra parameters specific to the exchange API endpoint
1014
1275
  * @param {boolean} [params.trigger] trigger orders are watched if true
1276
+ * @param {string} [params.type] 'spot' or 'swap' (default is 'spot' if symbol is not provided)
1015
1277
  * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/?id=order-structure}
1016
1278
  */
1017
1279
  async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
1018
1280
  await this.loadMarkets();
1019
- const trigger = this.safeValue2(params, 'stop', 'trigger');
1281
+ const trigger = this.safeBool2(params, 'stop', 'trigger');
1020
1282
  params = this.omit(params, ['stop', 'trigger']);
1021
- const url = await this.negotiate(true);
1022
- const topic = trigger ? '/spotMarket/advancedOrders' : '/spotMarket/tradeOrders';
1023
- const request = {
1024
- 'privateChannel': true,
1025
- };
1283
+ let market = undefined;
1026
1284
  let messageHash = 'orders';
1027
1285
  if (symbol !== undefined) {
1028
- const market = this.market(symbol);
1286
+ market = this.market(symbol);
1029
1287
  symbol = market['symbol'];
1030
1288
  messageHash = messageHash + ':' + symbol;
1031
1289
  }
1290
+ let marketType = undefined;
1291
+ [marketType, params] = this.handleMarketTypeAndParams('watchOrders', market, params);
1292
+ const isFuturesMethod = ((marketType !== 'spot') && (marketType !== 'margin'));
1293
+ const url = await this.negotiate(true, isFuturesMethod);
1294
+ let topic = trigger ? '/spotMarket/advancedOrders' : '/spotMarket/tradeOrders';
1295
+ if (isFuturesMethod) {
1296
+ topic = trigger ? '/contractMarket/advancedOrders' : '/contractMarket/tradeOrders';
1297
+ }
1298
+ if (symbol === undefined) {
1299
+ const suffix = this.getOrdersMessageHashSuffix(topic);
1300
+ messageHash += suffix;
1301
+ }
1302
+ const request = {
1303
+ 'privateChannel': true,
1304
+ };
1032
1305
  const orders = await this.subscribe(url, messageHash, topic, this.extend(request, params));
1033
1306
  if (this.newUpdates) {
1034
1307
  limit = orders.getLimit(symbol, limit);
1035
1308
  }
1036
1309
  return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
1037
1310
  }
1311
+ getOrdersMessageHashSuffix(topic) {
1312
+ let suffix = '-spot';
1313
+ if (topic === '/spotMarket/advancedOrders') {
1314
+ suffix += '-trigger';
1315
+ }
1316
+ else if (topic === '/contractMarket/tradeOrders') {
1317
+ suffix = '-contract';
1318
+ }
1319
+ else if (topic === '/contractMarket/advancedOrders') {
1320
+ suffix = '-contract-trigger';
1321
+ }
1322
+ return suffix;
1323
+ }
1038
1324
  parseWsOrderStatus(status) {
1039
1325
  const statuses = {
1040
1326
  'open': 'open',
@@ -1085,13 +1371,42 @@ export default class kucoin extends kucoinRest {
1085
1371
  // "type": "triggered"
1086
1372
  // }
1087
1373
  //
1374
+ // futures
1375
+ // {
1376
+ // "symbol": "ETHUSDTM",
1377
+ // "orderType": "market",
1378
+ // "side": "buy",
1379
+ // "canceledSize": "0",
1380
+ // "orderId": "416204113500479490",
1381
+ // "positionSide": "LONG",
1382
+ // "liquidity": "taker",
1383
+ // "marginMode": "ISOLATED",
1384
+ // "type": "match",
1385
+ // "feeType": "takerFee",
1386
+ // "orderTime": "1772043995356345762",
1387
+ // "size": "1",
1388
+ // "filledSize": "1",
1389
+ // "price": "0",
1390
+ // "matchPrice": "2068.55",
1391
+ // "matchSize": "1",
1392
+ // "remainSize": "0",
1393
+ // "tradeId": "1815302608109",
1394
+ // "clientOid": "9f7a2be0-effe-45bd-bdc8-1614715a583a",
1395
+ // "tradeType": "trade",
1396
+ // "status": "match",
1397
+ // "ts": 1772043995362000000
1398
+ // }
1399
+ //
1088
1400
  const rawType = this.safeString(order, 'type');
1089
1401
  let status = this.parseWsOrderStatus(rawType);
1090
- const timestamp = this.safeInteger2(order, 'orderTime', 'createdAt');
1402
+ let timestamp = this.safeInteger2(order, 'orderTime', 'createdAt');
1091
1403
  const marketId = this.safeString(order, 'symbol');
1092
1404
  market = this.safeMarket(marketId, market);
1405
+ if (market['contract']) {
1406
+ timestamp = this.safeIntegerProduct(order, 'orderTime', 0.000001);
1407
+ }
1093
1408
  const triggerPrice = this.safeString(order, 'stopPrice');
1094
- const triggerSuccess = this.safeValue(order, 'triggerSuccess');
1409
+ const triggerSuccess = this.safeBool(order, 'triggerSuccess');
1095
1410
  const triggerFail = (triggerSuccess !== true) && (triggerSuccess !== undefined); // TODO: updated to triggerSuccess === False once transpiler transpiles it correctly
1096
1411
  if ((status === 'triggered') && triggerFail) {
1097
1412
  status = 'canceled';
@@ -1142,8 +1457,7 @@ export default class kucoin extends kucoinRest {
1142
1457
  // "type": "open"
1143
1458
  // }
1144
1459
  //
1145
- const messageHash = 'orders';
1146
- const data = this.safeValue(message, 'data');
1460
+ const data = this.safeDict(message, 'data');
1147
1461
  const tradeId = this.safeString(data, 'tradeId');
1148
1462
  if (tradeId !== undefined) {
1149
1463
  this.handleMyTrade(client, message);
@@ -1151,7 +1465,7 @@ export default class kucoin extends kucoinRest {
1151
1465
  const parsed = this.parseWsOrder(data);
1152
1466
  const symbol = this.safeString(parsed, 'symbol');
1153
1467
  const orderId = this.safeString(parsed, 'id');
1154
- const triggerPrice = this.safeValue(parsed, 'triggerPrice');
1468
+ const triggerPrice = this.safeString(parsed, 'triggerPrice');
1155
1469
  const isTriggerOrder = (triggerPrice !== undefined);
1156
1470
  if (this.orders === undefined) {
1157
1471
  const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
@@ -1168,42 +1482,63 @@ export default class kucoin extends kucoinRest {
1168
1482
  }
1169
1483
  }
1170
1484
  cachedOrders.append(parsed);
1171
- client.resolve(cachedOrders, messageHash);
1485
+ const messageHash = 'orders';
1486
+ const topic = this.safeString(message, 'topic');
1487
+ const suffix = this.getOrdersMessageHashSuffix(topic);
1488
+ const typeSpecificMessageHash = messageHash + suffix;
1489
+ client.resolve(cachedOrders, typeSpecificMessageHash);
1172
1490
  const symbolSpecificMessageHash = messageHash + ':' + symbol;
1173
1491
  client.resolve(cachedOrders, symbolSpecificMessageHash);
1174
1492
  }
1175
1493
  /**
1176
1494
  * @method
1177
1495
  * @name kucoin#watchMyTrades
1178
- * @description watches information on multiple trades made by the user
1179
- * @see https://www.kucoin.com/docs/websocket/spot-trading/private-channels/private-order-change
1496
+ * @description watches information on multiple trades made by the user on spot
1497
+ * @see https://www.kucoin.com/docs-new/3470074w0
1498
+ * @see https://www.kucoin.com/docs-new/3470090w0
1180
1499
  * @param {string} symbol unified market symbol of the market trades were made in
1181
1500
  * @param {int} [since] the earliest time in ms to fetch trades for
1182
1501
  * @param {int} [limit] the maximum number of trade structures to retrieve
1183
1502
  * @param {object} [params] extra parameters specific to the exchange API endpoint
1184
- * @param {string} [params.method] '/spotMarket/tradeOrders' or '/spot/tradeFills' default is '/spotMarket/tradeOrders'
1503
+ * @param {string} [params.method] '/spotMarket/tradeOrders' or '/spot/tradeFills' or '/contractMarket/tradeOrders', default is '/spotMarket/tradeOrders'
1185
1504
  * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=trade-structure}
1186
1505
  */
1187
1506
  async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
1188
1507
  await this.loadMarkets();
1189
- const url = await this.negotiate(true);
1190
- let topic = undefined;
1191
- [topic, params] = this.handleOptionAndParams(params, 'watchMyTrades', 'method', '/spotMarket/tradeOrders');
1192
- const request = {
1193
- 'privateChannel': true,
1194
- };
1195
1508
  let messageHash = 'myTrades';
1509
+ let market = undefined;
1196
1510
  if (symbol !== undefined) {
1197
- const market = this.market(symbol);
1511
+ market = this.market(symbol);
1198
1512
  symbol = market['symbol'];
1199
1513
  messageHash = messageHash + ':' + market['symbol'];
1200
1514
  }
1515
+ let marketType = undefined;
1516
+ [marketType, params] = this.handleMarketTypeAndParams('watchMyTrades', market, params);
1517
+ const isFuturesMethod = ((marketType !== 'spot') && (marketType !== 'margin'));
1518
+ const url = await this.negotiate(true, isFuturesMethod);
1519
+ let topic = isFuturesMethod ? '/contractMarket/tradeOrders' : '/spotMarket/tradeOrders';
1520
+ const optionName = isFuturesMethod ? 'contractMethod' : 'spotMethod';
1521
+ [topic, params] = this.handleOptionAndParams2(params, 'watchMyTrades', optionName, 'method', topic);
1522
+ const request = {
1523
+ 'privateChannel': true,
1524
+ };
1525
+ if (symbol === undefined) {
1526
+ const suffix = this.getMyTradesMessageHashSuffix(topic);
1527
+ messageHash += suffix;
1528
+ }
1201
1529
  const trades = await this.subscribe(url, messageHash, topic, this.extend(request, params));
1202
1530
  if (this.newUpdates) {
1203
1531
  limit = trades.getLimit(symbol, limit);
1204
1532
  }
1205
1533
  return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true);
1206
1534
  }
1535
+ getMyTradesMessageHashSuffix(topic) {
1536
+ let suffix = '-spot';
1537
+ if (topic.indexOf('contractMarket') >= 0) {
1538
+ suffix = '-contract';
1539
+ }
1540
+ return suffix;
1541
+ }
1207
1542
  handleMyTrade(client, message) {
1208
1543
  //
1209
1544
  // {
@@ -1242,7 +1577,10 @@ export default class kucoin extends kucoinRest {
1242
1577
  const myTrades = this.myTrades;
1243
1578
  myTrades.append(parsed);
1244
1579
  const messageHash = 'myTrades';
1245
- client.resolve(this.myTrades, messageHash);
1580
+ const topic = this.safeString(message, 'topic');
1581
+ const suffix = this.getMyTradesMessageHashSuffix(topic);
1582
+ const typeSpecificMessageHash = messageHash + suffix;
1583
+ client.resolve(this.myTrades, typeSpecificMessageHash);
1246
1584
  const symbolSpecificMessageHash = messageHash + ':' + parsed['symbol'];
1247
1585
  client.resolve(this.myTrades, symbolSpecificMessageHash);
1248
1586
  }
@@ -1329,19 +1667,74 @@ export default class kucoin extends kucoinRest {
1329
1667
  * @method
1330
1668
  * @name kucoin#watchBalance
1331
1669
  * @description watch balance and get the amount of funds available for trading or funds locked in orders
1332
- * @see https://www.kucoin.com/docs/websocket/spot-trading/private-channels/account-balance-change
1670
+ * @see https://www.kucoin.com/docs-new/3470075w0 // spot balance
1671
+ * @see https://www.kucoin.com/docs-new/3470092w0 // contract balance
1333
1672
  * @param {object} [params] extra parameters specific to the exchange API endpoint
1673
+ * @param {string} [params.type] 'spot' or 'swap' (default is 'spot')
1334
1674
  * @returns {object} a [balance structure]{@link https://docs.ccxt.com/?id=balance-structure}
1335
1675
  */
1336
1676
  async watchBalance(params = {}) {
1337
1677
  await this.loadMarkets();
1338
- const url = await this.negotiate(true);
1339
- const topic = '/account/balance';
1678
+ const defaultType = this.safeString(this.options, 'defaultType', 'spot');
1679
+ const type = this.safeString(params, 'type', defaultType);
1680
+ params = this.omit(params, 'type');
1681
+ const accountsByType = this.safeDict(this.options, 'accountsByType', {});
1682
+ const uniformType = this.safeString(accountsByType, type, type);
1683
+ const isFuturesMethod = (uniformType === 'contract');
1684
+ const url = await this.negotiate(true, isFuturesMethod);
1685
+ const client = this.client(url);
1686
+ this.setBalanceCache(client, uniformType);
1687
+ const options = this.safeDict(this.options, 'watchBalance');
1688
+ const fetchBalanceSnapshot = this.safeBool(options, 'fetchBalanceSnapshot', false);
1689
+ const awaitBalanceSnapshot = this.safeBool(options, 'awaitBalanceSnapshot', true);
1690
+ if (fetchBalanceSnapshot && awaitBalanceSnapshot) {
1691
+ await client.future(uniformType + ':fetchBalanceSnapshot');
1692
+ }
1693
+ const messageHash = uniformType + ':balance';
1694
+ const requestId = this.requestId().toString();
1695
+ const subscriptionHash = isFuturesMethod ? '/contractAccount/wallet' : '/account/balance';
1340
1696
  const request = {
1697
+ 'id': requestId,
1698
+ 'type': 'subscribe',
1699
+ 'topic': subscriptionHash,
1700
+ 'response': true,
1341
1701
  'privateChannel': true,
1342
1702
  };
1343
- const messageHash = 'balance';
1344
- return await this.subscribe(url, messageHash, topic, this.extend(request, params));
1703
+ const message = this.extend(request, params);
1704
+ if (!(subscriptionHash in client.subscriptions)) {
1705
+ client.subscriptions[requestId] = subscriptionHash;
1706
+ }
1707
+ return await this.watch(url, messageHash, message, type);
1708
+ }
1709
+ setBalanceCache(client, type) {
1710
+ if ((type in client.subscriptions) && (type in this.balance)) {
1711
+ return;
1712
+ }
1713
+ const options = this.safeDict(this.options, 'watchBalance');
1714
+ const fetchBalanceSnapshot = this.safeBool(options, 'fetchBalanceSnapshot', false);
1715
+ if (fetchBalanceSnapshot) {
1716
+ const messageHash = type + ':fetchBalanceSnapshot';
1717
+ if (!(messageHash in client.futures)) {
1718
+ client.future(messageHash);
1719
+ this.spawn(this.loadBalanceSnapshot, client, messageHash, type);
1720
+ }
1721
+ }
1722
+ else {
1723
+ this.balance[type] = {};
1724
+ }
1725
+ }
1726
+ async loadBalanceSnapshot(client, messageHash, type) {
1727
+ const params = {
1728
+ 'type': type,
1729
+ };
1730
+ const response = await this.fetchBalance(params);
1731
+ this.balance[type] = this.extend(response, this.safeValue(this.balance, type, {}));
1732
+ // don't remove the future from the .futures cache
1733
+ if (messageHash in client.futures) {
1734
+ const future = client.futures[messageHash];
1735
+ future.resolve();
1736
+ client.resolve(this.balance[type], type + ':balance');
1737
+ }
1345
1738
  }
1346
1739
  handleBalance(client, message) {
1347
1740
  //
@@ -1367,8 +1760,51 @@ export default class kucoin extends kucoinRest {
1367
1760
  // "total":"89"
1368
1761
  // }
1369
1762
  //
1370
- const data = this.safeValue(message, 'data', {});
1371
- const messageHash = 'balance';
1763
+ // futures
1764
+ // {
1765
+ // "id": "6375553193027a0001f6566f",
1766
+ // "type": "message",
1767
+ // "topic": "/contractAccount/wallet",
1768
+ // "userId": "613a896885d8660006151f01",
1769
+ // "channelType": "private",
1770
+ // "subject": "availableBalance.change",
1771
+ // "data": {
1772
+ // "currency": "USDT",
1773
+ // "holdBalance": "0.0000000000",
1774
+ // "availableBalance": "14.0350281903",
1775
+ // "timestamp": "1668633905657"
1776
+ // }
1777
+ // }
1778
+ //
1779
+ // {
1780
+ // "topic": "/contractAccount/wallet",
1781
+ // "type": "message",
1782
+ // "subject": "walletBalance.change",
1783
+ // "id": "699f586d4416a80001df3804",
1784
+ // "userId": "64f99aced178640001306e6e",
1785
+ // "channelType": "private",
1786
+ // "data": {
1787
+ // "crossPosMargin": "0",
1788
+ // "isolatedOrderMargin": "0",
1789
+ // "holdBalance": "0",
1790
+ // "equity": "49.50050236",
1791
+ // "version": "2874",
1792
+ // "availableBalance": "28.67180236",
1793
+ // "isolatedPosMargin": "20.7308",
1794
+ // "maxWithdrawAmount": "28.67180236",
1795
+ // "walletBalance": "49.40260236",
1796
+ // "isolatedFundingFeeMargin": "0",
1797
+ // "crossUnPnl": "0",
1798
+ // "totalCrossMargin": "28.67180236",
1799
+ // "currency": "USDT",
1800
+ // "isolatedUnPnl": "0.0979",
1801
+ // "availableMargin": "28.67180236",
1802
+ // "crossOrderMargin": "0",
1803
+ // "timestamp": "1772050541214"
1804
+ // }
1805
+ // }
1806
+ //
1807
+ const data = this.safeDict(message, 'data', {});
1372
1808
  const currencyId = this.safeString(data, 'currency');
1373
1809
  const relationEvent = this.safeString(data, 'relationEvent');
1374
1810
  let requestAccountType = undefined;
@@ -1376,27 +1812,210 @@ export default class kucoin extends kucoinRest {
1376
1812
  const relationEventParts = relationEvent.split('.');
1377
1813
  requestAccountType = this.safeString(relationEventParts, 0);
1378
1814
  }
1379
- const selectedType = this.safeString2(this.options, 'watchBalance', 'defaultType', 'trade'); // trade, main, margin or other
1380
- const accountsByType = this.safeValue(this.options, 'accountsByType');
1815
+ const topic = this.safeString(message, 'topic');
1816
+ if (topic === '/contractAccount/wallet') {
1817
+ requestAccountType = 'contract';
1818
+ }
1819
+ const accountsByType = this.safeDict(this.options, 'accountsByType');
1381
1820
  const uniformType = this.safeString(accountsByType, requestAccountType, 'trade');
1382
1821
  if (!(uniformType in this.balance)) {
1383
1822
  this.balance[uniformType] = {};
1384
1823
  }
1385
1824
  this.balance[uniformType]['info'] = data;
1386
- const timestamp = this.safeInteger(data, 'time');
1825
+ const timestamp = this.safeInteger2(data, 'time', 'timestamp');
1387
1826
  this.balance[uniformType]['timestamp'] = timestamp;
1388
1827
  this.balance[uniformType]['datetime'] = this.iso8601(timestamp);
1389
1828
  const code = this.safeCurrencyCode(currencyId);
1390
1829
  const account = this.account();
1391
- account['free'] = this.safeString(data, 'available');
1392
- account['used'] = this.safeString(data, 'hold');
1830
+ let used = this.safeString2(data, 'hold', 'holdBalance');
1831
+ const isolatedPosMargin = this.omitZero(this.safeString(data, 'isolatedPosMargin'));
1832
+ if (isolatedPosMargin !== undefined) {
1833
+ used = Precise.stringAdd(used, isolatedPosMargin);
1834
+ }
1835
+ account['free'] = this.safeString2(data, 'available', 'availableBalance');
1836
+ account['used'] = used;
1393
1837
  account['total'] = this.safeString(data, 'total');
1394
1838
  this.balance[uniformType][code] = account;
1395
1839
  this.balance[uniformType] = this.safeBalance(this.balance[uniformType]);
1396
- if (uniformType === selectedType) {
1397
- client.resolve(this.balance[uniformType], messageHash);
1840
+ const messageHash = uniformType + ':balance';
1841
+ client.resolve(this.balance[uniformType], messageHash);
1842
+ }
1843
+ /**
1844
+ * @method
1845
+ * @name kucoin#watchPosition
1846
+ * @description watch open positions for a specific symbol
1847
+ * @see https://www.kucoin.com/docs-new/3470093w0
1848
+ * @param {string|undefined} symbol unified market symbol
1849
+ * @param {object} params extra parameters specific to the exchange API endpoint
1850
+ * @returns {object} a [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
1851
+ */
1852
+ async watchPosition(symbol = undefined, params = {}) {
1853
+ if (symbol === undefined) {
1854
+ throw new ArgumentsRequired(this.id + ' watchPosition() requires a symbol argument');
1855
+ }
1856
+ await this.loadMarkets();
1857
+ const url = await this.negotiate(true);
1858
+ const market = this.market(symbol);
1859
+ const topic = '/contract/position:' + market['id'];
1860
+ const request = {
1861
+ 'privateChannel': true,
1862
+ };
1863
+ const messageHash = 'position:' + market['symbol'];
1864
+ const client = this.client(url);
1865
+ this.setPositionCache(client, symbol);
1866
+ const fetchPositionSnapshot = this.handleOption('watchPosition', 'fetchPositionSnapshot', true);
1867
+ const awaitPositionSnapshot = this.handleOption('watchPosition', 'awaitPositionSnapshot', true);
1868
+ const currentPosition = this.getCurrentPosition(symbol);
1869
+ if (fetchPositionSnapshot && awaitPositionSnapshot && currentPosition === undefined) {
1870
+ const snapshot = await client.future('fetchPositionSnapshot:' + symbol);
1871
+ return snapshot;
1872
+ }
1873
+ return await this.subscribe(url, messageHash, topic, this.extend(request, params));
1874
+ }
1875
+ getCurrentPosition(symbol) {
1876
+ if (this.positions === undefined) {
1877
+ return undefined;
1878
+ }
1879
+ const cache = this.positions.hashmap;
1880
+ const symbolCache = this.safeValue(cache, symbol, {});
1881
+ const values = Object.values(symbolCache);
1882
+ return this.safeValue(values, 0);
1883
+ }
1884
+ setPositionCache(client, symbol) {
1885
+ const fetchPositionSnapshot = this.handleOption('watchPosition', 'fetchPositionSnapshot', false);
1886
+ if (fetchPositionSnapshot) {
1887
+ const messageHash = 'fetchPositionSnapshot:' + symbol;
1888
+ if (!(messageHash in client.futures)) {
1889
+ client.future(messageHash);
1890
+ this.spawn(this.loadPositionSnapshot, client, messageHash, symbol);
1891
+ }
1398
1892
  }
1399
1893
  }
1894
+ async loadPositionSnapshot(client, messageHash, symbol) {
1895
+ const position = await this.fetchPosition(symbol);
1896
+ this.positions = new ArrayCacheBySymbolById();
1897
+ const cache = this.positions;
1898
+ cache.append(position);
1899
+ // don't remove the future from the .futures cache
1900
+ if (messageHash in client.futures) {
1901
+ const future = client.futures[messageHash];
1902
+ future.resolve(cache);
1903
+ client.resolve(position, 'position:' + symbol);
1904
+ }
1905
+ }
1906
+ handlePosition(client, message) {
1907
+ //
1908
+ // Position Changes Caused Operations
1909
+ // {
1910
+ // "type": "message",
1911
+ // "userId": "5c32d69203aa676ce4b543c7", // Deprecated, will detele later
1912
+ // "channelType": "private",
1913
+ // "topic": "/contract/position:XBTUSDM",
1914
+ // "subject": "position.change",
1915
+ // "data": {
1916
+ // "realisedGrossPnl": 0E-8, //Accumulated realised profit and loss
1917
+ // "symbol": "XBTUSDM", //Symbol
1918
+ // "crossMode": false, //Cross mode or not
1919
+ // "liquidationPrice": 1000000.0, //Liquidation price
1920
+ // "posLoss": 0E-8, //Manually added margin amount
1921
+ // "avgEntryPrice": 7508.22, //Average entry price
1922
+ // "unrealisedPnl": -0.00014735, //Unrealised profit and loss
1923
+ // "markPrice": 7947.83, //Mark price
1924
+ // "posMargin": 0.00266779, //Position margin
1925
+ // "autoDeposit": false, //Auto deposit margin or not
1926
+ // "riskLimit": 100000, //Risk limit
1927
+ // "unrealisedCost": 0.00266375, //Unrealised value
1928
+ // "posComm": 0.00000392, //Bankruptcy cost
1929
+ // "posMaint": 0.00001724, //Maintenance margin
1930
+ // "posCost": 0.00266375, //Position value
1931
+ // "maintMarginReq": 0.005, //Maintenance margin rate
1932
+ // "bankruptPrice": 1000000.0, //Bankruptcy price
1933
+ // "realisedCost": 0.00000271, //Currently accumulated realised position value
1934
+ // "markValue": 0.00251640, //Mark value
1935
+ // "posInit": 0.00266375, //Position margin
1936
+ // "realisedPnl": -0.00000253, //Realised profit and losts
1937
+ // "maintMargin": 0.00252044, //Position margin
1938
+ // "realLeverage": 1.06, //Leverage of the order
1939
+ // "changeReason": "positionChange", //changeReason:marginChange、positionChange、liquidation、autoAppendMarginStatusChange、adl
1940
+ // "currentCost": 0.00266375, //Current position value
1941
+ // "openingTimestamp": 1558433191000, //Open time
1942
+ // "currentQty": -20, //Current position
1943
+ // "delevPercentage": 0.52, //ADL ranking percentile
1944
+ // "currentComm": 0.00000271, //Current commission
1945
+ // "realisedGrossCost": 0E-8, //Accumulated reliased gross profit value
1946
+ // "isOpen": true, //Opened position or not
1947
+ // "posCross": 1.2E-7, //Manually added margin
1948
+ // "currentTimestamp": 1558506060394, //Current timestamp
1949
+ // "unrealisedRoePcnt": -0.0553, //Rate of return on investment
1950
+ // "unrealisedPnlPcnt": -0.0553, //Position profit and loss ratio
1951
+ // "settleCurrency": "XBT" //Currency used to clear and settle the trades
1952
+ // }
1953
+ // }
1954
+ // Position Changes Caused by Mark Price
1955
+ // {
1956
+ // "userId": "5cd3f1a7b7ebc19ae9558591", // Deprecated, will detele later
1957
+ // "topic": "/contract/position:XBTUSDM",
1958
+ // "subject": "position.change",
1959
+ // "data": {
1960
+ // "markPrice": 7947.83, //Mark price
1961
+ // "markValue": 0.00251640, //Mark value
1962
+ // "maintMargin": 0.00252044, //Position margin
1963
+ // "realLeverage": 10.06, //Leverage of the order
1964
+ // "unrealisedPnl": -0.00014735, //Unrealised profit and lost
1965
+ // "unrealisedRoePcnt": -0.0553, //Rate of return on investment
1966
+ // "unrealisedPnlPcnt": -0.0553, //Position profit and loss ratio
1967
+ // "delevPercentage": 0.52, //ADL ranking percentile
1968
+ // "currentTimestamp": 1558087175068, //Current timestamp
1969
+ // "settleCurrency": "XBT" //Currency used to clear and settle the trades
1970
+ // }
1971
+ // }
1972
+ // Funding Settlement
1973
+ // {
1974
+ // "userId": "xbc453tg732eba53a88ggyt8c", // Deprecated, will detele later
1975
+ // "topic": "/contract/position:XBTUSDM",
1976
+ // "subject": "position.settlement",
1977
+ // "data": {
1978
+ // "fundingTime": 1551770400000, //Funding time
1979
+ // "qty": 100, //Position siz
1980
+ // "markPrice": 3610.85, //Settlement price
1981
+ // "fundingRate": -0.002966, //Funding rate
1982
+ // "fundingFee": -296, //Funding fees
1983
+ // "ts": 1547697294838004923, //Current time (nanosecond)
1984
+ // "settleCurrency": "XBT" //Currency used to clear and settle the trades
1985
+ // }
1986
+ // }
1987
+ // Adjustmet result of risk limit level
1988
+ // {
1989
+ // "userId": "xbc453tg732eba53a88ggyt8c",
1990
+ // "topic": "/contract/position:ADAUSDTM",
1991
+ // "subject": "position.adjustRiskLimit",
1992
+ // "data": {
1993
+ // "success": true, // Successful or not
1994
+ // "riskLimitLevel": 1, // Current risk limit level
1995
+ // "msg": "" // Failure reason
1996
+ // }
1997
+ // }
1998
+ //
1999
+ const topic = this.safeString(message, 'topic', '');
2000
+ const parts = topic.split(':');
2001
+ const marketId = this.safeString(parts, 1);
2002
+ const symbol = this.safeSymbol(marketId, undefined, '');
2003
+ const cache = this.positions;
2004
+ const currentPosition = this.getCurrentPosition(symbol);
2005
+ const messageHash = 'position:' + symbol;
2006
+ const data = this.safeDict(message, 'data', {});
2007
+ const newPosition = this.parsePosition(data);
2008
+ const keys = Object.keys(newPosition);
2009
+ for (let i = 0; i < keys.length; i++) {
2010
+ const key = keys[i];
2011
+ if (newPosition[key] === undefined) {
2012
+ delete newPosition[key];
2013
+ }
2014
+ }
2015
+ const position = this.extend(currentPosition, newPosition);
2016
+ cache.append(position);
2017
+ client.resolve(position, messageHash);
2018
+ }
1400
2019
  handleSubject(client, message) {
1401
2020
  //
1402
2021
  // {
@@ -1432,6 +2051,18 @@ export default class kucoin extends kucoinRest {
1432
2051
  'orderChange': this.handleOrder,
1433
2052
  'stopOrder': this.handleOrder,
1434
2053
  '/spot/tradeFills': this.handleMyTrade,
2054
+ // futures messages
2055
+ 'ticker': this.handleTicker,
2056
+ 'tickerV2': this.handleBidAsk,
2057
+ 'candle.stick': this.handleOHLCV,
2058
+ 'match': this.handleTrade,
2059
+ 'orderUpdated': this.handleOrder,
2060
+ 'symbolOrderChange': this.handleOrder,
2061
+ 'availableBalance.change': this.handleBalance,
2062
+ 'walletBalance.change': this.handleBalance,
2063
+ 'position.change': this.handlePosition,
2064
+ 'position.settlement': this.handlePosition,
2065
+ 'position.adjustRiskLimit': this.handlePosition,
1435
2066
  };
1436
2067
  const method = this.safeValue(methods, subject);
1437
2068
  if (method !== undefined) {
@@ -1487,4 +2118,14 @@ export default class kucoin extends kucoinRest {
1487
2118
  method.call(this, client, message);
1488
2119
  }
1489
2120
  }
2121
+ getMessageHash(elementName, symbol = undefined) {
2122
+ // method from kucoinfutures
2123
+ // elementName can be 'ticker', 'bidask', ...
2124
+ if (symbol !== undefined) {
2125
+ return elementName + ':' + symbol;
2126
+ }
2127
+ else {
2128
+ return elementName + 's@all';
2129
+ }
2130
+ }
1490
2131
  }