ccxt 4.4.21 → 4.4.23

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 (86) hide show
  1. package/README.md +112 -111
  2. package/dist/ccxt.browser.min.js +2 -2
  3. package/dist/cjs/ccxt.js +6 -1
  4. package/dist/cjs/src/abstract/coincatch.js +9 -0
  5. package/dist/cjs/src/alpaca.js +1 -0
  6. package/dist/cjs/src/base/Exchange.js +21 -0
  7. package/dist/cjs/src/bigone.js +3 -0
  8. package/dist/cjs/src/binance.js +172 -44
  9. package/dist/cjs/src/bitfinex.js +4 -0
  10. package/dist/cjs/src/bitflyer.js +58 -0
  11. package/dist/cjs/src/bitget.js +77 -0
  12. package/dist/cjs/src/bitrue.js +3 -0
  13. package/dist/cjs/src/bybit.js +80 -2
  14. package/dist/cjs/src/cex.js +1307 -1381
  15. package/dist/cjs/src/coinbase.js +1 -1
  16. package/dist/cjs/src/coinbaseexchange.js +3 -0
  17. package/dist/cjs/src/coincatch.js +5370 -0
  18. package/dist/cjs/src/coinex.js +63 -1
  19. package/dist/cjs/src/cryptocom.js +1 -1
  20. package/dist/cjs/src/gate.js +103 -3
  21. package/dist/cjs/src/htx.js +1 -7
  22. package/dist/cjs/src/hyperliquid.js +10 -8
  23. package/dist/cjs/src/kucoin.js +27 -59
  24. package/dist/cjs/src/latoken.js +6 -0
  25. package/dist/cjs/src/mexc.js +1 -1
  26. package/dist/cjs/src/oceanex.js +2 -0
  27. package/dist/cjs/src/okcoin.js +1 -0
  28. package/dist/cjs/src/okx.js +74 -0
  29. package/dist/cjs/src/poloniex.js +5 -0
  30. package/dist/cjs/src/pro/coincatch.js +1554 -0
  31. package/js/ccxt.d.ts +9 -3
  32. package/js/ccxt.js +6 -2
  33. package/js/src/abstract/binance.d.ts +21 -0
  34. package/js/src/abstract/binancecoinm.d.ts +21 -0
  35. package/js/src/abstract/binanceus.d.ts +21 -0
  36. package/js/src/abstract/binanceusdm.d.ts +21 -0
  37. package/js/src/abstract/bitflyer.d.ts +1 -0
  38. package/js/src/abstract/bitget.d.ts +3 -0
  39. package/js/src/abstract/cex.d.ts +28 -29
  40. package/js/src/abstract/coincatch.d.ts +97 -0
  41. package/js/src/abstract/coincatch.js +11 -0
  42. package/js/src/abstract/gate.d.ts +5 -0
  43. package/js/src/abstract/gateio.d.ts +5 -0
  44. package/js/src/abstract/kucoin.d.ts +1 -0
  45. package/js/src/abstract/kucoinfutures.d.ts +1 -0
  46. package/js/src/abstract/okx.d.ts +1 -0
  47. package/js/src/alpaca.js +1 -0
  48. package/js/src/base/Exchange.d.ts +8 -2
  49. package/js/src/base/Exchange.js +21 -0
  50. package/js/src/base/types.d.ts +8 -0
  51. package/js/src/bigone.js +3 -0
  52. package/js/src/binance.d.ts +3 -1
  53. package/js/src/binance.js +172 -44
  54. package/js/src/bitfinex.js +4 -0
  55. package/js/src/bitflyer.d.ts +3 -1
  56. package/js/src/bitflyer.js +58 -0
  57. package/js/src/bitget.d.ts +3 -1
  58. package/js/src/bitget.js +77 -0
  59. package/js/src/bitrue.js +3 -0
  60. package/js/src/bybit.d.ts +3 -1
  61. package/js/src/bybit.js +80 -2
  62. package/js/src/cex.d.ts +34 -20
  63. package/js/src/cex.js +1308 -1382
  64. package/js/src/coinbase.js +1 -1
  65. package/js/src/coinbaseexchange.js +3 -0
  66. package/js/src/coincatch.d.ts +130 -0
  67. package/js/src/coincatch.js +5371 -0
  68. package/js/src/coinex.d.ts +1 -0
  69. package/js/src/coinex.js +63 -1
  70. package/js/src/cryptocom.js +1 -1
  71. package/js/src/gate.d.ts +2 -0
  72. package/js/src/gate.js +103 -3
  73. package/js/src/htx.js +1 -7
  74. package/js/src/hyperliquid.js +10 -8
  75. package/js/src/kucoin.d.ts +0 -1
  76. package/js/src/kucoin.js +27 -59
  77. package/js/src/latoken.js +6 -0
  78. package/js/src/mexc.js +1 -1
  79. package/js/src/oceanex.js +2 -0
  80. package/js/src/okcoin.js +1 -0
  81. package/js/src/okx.d.ts +3 -1
  82. package/js/src/okx.js +74 -0
  83. package/js/src/poloniex.js +5 -0
  84. package/js/src/pro/coincatch.d.ts +57 -0
  85. package/js/src/pro/coincatch.js +1555 -0
  86. package/package.json +1 -1
@@ -0,0 +1,1554 @@
1
+ 'use strict';
2
+
3
+ var coincatch$1 = require('../coincatch.js');
4
+ var errors = require('../base/errors.js');
5
+ var Precise = require('../base/Precise.js');
6
+ var sha256 = require('../static_dependencies/noble-hashes/sha256.js');
7
+ var Cache = require('../base/ws/Cache.js');
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // ---------------------------------------------------------------------------
11
+ class coincatch extends coincatch$1 {
12
+ describe() {
13
+ return this.deepExtend(super.describe(), {
14
+ 'has': {
15
+ 'ws': true,
16
+ 'watchTrades': true,
17
+ 'watchTradesForSymbols': true,
18
+ 'watchOrderBook': true,
19
+ 'watchOrderBookForSymbols': true,
20
+ 'watchOHLCV': true,
21
+ 'watchOHLCVForSymbols': false,
22
+ 'watchOrders': true,
23
+ 'watchMyTrades': false,
24
+ 'watchTicker': true,
25
+ 'watchTickers': true,
26
+ 'watchBalance': true,
27
+ 'watchPositions': true,
28
+ },
29
+ 'urls': {
30
+ 'api': {
31
+ 'ws': {
32
+ 'public': 'wss://ws.coincatch.com/public/v1/stream',
33
+ 'private': 'wss://ws.coincatch.com/private/v1/stream',
34
+ },
35
+ },
36
+ },
37
+ 'options': {
38
+ 'tradesLimit': 1000,
39
+ 'OHLCVLimit': 200,
40
+ 'timeframesForWs': {
41
+ '1m': '1m',
42
+ '5m': '5m',
43
+ '15m': '15m',
44
+ '30m': '30m',
45
+ '1h': '1H',
46
+ '4h': '4H',
47
+ '12h': '12H',
48
+ '1d': '1D',
49
+ '1w': '1W',
50
+ },
51
+ 'watchOrderBook': {
52
+ 'checksum': true,
53
+ },
54
+ },
55
+ 'streaming': {
56
+ 'ping': this.ping,
57
+ },
58
+ 'exceptions': {
59
+ 'ws': {
60
+ 'exact': {
61
+ '30001': errors.BadRequest,
62
+ '30002': errors.AuthenticationError,
63
+ '30003': errors.BadRequest,
64
+ '30004': errors.AuthenticationError,
65
+ '30005': errors.AuthenticationError,
66
+ '30006': errors.RateLimitExceeded,
67
+ '30007': errors.RateLimitExceeded,
68
+ '30011': errors.AuthenticationError,
69
+ '30012': errors.AuthenticationError,
70
+ '30013': errors.AuthenticationError,
71
+ '30014': errors.BadRequest,
72
+ '30015': errors.AuthenticationError, // { event: 'error', code: 30015, msg: 'Invalid sign' }
73
+ },
74
+ 'broad': {},
75
+ },
76
+ },
77
+ });
78
+ }
79
+ getMarketFromArg(entry) {
80
+ const instId = this.safeString(entry, 'instId');
81
+ const instType = this.safeString(entry, 'instType');
82
+ const baseAndQuote = this.parseSpotMarketId(instId);
83
+ const baseId = baseAndQuote['baseId'];
84
+ const quoteId = baseAndQuote['quoteId'];
85
+ let suffix = '_SPBL'; // spot suffix
86
+ if (instType === 'mc') {
87
+ if (quoteId === 'USD') {
88
+ suffix = '_DMCBL';
89
+ }
90
+ else {
91
+ suffix = '_UMCBL';
92
+ }
93
+ }
94
+ const marketId = this.safeCurrencyCode(baseId) + this.safeCurrencyCode(quoteId) + suffix;
95
+ return this.safeMarketCustom(marketId);
96
+ }
97
+ async authenticate(params = {}) {
98
+ this.checkRequiredCredentials();
99
+ const url = this.urls['api']['ws']['private'];
100
+ const client = this.client(url);
101
+ const messageHash = 'authenticated';
102
+ const future = client.future(messageHash);
103
+ const authenticated = this.safeValue(client.subscriptions, messageHash);
104
+ if (authenticated === undefined) {
105
+ const timestamp = this.seconds().toString();
106
+ const auth = timestamp + 'GET' + '/user/verify';
107
+ const signature = this.hmac(this.encode(auth), this.encode(this.secret), sha256.sha256, 'base64');
108
+ const operation = 'login';
109
+ const request = {
110
+ 'op': operation,
111
+ 'args': [
112
+ {
113
+ 'apiKey': this.apiKey,
114
+ 'passphrase': this.password,
115
+ 'timestamp': timestamp,
116
+ 'sign': signature,
117
+ },
118
+ ],
119
+ };
120
+ const message = this.extend(request, params);
121
+ this.watch(url, messageHash, message, messageHash);
122
+ }
123
+ return await future;
124
+ }
125
+ async watchPublic(messageHash, subscribeHash, args, params = {}) {
126
+ const url = this.urls['api']['ws']['public'];
127
+ const request = {
128
+ 'op': 'subscribe',
129
+ 'args': [args],
130
+ };
131
+ const message = this.extend(request, params);
132
+ return await this.watch(url, messageHash, message, subscribeHash);
133
+ }
134
+ async unWatchPublic(messageHash, args, params = {}) {
135
+ const url = this.urls['api']['ws']['public'];
136
+ const request = {
137
+ 'op': 'unsubscribe',
138
+ 'args': [args],
139
+ };
140
+ const message = this.extend(request, params);
141
+ return await this.watch(url, messageHash, message, messageHash);
142
+ }
143
+ async watchPrivate(messageHash, subscribeHash, args, params = {}) {
144
+ await this.authenticate();
145
+ const url = this.urls['api']['ws']['private'];
146
+ const request = {
147
+ 'op': 'subscribe',
148
+ 'args': [args],
149
+ };
150
+ const message = this.extend(request, params);
151
+ return await this.watch(url, messageHash, message, subscribeHash);
152
+ }
153
+ async watchPrivateMultiple(messageHashes, subscribeHashes, args, params = {}) {
154
+ await this.authenticate();
155
+ const url = this.urls['api']['ws']['private'];
156
+ const request = {
157
+ 'op': 'subscribe',
158
+ 'args': args,
159
+ };
160
+ const message = this.extend(request, params);
161
+ return await this.watchMultiple(url, messageHashes, message, subscribeHashes);
162
+ }
163
+ handleAuthenticate(client, message) {
164
+ //
165
+ // { event: "login", code: 0 }
166
+ //
167
+ const messageHash = 'authenticated';
168
+ const future = this.safeValue(client.futures, messageHash);
169
+ future.resolve(true);
170
+ }
171
+ async watchPublicMultiple(messageHashes, subscribeHashes, argsArray, params = {}) {
172
+ const url = this.urls['api']['ws']['public'];
173
+ const request = {
174
+ 'op': 'subscribe',
175
+ 'args': argsArray,
176
+ };
177
+ const message = this.extend(request, params);
178
+ return await this.watchMultiple(url, messageHashes, message, subscribeHashes);
179
+ }
180
+ async unWatchChannel(symbol, channel, messageHashTopic, params = {}) {
181
+ await this.loadMarkets();
182
+ const market = this.market(symbol);
183
+ const [instType, instId] = this.getPublicInstTypeAndId(market);
184
+ const messageHash = 'unsubscribe:' + messageHashTopic + ':' + symbol;
185
+ const args = {
186
+ 'instType': instType,
187
+ 'channel': channel,
188
+ 'instId': instId,
189
+ };
190
+ return await this.unWatchPublic(messageHash, args, params);
191
+ }
192
+ getPublicInstTypeAndId(market) {
193
+ const instId = market['baseId'] + market['quoteId'];
194
+ let instType = undefined;
195
+ if (market['spot']) {
196
+ instType = 'SP';
197
+ }
198
+ else if (market['swap']) {
199
+ instType = 'MC';
200
+ }
201
+ else {
202
+ throw new errors.NotSupported(this.id + ' supports only spot and swap markets');
203
+ }
204
+ return [instType, instId];
205
+ }
206
+ handleDMCBLMarketByMessageHashes(market, hash, client, timeframe = undefined) {
207
+ const marketId = market['id'];
208
+ const messageHashes = this.findMessageHashes(client, hash);
209
+ // the exchange counts DMCBL markets as the same market with different quote currencies
210
+ // for example symbols ETHUSD:ETH and ETH/USD:BTC both have the same marketId ETHUSD_DMCBL
211
+ // we need to check all markets with the same marketId to find the correct market that is in messageHashes
212
+ const marketsWithCurrentId = this.safeList(this.markets_by_id, marketId, []);
213
+ let suffix = '';
214
+ if (timeframe !== undefined) {
215
+ suffix = ':' + timeframe;
216
+ }
217
+ for (let i = 0; i < marketsWithCurrentId.length; i++) {
218
+ market = marketsWithCurrentId[i];
219
+ const symbol = market['symbol'];
220
+ const messageHash = hash + symbol + suffix;
221
+ if (this.inArray(messageHash, messageHashes)) {
222
+ return market;
223
+ }
224
+ }
225
+ return market;
226
+ }
227
+ async watchTicker(symbol, params = {}) {
228
+ /**
229
+ * @method
230
+ * @name coincatch#watchTicker
231
+ * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
232
+ * @see https://coincatch.github.io/github.io/en/spot/#tickers-channel
233
+ * @param {string} symbol unified symbol of the market to fetch the ticker for
234
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
235
+ * @param {string} [params.instType] the type of the instrument to fetch the ticker for, 'SP' for spot markets, 'MC' for futures markets (default is 'SP')
236
+ * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
237
+ */
238
+ await this.loadMarkets();
239
+ const market = this.market(symbol);
240
+ const [instType, instId] = this.getPublicInstTypeAndId(market);
241
+ const channel = 'ticker';
242
+ const messageHash = channel + ':' + symbol;
243
+ const args = {
244
+ 'instType': instType,
245
+ 'channel': channel,
246
+ 'instId': instId,
247
+ };
248
+ return await this.watchPublic(messageHash, messageHash, args, params);
249
+ }
250
+ async unWatchTicker(symbol, params = {}) {
251
+ /**
252
+ * @method
253
+ * @name coinctach#unWatchTicker
254
+ * @description unsubscribe from the ticker channel
255
+ * @see https://coincatch.github.io/github.io/en/mix/#tickers-channel
256
+ * @param {string} symbol unified symbol of the market to unwatch the ticker for
257
+ * @returns {any} status of the unwatch request
258
+ */
259
+ await this.loadMarkets();
260
+ return await this.unWatchChannel(symbol, 'ticker', 'ticker', params);
261
+ }
262
+ async watchTickers(symbols = undefined, params = {}) {
263
+ /**
264
+ * @method
265
+ * @name coincatch#watchTickers
266
+ * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
267
+ * @see https://coincatch.github.io/github.io/en/mix/#tickers-channel
268
+ * @param {string[]} symbols unified symbol of the market to watch the tickers for
269
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
270
+ * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
271
+ */
272
+ await this.loadMarkets();
273
+ if (symbols === undefined) {
274
+ symbols = this.symbols;
275
+ }
276
+ const topics = [];
277
+ const messageHashes = [];
278
+ for (let i = 0; i < symbols.length; i++) {
279
+ const symbol = symbols[i];
280
+ const market = this.market(symbol);
281
+ const [instType, instId] = this.getPublicInstTypeAndId(market);
282
+ const args = {
283
+ 'instType': instType,
284
+ 'channel': 'ticker',
285
+ 'instId': instId,
286
+ };
287
+ topics.push(args);
288
+ messageHashes.push('ticker:' + symbol);
289
+ }
290
+ const tickers = await this.watchPublicMultiple(messageHashes, messageHashes, topics, params);
291
+ if (this.newUpdates) {
292
+ const result = {};
293
+ result[tickers['symbol']] = tickers;
294
+ return result;
295
+ }
296
+ return this.filterByArray(this.tickers, 'symbol', symbols);
297
+ }
298
+ handleTicker(client, message) {
299
+ //
300
+ // action: 'snapshot',
301
+ // arg: { instType: 'sp', channel: 'ticker', instId: 'ETHUSDT' },
302
+ // data: [
303
+ // {
304
+ // instId: 'ETHUSDT',
305
+ // last: '2421.06',
306
+ // open24h: '2416.93',
307
+ // high24h: '2441.47',
308
+ // low24h: '2352.99',
309
+ // bestBid: '2421.03',
310
+ // bestAsk: '2421.06',
311
+ // baseVolume: '9445.2043',
312
+ // quoteVolume: '22807159.1148',
313
+ // ts: 1728131730687,
314
+ // labeId: 0,
315
+ // openUtc: '2414.50',
316
+ // chgUTC: '0.00272',
317
+ // bidSz: '3.866',
318
+ // askSz: '0.124'
319
+ // }
320
+ // ],
321
+ // ts: 1728131730688
322
+ //
323
+ const arg = this.safeDict(message, 'arg', {});
324
+ let market = this.getMarketFromArg(arg);
325
+ const marketId = market['id'];
326
+ const hash = 'ticker:';
327
+ if (marketId.indexOf('_DMCBL') >= 0) {
328
+ market = this.handleDMCBLMarketByMessageHashes(market, hash, client);
329
+ }
330
+ const data = this.safeList(message, 'data', []);
331
+ const ticker = this.parseWsTicker(this.safeDict(data, 0, {}), market);
332
+ const symbol = market['symbol'];
333
+ this.tickers[symbol] = ticker;
334
+ const messageHash = hash + symbol;
335
+ client.resolve(this.tickers[symbol], messageHash);
336
+ }
337
+ parseWsTicker(ticker, market = undefined) {
338
+ //
339
+ // spot
340
+ // {
341
+ // instId: 'ETHUSDT',
342
+ // last: '2421.06',
343
+ // open24h: '2416.93',
344
+ // high24h: '2441.47',
345
+ // low24h: '2352.99',
346
+ // bestBid: '2421.03',
347
+ // bestAsk: '2421.06',
348
+ // baseVolume: '9445.2043',
349
+ // quoteVolume: '22807159.1148',
350
+ // ts: 1728131730687,
351
+ // labeId: 0,
352
+ // openUtc: '2414.50',
353
+ // chgUTC: '0.00272',
354
+ // bidSz: '3.866',
355
+ // askSz: '0.124'
356
+ // }
357
+ //
358
+ // swap
359
+ // {
360
+ // instId: 'ETHUSDT',
361
+ // last: '2434.47',
362
+ // bestAsk: '2434.48',
363
+ // bestBid: '2434.47',
364
+ // high24h: '2471.68',
365
+ // low24h: '2400.01',
366
+ // priceChangePercent: '0.00674',
367
+ // capitalRate: '0.000082',
368
+ // nextSettleTime: 1728489600000,
369
+ // systemTime: 1728471993602,
370
+ // markPrice: '2434.46',
371
+ // indexPrice: '2435.44',
372
+ // holding: '171450.25',
373
+ // baseVolume: '1699298.91',
374
+ // quoteVolume: '4144522832.32',
375
+ // openUtc: '2439.67',
376
+ // chgUTC: '-0.00213',
377
+ // symbolType: 1,
378
+ // symbolId: 'ETHUSDT_UMCBL',
379
+ // deliveryPrice: '0',
380
+ // bidSz: '26.12',
381
+ // askSz: '49.6'
382
+ // }
383
+ //
384
+ const last = this.safeString(ticker, 'last');
385
+ const timestamp = this.safeInteger2(ticker, 'ts', 'systemTime');
386
+ return this.safeTicker({
387
+ 'symbol': market['symbol'],
388
+ 'timestamp': timestamp,
389
+ 'datetime': this.iso8601(timestamp),
390
+ 'high': this.safeString(ticker, 'high24h'),
391
+ 'low': this.safeString(ticker, 'low24h'),
392
+ 'bid': this.safeString(ticker, 'bestBid'),
393
+ 'bidVolume': this.safeString(ticker, 'bidSz'),
394
+ 'ask': this.safeString(ticker, 'bestAsk'),
395
+ 'askVolume': this.safeString(ticker, 'askSz'),
396
+ 'vwap': undefined,
397
+ 'open': this.safeString2(ticker, 'open24h', 'openUtc'),
398
+ 'close': last,
399
+ 'last': last,
400
+ 'previousClose': undefined,
401
+ 'change': undefined,
402
+ 'percentage': Precise["default"].stringMul(this.safeString(ticker, 'chgUTC'), '100'),
403
+ 'average': undefined,
404
+ 'baseVolume': this.safeNumber(ticker, 'baseVolume'),
405
+ 'quoteVolume': this.safeNumber(ticker, 'quoteVolume'),
406
+ 'indexPrice': this.safeString(ticker, 'indexPrice'),
407
+ 'markPrice': this.safeString(ticker, 'markPrice'),
408
+ 'info': ticker,
409
+ }, market);
410
+ }
411
+ async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
412
+ /**
413
+ * @method
414
+ * @name coincatch#watchOHLCV
415
+ * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
416
+ * @see https://coincatch.github.io/github.io/en/spot/#candlesticks-channel
417
+ * @param {string} symbol unified symbol of the market to fetch OHLCV data for
418
+ * @param {string} timeframe the length of time each candle represents
419
+ * @param {int} [since] timestamp in ms of the earliest candle to fetch (not including)
420
+ * @param {int} [limit] the maximum amount of candles to fetch (not including)
421
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
422
+ * @param {bool} [params.instType] the type of the instrument to fetch the OHLCV data for, 'SP' for spot markets, 'MC' for futures markets (default is 'SP')
423
+ * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
424
+ */
425
+ await this.loadMarkets();
426
+ const market = this.market(symbol);
427
+ const timeframes = this.options['timeframesForWs'];
428
+ const channel = 'candle' + this.safeString(timeframes, timeframe);
429
+ const [instType, instId] = this.getPublicInstTypeAndId(market);
430
+ const args = {
431
+ 'instType': instType,
432
+ 'channel': channel,
433
+ 'instId': instId,
434
+ };
435
+ const messageHash = 'ohlcv:' + symbol + ':' + timeframe;
436
+ const ohlcv = await this.watchPublic(messageHash, messageHash, args, params);
437
+ if (this.newUpdates) {
438
+ limit = ohlcv.getLimit(symbol, limit);
439
+ }
440
+ return this.filterBySinceLimit(ohlcv, since, limit, 0, true);
441
+ }
442
+ async unWatchOHLCV(symbol, timeframe = '1m', params = {}) {
443
+ /**
444
+ * @method
445
+ * @name coincatch#unWatchOHLCV
446
+ * @description unsubscribe from the ohlcv channel
447
+ * @see https://www.bitget.com/api-doc/spot/websocket/public/Candlesticks-Channel
448
+ * @param {string} symbol unified symbol of the market to unwatch the ohlcv for
449
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
450
+ */
451
+ await this.loadMarkets();
452
+ const timeframes = this.options['timeframesForWs'];
453
+ const interval = this.safeString(timeframes, timeframe);
454
+ const channel = 'candle' + interval;
455
+ return await this.unWatchChannel(symbol, channel, 'ohlcv:' + interval, params);
456
+ }
457
+ handleOHLCV(client, message) {
458
+ //
459
+ // {
460
+ // action: 'update',
461
+ // arg: { instType: 'sp', channel: 'candle1D', instId: 'ETHUSDT' },
462
+ // data: [
463
+ // [
464
+ // '1728316800000',
465
+ // '2474.5',
466
+ // '2478.21',
467
+ // '2459.8',
468
+ // '2463.51',
469
+ // '86.0551'
470
+ // ]
471
+ // ],
472
+ // ts: 1728317607657
473
+ // }
474
+ //
475
+ const arg = this.safeDict(message, 'arg', {});
476
+ let market = this.getMarketFromArg(arg);
477
+ const marketId = market['id'];
478
+ const hash = 'ohlcv:';
479
+ const data = this.safeList(message, 'data', []);
480
+ const channel = this.safeString(arg, 'channel');
481
+ const klineType = channel.slice(6);
482
+ const timeframe = this.findTimeframe(klineType);
483
+ if (marketId.indexOf('_DMCBL') >= 0) {
484
+ market = this.handleDMCBLMarketByMessageHashes(market, hash, client, timeframe);
485
+ }
486
+ const symbol = market['symbol'];
487
+ if (!(symbol in this.ohlcvs)) {
488
+ this.ohlcvs[symbol] = {};
489
+ }
490
+ if (!(timeframe in this.ohlcvs[symbol])) {
491
+ const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
492
+ this.ohlcvs[symbol][timeframe] = new Cache.ArrayCacheByTimestamp(limit);
493
+ }
494
+ const stored = this.ohlcvs[symbol][timeframe];
495
+ for (let i = 0; i < data.length; i++) {
496
+ const candle = this.safeList(data, i, []);
497
+ const parsed = this.parseWsOHLCV(candle, market);
498
+ stored.append(parsed);
499
+ }
500
+ const messageHash = hash + symbol + ':' + timeframe;
501
+ client.resolve(stored, messageHash);
502
+ }
503
+ parseWsOHLCV(ohlcv, market = undefined) {
504
+ //
505
+ // [
506
+ // '1728316800000',
507
+ // '2474.5',
508
+ // '2478.21',
509
+ // '2459.8',
510
+ // '2463.51',
511
+ // '86.0551'
512
+ // ]
513
+ //
514
+ return [
515
+ this.safeInteger(ohlcv, 0),
516
+ this.safeNumber(ohlcv, 1),
517
+ this.safeNumber(ohlcv, 2),
518
+ this.safeNumber(ohlcv, 3),
519
+ this.safeNumber(ohlcv, 4),
520
+ this.safeNumber(ohlcv, 5),
521
+ ];
522
+ }
523
+ async watchOrderBook(symbol, limit = undefined, params = {}) {
524
+ /**
525
+ * @method
526
+ * @name coincatch#watchOrderBook
527
+ * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
528
+ * @see https://coincatch.github.io/github.io/en/spot/#depth-channel
529
+ * @param {string} symbol unified symbol of the market to fetch the order book for
530
+ * @param {int} [limit] the maximum amount of order book entries to return
531
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
532
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
533
+ */
534
+ return await this.watchOrderBookForSymbols([symbol], limit, params);
535
+ }
536
+ async unWatchOrderBook(symbol, params = {}) {
537
+ /**
538
+ * @method
539
+ * @name coincatch#unWatchOrderBook
540
+ * @description unsubscribe from the orderbook channel
541
+ * @see https://coincatch.github.io/github.io/en/spot/#depth-channel
542
+ * @param {string} symbol unified symbol of the market to fetch the order book for
543
+ * @param {int} [params.limit] orderbook limit, default is undefined
544
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
545
+ */
546
+ await this.loadMarkets();
547
+ let channel = 'books';
548
+ const limit = this.safeInteger(params, 'limit');
549
+ if ((limit === 5) || (limit === 15)) {
550
+ params = this.omit(params, 'limit');
551
+ channel += limit.toString();
552
+ }
553
+ return await this.unWatchChannel(symbol, channel, channel, params);
554
+ }
555
+ async watchOrderBookForSymbols(symbols, limit = undefined, params = {}) {
556
+ /**
557
+ * @method
558
+ * @name coincatch#watchOrderBook
559
+ * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
560
+ * @see https://coincatch.github.io/github.io/en/spot/#depth-channel
561
+ * @param {string} symbol unified symbol of the market to fetch the order book for
562
+ * @param {int} [limit] the maximum amount of order book entries to return
563
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
564
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
565
+ */
566
+ await this.loadMarkets();
567
+ symbols = this.marketSymbols(symbols);
568
+ const channel = 'books';
569
+ const topics = [];
570
+ const messageHashes = [];
571
+ for (let i = 0; i < symbols.length; i++) {
572
+ const symbol = symbols[i];
573
+ const market = this.market(symbol);
574
+ const [instType, instId] = this.getPublicInstTypeAndId(market);
575
+ const args = {
576
+ 'instType': instType,
577
+ 'channel': channel,
578
+ 'instId': instId,
579
+ };
580
+ topics.push(args);
581
+ messageHashes.push(channel + ':' + symbol);
582
+ }
583
+ const orderbook = await this.watchPublicMultiple(messageHashes, messageHashes, topics, params);
584
+ return orderbook.limit();
585
+ }
586
+ handleOrderBook(client, message) {
587
+ //
588
+ // {
589
+ // action: 'update',
590
+ // arg: { instType: 'sp', channel: 'books', instId: 'ETHUSDT' },
591
+ // data: [
592
+ // {
593
+ // asks: [ [ 2507.07, 0.4248 ] ],
594
+ // bids: [ [ 2507.05, 0.1198 ] ],
595
+ // checksum: -1400923312,
596
+ // ts: '1728339446908'
597
+ // }
598
+ // ],
599
+ // ts: 1728339446908
600
+ // }
601
+ //
602
+ const arg = this.safeDict(message, 'arg', {});
603
+ let market = this.getMarketFromArg(arg);
604
+ const marketId = market['id'];
605
+ const hash = 'books:';
606
+ if (marketId.indexOf('_DMCBL') >= 0) {
607
+ market = this.handleDMCBLMarketByMessageHashes(market, hash, client);
608
+ }
609
+ const symbol = market['symbol'];
610
+ const channel = this.safeString(arg, 'channel');
611
+ const messageHash = hash + symbol;
612
+ const data = this.safeList(message, 'data', []);
613
+ const rawOrderBook = this.safeDict(data, 0);
614
+ const timestamp = this.safeInteger(rawOrderBook, 'ts');
615
+ const incrementalBook = channel;
616
+ if (incrementalBook) {
617
+ if (!(symbol in this.orderbooks)) {
618
+ const ob = this.countedOrderBook({});
619
+ ob['symbol'] = symbol;
620
+ this.orderbooks[symbol] = ob;
621
+ }
622
+ const storedOrderBook = this.orderbooks[symbol];
623
+ const asks = this.safeList(rawOrderBook, 'asks', []);
624
+ const bids = this.safeList(rawOrderBook, 'bids', []);
625
+ this.handleDeltas(storedOrderBook['asks'], asks);
626
+ this.handleDeltas(storedOrderBook['bids'], bids);
627
+ storedOrderBook['timestamp'] = timestamp;
628
+ storedOrderBook['datetime'] = this.iso8601(timestamp);
629
+ const checksum = this.safeBool(this.options, 'checksum', true);
630
+ const isSnapshot = this.safeString(message, 'action') === 'snapshot';
631
+ if (!isSnapshot && checksum) {
632
+ const storedAsks = storedOrderBook['asks'];
633
+ const storedBids = storedOrderBook['bids'];
634
+ const asksLength = storedAsks.length;
635
+ const bidsLength = storedBids.length;
636
+ const payloadArray = [];
637
+ for (let i = 0; i < 25; i++) {
638
+ if (i < bidsLength) {
639
+ payloadArray.push(storedBids[i][2][0]);
640
+ payloadArray.push(storedBids[i][2][1]);
641
+ }
642
+ if (i < asksLength) {
643
+ payloadArray.push(storedAsks[i][2][0]);
644
+ payloadArray.push(storedAsks[i][2][1]);
645
+ }
646
+ }
647
+ const payload = payloadArray.join(':');
648
+ const calculatedChecksum = this.crc32(payload, true);
649
+ const responseChecksum = this.safeInteger(rawOrderBook, 'checksum');
650
+ if (calculatedChecksum !== responseChecksum) {
651
+ this.spawn(this.handleCheckSumError, client, symbol, messageHash);
652
+ return;
653
+ }
654
+ }
655
+ }
656
+ else {
657
+ const orderbook = this.orderBook({});
658
+ const parsedOrderbook = this.parseOrderBook(rawOrderBook, symbol, timestamp);
659
+ orderbook.reset(parsedOrderbook);
660
+ this.orderbooks[symbol] = orderbook;
661
+ }
662
+ client.resolve(this.orderbooks[symbol], messageHash);
663
+ }
664
+ async handleCheckSumError(client, symbol, messageHash) {
665
+ await this.unWatchOrderBook(symbol);
666
+ const error = new errors.ChecksumError(this.id + ' ' + this.orderbookChecksumMessage(symbol));
667
+ client.reject(error, messageHash);
668
+ }
669
+ handleDelta(bookside, delta) {
670
+ const bidAsk = this.parseBidAsk(delta, 0, 1);
671
+ bidAsk.push(delta);
672
+ bookside.storeArray(bidAsk);
673
+ }
674
+ handleDeltas(bookside, deltas) {
675
+ for (let i = 0; i < deltas.length; i++) {
676
+ this.handleDelta(bookside, deltas[i]);
677
+ }
678
+ }
679
+ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
680
+ /**
681
+ * @method
682
+ * @name coincatch#watchTrades
683
+ * @description get the list of most recent trades for a particular symbol
684
+ * @see https://coincatch.github.io/github.io/en/spot/#trades-channel
685
+ * @param {string} symbol unified symbol of the market to fetch trades for
686
+ * @param {int} [since] timestamp in ms of the earliest trade to fetch
687
+ * @param {int} [limit] the maximum amount of trades to fetch
688
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
689
+ * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
690
+ */
691
+ return await this.watchTradesForSymbols([symbol], since, limit, params);
692
+ }
693
+ async watchTradesForSymbols(symbols, since = undefined, limit = undefined, params = {}) {
694
+ /**
695
+ * @method
696
+ * @name coincatch#watchTrades
697
+ * @description watches information on multiple trades made in a market
698
+ * @see https://coincatch.github.io/github.io/en/spot/#trades-channel
699
+ * @param {string} symbol unified market symbol of the market trades were made in
700
+ * @param {int} [since] the earliest time in ms to fetch orders for
701
+ * @param {int} [limit] the maximum number of trade structures to retrieve
702
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
703
+ * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}
704
+ */
705
+ const symbolsLength = symbols.length;
706
+ if (symbolsLength === 0) {
707
+ throw new errors.ArgumentsRequired(this.id + ' watchTradesForSymbols() requires a non-empty array of symbols');
708
+ }
709
+ await this.loadMarkets();
710
+ symbols = this.marketSymbols(symbols);
711
+ const topics = [];
712
+ const messageHashes = [];
713
+ for (let i = 0; i < symbols.length; i++) {
714
+ const symbol = symbols[i];
715
+ const market = this.market(symbol);
716
+ const [instType, instId] = this.getPublicInstTypeAndId(market);
717
+ const args = {
718
+ 'instType': instType,
719
+ 'channel': 'trade',
720
+ 'instId': instId,
721
+ };
722
+ topics.push(args);
723
+ messageHashes.push('trade:' + symbol);
724
+ }
725
+ const trades = await this.watchPublicMultiple(messageHashes, messageHashes, topics, params);
726
+ if (this.newUpdates) {
727
+ const first = this.safeDict(trades, 0);
728
+ const tradeSymbol = this.safeString(first, 'symbol');
729
+ limit = trades.getLimit(tradeSymbol, limit);
730
+ }
731
+ return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
732
+ }
733
+ async unWatchTrades(symbol, params = {}) {
734
+ /**
735
+ * @method
736
+ * @name coincatch#unWatchTrades
737
+ * @description unsubscribe from the trades channel
738
+ * @see https://coincatch.github.io/github.io/en/spot/#trades-channel
739
+ * @param {string} symbol unified symbol of the market to unwatch the trades for
740
+ * @returns {any} status of the unwatch request
741
+ */
742
+ await this.loadMarkets();
743
+ return await this.unWatchChannel(symbol, 'trade', 'trade', params);
744
+ }
745
+ handleTrades(client, message) {
746
+ //
747
+ // {
748
+ // action: 'update',
749
+ // arg: { instType: 'sp', channel: 'trade', instId: 'ETHUSDT' },
750
+ // data: [ [ '1728341807469', '2421.41', '0.478', 'sell' ] ],
751
+ // ts: 1728341807482
752
+ // }
753
+ //
754
+ const arg = this.safeDict(message, 'arg', {});
755
+ let market = this.getMarketFromArg(arg);
756
+ const marketId = market['id'];
757
+ const hash = 'trade:';
758
+ if (marketId.indexOf('_DMCBL') >= 0) {
759
+ market = this.handleDMCBLMarketByMessageHashes(market, hash, client);
760
+ }
761
+ const symbol = market['symbol'];
762
+ if (!(symbol in this.trades)) {
763
+ const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
764
+ this.trades[symbol] = new Cache.ArrayCache(limit);
765
+ }
766
+ const stored = this.trades[symbol];
767
+ let data = this.safeList(message, 'data', []);
768
+ if (data !== undefined) {
769
+ data = this.sortBy(data, 0);
770
+ for (let i = 0; i < data.length; i++) {
771
+ const trade = this.safeList(data, i);
772
+ const parsed = this.parseWsTrade(trade, market);
773
+ stored.append(parsed);
774
+ }
775
+ }
776
+ const messageHash = hash + symbol;
777
+ client.resolve(stored, messageHash);
778
+ }
779
+ parseWsTrade(trade, market = undefined) {
780
+ //
781
+ // [
782
+ // '1728341807469',
783
+ // '2421.41',
784
+ // '0.478',
785
+ // 'sell'
786
+ // ]
787
+ //
788
+ const timestamp = this.safeInteger(trade, 0);
789
+ return this.safeTrade({
790
+ 'id': undefined,
791
+ 'timestamp': timestamp,
792
+ 'datetime': this.iso8601(timestamp),
793
+ 'symbol': market['symbol'],
794
+ 'side': this.safeStringLower(trade, 3),
795
+ 'price': this.safeString(trade, 1),
796
+ 'amount': this.safeString(trade, 2),
797
+ 'cost': undefined,
798
+ 'takerOrMaker': undefined,
799
+ 'type': undefined,
800
+ 'order': undefined,
801
+ 'fee': undefined,
802
+ 'info': trade,
803
+ }, market);
804
+ }
805
+ async watchBalance(params = {}) {
806
+ /**
807
+ * @method
808
+ * @name coincatch#watchBalance
809
+ * @description watch balance and get the amount of funds available for trading or funds locked in orders
810
+ * @see https://coincatch.github.io/github.io/en/spot/#account-channel
811
+ * @see https://coincatch.github.io/github.io/en/mix/#account-channel
812
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
813
+ * @param {str} [params.type] 'spot' or 'swap' (default is 'spot')
814
+ * @param {string} [params.instType] *swap only* 'umcbl' or 'dmcbl' (default is 'umcbl')
815
+ * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
816
+ */
817
+ let type = undefined;
818
+ [type, params] = this.handleMarketTypeAndParams('watchBalance', undefined, params);
819
+ let instType = 'spbl'; // must be lower case for spot
820
+ if (type === 'swap') {
821
+ instType = 'umcbl';
822
+ }
823
+ const channel = 'account';
824
+ [instType, params] = this.handleOptionAndParams(params, 'watchBalance', 'instType', instType);
825
+ const args = {
826
+ 'instType': instType,
827
+ 'channel': channel,
828
+ 'instId': 'default',
829
+ };
830
+ const messageHash = 'balance:' + instType.toLowerCase();
831
+ return await this.watchPrivate(messageHash, messageHash, args, params);
832
+ }
833
+ handleBalance(client, message) {
834
+ //
835
+ // spot
836
+ // {
837
+ // action: 'snapshot',
838
+ // arg: { instType: 'spbl', channel: 'account', instId: 'default' },
839
+ // data: [
840
+ // {
841
+ // coinId: '3',
842
+ // coinName: 'ETH',
843
+ // available: '0.0000832',
844
+ // frozen: '0',
845
+ // lock: '0'
846
+ // }
847
+ // ],
848
+ // ts: 1728464548725
849
+ // }
850
+ //
851
+ // // swap
852
+ // {
853
+ // action: 'snapshot',
854
+ // arg: { instType: 'dmcbl', channel: 'account', instId: 'default' },
855
+ // data: [
856
+ // {
857
+ // marginCoin: 'ETH',
858
+ // locked: '0.00000000',
859
+ // available: '0.00001203',
860
+ // maxOpenPosAvailable: '0.00001203',
861
+ // maxTransferOut: '0.00001203',
862
+ // equity: '0.00001203',
863
+ // usdtEquity: '0.029092328738',
864
+ // coinDisplayName: 'ETH'
865
+ // }
866
+ // ],
867
+ // ts: 1728650777643
868
+ // }
869
+ //
870
+ const data = this.safeList(message, 'data', []);
871
+ for (let i = 0; i < data.length; i++) {
872
+ const rawBalance = data[i];
873
+ const currencyId = this.safeString2(rawBalance, 'coinName', 'marginCoin');
874
+ const code = this.safeCurrencyCode(currencyId);
875
+ const account = (code in this.balance) ? this.balance[code] : this.account();
876
+ const freeQuery = ('maxTransferOut' in rawBalance) ? 'maxTransferOut' : 'available';
877
+ account['free'] = this.safeString(rawBalance, freeQuery);
878
+ account['total'] = this.safeString(rawBalance, 'equity');
879
+ account['used'] = this.safeString(rawBalance, 'frozen');
880
+ this.balance[code] = account;
881
+ }
882
+ this.balance = this.safeBalance(this.balance);
883
+ const arg = this.safeDict(message, 'arg');
884
+ const instType = this.safeStringLower(arg, 'instType');
885
+ const messageHash = 'balance:' + instType;
886
+ client.resolve(this.balance, messageHash);
887
+ }
888
+ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
889
+ /**
890
+ * @method
891
+ * @name coincatch#watchOrders
892
+ * @description watches information on multiple orders made by the user
893
+ * @see https://coincatch.github.io/github.io/en/spot/#order-channel
894
+ * @see https://coincatch.github.io/github.io/en/mix/#order-channel
895
+ * @see https://coincatch.github.io/github.io/en/mix/#plan-order-channel
896
+ * @param {string} symbol unified market symbol of the market orders were made in
897
+ * @param {int} [since] the earliest time in ms to fetch orders for
898
+ * @param {int} [limit] the maximum number of order structures to retrieve
899
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
900
+ * @param {string} [params.type] 'spot' or 'swap'
901
+ * @param {string} [params.instType] *swap only* 'umcbl' or 'dmcbl' (default is 'umcbl')
902
+ * @param {bool} [params.trigger] *swap only* whether to watch trigger orders (default is false)
903
+ * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
904
+ */
905
+ const methodName = 'watchOrders';
906
+ await this.loadMarkets();
907
+ let market = undefined;
908
+ let marketId = undefined;
909
+ if (symbol !== undefined) {
910
+ market = this.market(symbol);
911
+ symbol = market['symbol'];
912
+ marketId = market['id'];
913
+ }
914
+ let marketType = undefined;
915
+ [marketType, params] = this.handleMarketTypeAndParams(methodName, market, params);
916
+ let instType = 'spbl';
917
+ let instId = marketId;
918
+ if (marketType === 'spot') {
919
+ if (symbol === undefined) {
920
+ throw new errors.ArgumentsRequired(this.id + ' ' + methodName + '() requires a symbol argument for ' + marketType + ' markets.');
921
+ }
922
+ }
923
+ else {
924
+ instId = 'default';
925
+ instType = 'umcbl';
926
+ if (symbol === undefined) {
927
+ [instType, params] = this.handleOptionAndParams(params, methodName, 'instType', instType);
928
+ }
929
+ else {
930
+ if (marketId.indexOf('_DMCBL') >= 0) {
931
+ instType = 'dmcbl';
932
+ }
933
+ }
934
+ }
935
+ let channel = 'orders';
936
+ const isTrigger = this.safeBool(params, 'trigger');
937
+ if (isTrigger) {
938
+ channel = 'ordersAlgo'; // channel does not return any data
939
+ params = this.omit(params, 'trigger');
940
+ }
941
+ const args = {
942
+ 'instType': instType,
943
+ 'channel': channel,
944
+ 'instId': instId,
945
+ };
946
+ let messageHash = 'orders';
947
+ if (symbol !== undefined) {
948
+ messageHash += ':' + symbol;
949
+ }
950
+ const orders = await this.watchPrivate(messageHash, messageHash, args, params);
951
+ if (this.newUpdates) {
952
+ limit = orders.getLimit(symbol, limit);
953
+ }
954
+ return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
955
+ }
956
+ handleOrder(client, message) {
957
+ //
958
+ // spot
959
+ //
960
+ // {
961
+ // action: 'snapshot',
962
+ // arg: { instType: 'spbl', channel: 'orders', instId: 'ETHUSDT_SPBL' },
963
+ // data: [
964
+ // {
965
+ // instId: 'ETHUSDT_SPBL',
966
+ // ordId: '1228627925964996608',
967
+ // clOrdId: 'f0cccf74-c535-4523-a53d-dbe3b9958559',
968
+ // px: '2000',
969
+ // sz: '0.001',
970
+ // notional: '2',
971
+ // ordType: 'limit',
972
+ // force: 'normal',
973
+ // side: 'buy',
974
+ // accFillSz: '0',
975
+ // avgPx: '0',
976
+ // status: 'new',
977
+ // cTime: 1728653645030,
978
+ // uTime: 1728653645030,
979
+ // orderFee: [],
980
+ // eps: 'API'
981
+ // }
982
+ // ],
983
+ // ts: 1728653645046
984
+ // }
985
+ //
986
+ // swap
987
+ //
988
+ // {
989
+ // action: 'snapshot',
990
+ // arg: { instType: 'umcbl', channel: 'orders', instId: 'default' },
991
+ // data: [
992
+ // {
993
+ // accFillSz: '0',
994
+ // cTime: 1728653796976,
995
+ // clOrdId: '1228628563272753152',
996
+ // eps: 'API',
997
+ // force: 'normal',
998
+ // hM: 'single_hold',
999
+ // instId: 'ETHUSDT_UMCBL',
1000
+ // lever: '5',
1001
+ // low: false,
1002
+ // notionalUsd: '20',
1003
+ // ordId: '1228628563188867072',
1004
+ // ordType: 'limit',
1005
+ // orderFee: [],
1006
+ // posSide: 'net',
1007
+ // px: '2000',
1008
+ // side: 'buy',
1009
+ // status: 'new',
1010
+ // sz: '0.01',
1011
+ // tS: 'buy_single',
1012
+ // tdMode: 'cross',
1013
+ // tgtCcy: 'USDT',
1014
+ // uTime: 1728653796976
1015
+ // }
1016
+ // ],
1017
+ // ts: 1728653797002
1018
+ // }
1019
+ //
1020
+ //
1021
+ const arg = this.safeDict(message, 'arg', {});
1022
+ const instType = this.safeString(arg, 'instType');
1023
+ const argInstId = this.safeString(arg, 'instId');
1024
+ let marketType = undefined;
1025
+ if (instType === 'spbl') {
1026
+ marketType = 'spot';
1027
+ }
1028
+ else {
1029
+ marketType = 'swap';
1030
+ }
1031
+ const data = this.safeList(message, 'data', []);
1032
+ if (this.orders === undefined) {
1033
+ const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
1034
+ this.orders = new Cache.ArrayCacheBySymbolById(limit);
1035
+ }
1036
+ const hash = 'orders';
1037
+ const stored = this.orders;
1038
+ let symbol = undefined;
1039
+ for (let i = 0; i < data.length; i++) {
1040
+ const order = data[i];
1041
+ const marketId = this.safeString(order, 'instId', argInstId);
1042
+ const market = this.safeMarket(marketId, undefined, undefined, marketType);
1043
+ const parsed = this.parseWsOrder(order, market);
1044
+ stored.append(parsed);
1045
+ symbol = parsed['symbol'];
1046
+ const messageHash = 'orders:' + symbol;
1047
+ client.resolve(stored, messageHash);
1048
+ }
1049
+ client.resolve(stored, hash);
1050
+ }
1051
+ parseWsOrder(order, market = undefined) {
1052
+ //
1053
+ // spot
1054
+ // {
1055
+ // instId: 'ETHUSDT_SPBL',
1056
+ // ordId: '1228627925964996608',
1057
+ // clOrdId: 'f0cccf74-c535-4523-a53d-dbe3b9958559',
1058
+ // px: '2000',
1059
+ // sz: '0.001',
1060
+ // notional: '2',
1061
+ // ordType: 'limit',
1062
+ // force: 'normal',
1063
+ // side: 'buy',
1064
+ // accFillSz: '0',
1065
+ // avgPx: '0',
1066
+ // status: 'new',
1067
+ // cTime: 1728653645030,
1068
+ // uTime: 1728653645030,
1069
+ // orderFee: orderFee: [ { fee: '0', feeCcy: 'USDT' } ],
1070
+ // eps: 'API'
1071
+ // }
1072
+ //
1073
+ // swap
1074
+ // {
1075
+ // accFillSz: '0',
1076
+ // cTime: 1728653796976,
1077
+ // clOrdId: '1228628563272753152',
1078
+ // eps: 'API',
1079
+ // force: 'normal',
1080
+ // hM: 'single_hold',
1081
+ // instId: 'ETHUSDT_UMCBL',
1082
+ // lever: '5',
1083
+ // low: false,
1084
+ // notionalUsd: '20',
1085
+ // ordId: '1228628563188867072',
1086
+ // ordType: 'limit',
1087
+ // orderFee: [ { fee: '0', feeCcy: 'USDT' } ],
1088
+ // posSide: 'net',
1089
+ // px: '2000',
1090
+ // side: 'buy',
1091
+ // status: 'new',
1092
+ // sz: '0.01',
1093
+ // tS: 'buy_single',
1094
+ // tdMode: 'cross',
1095
+ // tgtCcy: 'USDT',
1096
+ // uTime: 1728653796976
1097
+ // }
1098
+ //
1099
+ const marketId = this.safeString(order, 'instId');
1100
+ const settleId = this.safeString(order, 'tgtCcy');
1101
+ market = this.safeMarketCustom(marketId, market, settleId);
1102
+ const timestamp = this.safeInteger(order, 'cTime');
1103
+ const symbol = market['symbol'];
1104
+ const rawStatus = this.safeString(order, 'status');
1105
+ const orderFee = this.safeList(order, 'orderFee', []);
1106
+ const fee = this.safeDict(orderFee, 0);
1107
+ const feeCost = Precise["default"].stringMul(this.safeString(fee, 'fee'), '-1');
1108
+ const feeCurrency = this.safeString(fee, 'feeCcy');
1109
+ let price = this.omitZero(this.safeString(order, 'px'));
1110
+ const priceAvg = this.omitZero(this.safeString(order, 'avgPx'));
1111
+ if (price === undefined) {
1112
+ price = priceAvg;
1113
+ }
1114
+ const type = this.safeStringLower(order, 'ordType');
1115
+ return this.safeOrder({
1116
+ 'id': this.safeString(order, 'ordId'),
1117
+ 'clientOrderId': this.safeString(order, 'clOrdId'),
1118
+ 'timestamp': timestamp,
1119
+ 'datetime': this.iso8601(timestamp),
1120
+ 'lastTradeTimestamp': undefined,
1121
+ 'lastUpdateTimestamp': this.safeInteger(order, 'uTime'),
1122
+ 'status': this.parseOrderStatus(rawStatus),
1123
+ 'symbol': symbol,
1124
+ 'type': type,
1125
+ 'timeInForce': this.parseOrderTimeInForce(this.safeStringLower(order, 'force')),
1126
+ 'side': this.safeStringLower(order, 'side'),
1127
+ 'price': price,
1128
+ 'average': this.safeString(order, 'avgPx'),
1129
+ 'amount': this.safeString(order, 'sz'),
1130
+ 'filled': this.safeString(order, 'accFillSz'),
1131
+ 'remaining': undefined,
1132
+ 'triggerPrice': undefined,
1133
+ 'takeProfitPrice': undefined,
1134
+ 'stopLossPrice': undefined,
1135
+ 'cost': this.safeString(order, 'notional'),
1136
+ 'trades': undefined,
1137
+ 'fee': {
1138
+ 'currency': feeCurrency,
1139
+ 'cost': feeCost,
1140
+ },
1141
+ 'reduceOnly': this.safeBool(order, 'low'),
1142
+ 'postOnly': undefined,
1143
+ 'info': order,
1144
+ }, market);
1145
+ }
1146
+ async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) {
1147
+ /**
1148
+ * @method
1149
+ * @name coincatch#watchPositions
1150
+ * @description watch all open positions
1151
+ * @see https://coincatch.github.io/github.io/en/mix/#positions-channel
1152
+ * @param {string[]|undefined} symbols list of unified market symbols
1153
+ * @param {object} params extra parameters specific to the exchange API endpoint
1154
+ * @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
1155
+ */
1156
+ await this.loadMarkets();
1157
+ symbols = this.marketSymbols(symbols, 'swap');
1158
+ const messageHashes = [];
1159
+ const hash = 'positions';
1160
+ let instTypes = [];
1161
+ if (symbols !== undefined) {
1162
+ for (let i = 0; i < symbols.length; i++) {
1163
+ const symbol = symbols[i];
1164
+ const market = this.market(symbol);
1165
+ const instType = this.getPrivateInstType(market);
1166
+ if (!this.inArray(instType, instTypes)) {
1167
+ instTypes.push(instType);
1168
+ }
1169
+ messageHashes.push(hash + '::' + symbol);
1170
+ }
1171
+ }
1172
+ else {
1173
+ instTypes = ['umcbl', 'dmcbl'];
1174
+ messageHashes.push(hash);
1175
+ }
1176
+ const args = [];
1177
+ const subscribeHashes = [];
1178
+ for (let i = 0; i < instTypes.length; i++) {
1179
+ const instType = instTypes[i];
1180
+ const arg = {
1181
+ 'instType': instType,
1182
+ 'channel': hash,
1183
+ 'instId': 'default',
1184
+ };
1185
+ subscribeHashes.push(hash + '::' + instType);
1186
+ args.push(arg);
1187
+ }
1188
+ const newPositions = await this.watchPrivateMultiple(messageHashes, subscribeHashes, args, params);
1189
+ if (this.newUpdates) {
1190
+ return newPositions;
1191
+ }
1192
+ return this.filterBySymbolsSinceLimit(newPositions, symbols, since, limit, true);
1193
+ }
1194
+ getPrivateInstType(market) {
1195
+ const marketId = market['id'];
1196
+ if (marketId.indexOf('_DMCBL') >= 0) {
1197
+ return 'dmcbl';
1198
+ }
1199
+ return 'umcbl';
1200
+ }
1201
+ handlePositions(client, message) {
1202
+ //
1203
+ // {
1204
+ // action: 'snapshot',
1205
+ // arg: { instType: 'umcbl', channel: 'positions', instId: 'default' },
1206
+ // data: [
1207
+ // {
1208
+ // posId: '1221355728745619456',
1209
+ // instId: 'ETHUSDT_UMCBL',
1210
+ // instName: 'ETHUSDT',
1211
+ // marginCoin: 'USDT',
1212
+ // margin: '5.27182',
1213
+ // marginMode: 'crossed',
1214
+ // holdSide: 'long',
1215
+ // holdMode: 'single_hold',
1216
+ // total: '0.01',
1217
+ // available: '0.01',
1218
+ // locked: '0',
1219
+ // averageOpenPrice: '2635.91',
1220
+ // leverage: 5,
1221
+ // achievedProfits: '0',
1222
+ // upl: '-0.0267',
1223
+ // uplRate: '-0.005064664576',
1224
+ // liqPx: '-3110.66866033',
1225
+ // keepMarginRate: '0.0033',
1226
+ // marginRate: '0.002460827254',
1227
+ // cTime: '1726919818102',
1228
+ // uTime: '1728919604312',
1229
+ // markPrice: '2633.24',
1230
+ // autoMargin: 'off'
1231
+ // }
1232
+ // ],
1233
+ // ts: 1728919604329
1234
+ // }
1235
+ //
1236
+ if (this.positions === undefined) {
1237
+ this.positions = new Cache.ArrayCacheBySymbolBySide();
1238
+ }
1239
+ const cache = this.positions;
1240
+ const rawPositions = this.safeValue(message, 'data', []);
1241
+ const dataLength = rawPositions.length;
1242
+ if (dataLength === 0) {
1243
+ return;
1244
+ }
1245
+ const newPositions = [];
1246
+ const symbols = [];
1247
+ for (let i = 0; i < rawPositions.length; i++) {
1248
+ const rawPosition = rawPositions[i];
1249
+ const position = this.parseWsPosition(rawPosition);
1250
+ symbols.push(position['symbol']);
1251
+ newPositions.push(position);
1252
+ cache.append(position);
1253
+ }
1254
+ const hash = 'positions';
1255
+ const messageHashes = this.findMessageHashes(client, hash);
1256
+ for (let i = 0; i < messageHashes.length; i++) {
1257
+ const messageHash = messageHashes[i];
1258
+ const parts = messageHash.split('::');
1259
+ const symbol = parts[1];
1260
+ if (this.inArray(symbol, symbols)) {
1261
+ const positionsForSymbol = [];
1262
+ for (let j = 0; j < newPositions.length; j++) {
1263
+ const position = newPositions[j];
1264
+ if (position['symbol'] === symbol) {
1265
+ positionsForSymbol.push(position);
1266
+ }
1267
+ }
1268
+ client.resolve(positionsForSymbol, messageHash);
1269
+ }
1270
+ }
1271
+ client.resolve(newPositions, hash);
1272
+ }
1273
+ parseWsPosition(position, market = undefined) {
1274
+ //
1275
+ // {
1276
+ // posId: '1221355728745619456',
1277
+ // instId: 'ETHUSDT_UMCBL',
1278
+ // instName: 'ETHUSDT',
1279
+ // marginCoin: 'USDT',
1280
+ // margin: '5.27182',
1281
+ // marginMode: 'crossed',
1282
+ // holdSide: 'long',
1283
+ // holdMode: 'single_hold',
1284
+ // total: '0.01',
1285
+ // available: '0.01',
1286
+ // locked: '0',
1287
+ // averageOpenPrice: '2635.91',
1288
+ // leverage: 5,
1289
+ // achievedProfits: '0',
1290
+ // upl: '-0.0267',
1291
+ // uplRate: '-0.005064664576',
1292
+ // liqPx: '-3110.66866033',
1293
+ // keepMarginRate: '0.0033',
1294
+ // marginRate: '0.002460827254',
1295
+ // cTime: '1726919818102',
1296
+ // uTime: '1728919604312',
1297
+ // markPrice: '2633.24',
1298
+ // autoMargin: 'off'
1299
+ // }
1300
+ //
1301
+ const marketId = this.safeString(position, 'symbol');
1302
+ const settleId = this.safeString(position, 'marginCoin');
1303
+ market = this.safeMarketCustom(marketId, market, settleId);
1304
+ const timestamp = this.safeInteger(position, 'cTime');
1305
+ const marginModeId = this.safeString(position, 'marginMode');
1306
+ const marginMode = this.getSupportedMapping(marginModeId, {
1307
+ 'crossed': 'cross',
1308
+ 'isolated': 'isolated',
1309
+ });
1310
+ let isHedged = undefined;
1311
+ const holdMode = this.safeString(position, 'holdMode');
1312
+ if (holdMode === 'double_hold') {
1313
+ isHedged = true;
1314
+ }
1315
+ else if (holdMode === 'single_hold') {
1316
+ isHedged = false;
1317
+ }
1318
+ const percentageDecimal = this.safeString(position, 'uplRate');
1319
+ const percentage = Precise["default"].stringMul(percentageDecimal, '100');
1320
+ const margin = this.safeNumber(position, 'margin');
1321
+ return this.safePosition({
1322
+ 'symbol': market['symbol'],
1323
+ 'id': undefined,
1324
+ 'timestamp': timestamp,
1325
+ 'datetime': this.iso8601(timestamp),
1326
+ 'contracts': this.safeNumber(position, 'total'),
1327
+ 'contractSize': undefined,
1328
+ 'side': this.safeStringLower(position, 'holdSide'),
1329
+ 'notional': margin,
1330
+ 'leverage': this.safeInteger(position, 'leverage'),
1331
+ 'unrealizedPnl': this.safeNumber(position, 'upl'),
1332
+ 'realizedPnl': this.safeNumber(position, 'achievedProfits'),
1333
+ 'collateral': undefined,
1334
+ 'entryPrice': this.safeNumber(position, 'averageOpenPrice'),
1335
+ 'markPrice': this.safeNumber(position, 'markPrice'),
1336
+ 'liquidationPrice': this.safeNumber(position, 'liqPx'),
1337
+ 'marginMode': marginMode,
1338
+ 'hedged': isHedged,
1339
+ 'maintenanceMargin': undefined,
1340
+ 'maintenanceMarginPercentage': this.safeNumber(position, 'keepMarginRate'),
1341
+ 'initialMargin': margin,
1342
+ 'initialMarginPercentage': undefined,
1343
+ 'marginRatio': this.safeNumber(position, 'marginRate'),
1344
+ 'lastUpdateTimestamp': this.safeInteger(position, 'uTime'),
1345
+ 'lastPrice': undefined,
1346
+ 'stopLossPrice': undefined,
1347
+ 'takeProfitPrice': undefined,
1348
+ 'percentage': percentage,
1349
+ 'info': position,
1350
+ });
1351
+ }
1352
+ handleErrorMessage(client, message) {
1353
+ //
1354
+ // { event: "error", code: 30001, msg: "Channel does not exist" }
1355
+ //
1356
+ const event = this.safeString(message, 'event');
1357
+ try {
1358
+ if (event === 'error') {
1359
+ const code = this.safeString(message, 'code');
1360
+ const feedback = this.id + ' ' + this.json(message);
1361
+ this.throwExactlyMatchedException(this.exceptions['ws']['exact'], code, feedback);
1362
+ const msg = this.safeString(message, 'msg', '');
1363
+ this.throwBroadlyMatchedException(this.exceptions['ws']['broad'], msg, feedback);
1364
+ throw new errors.ExchangeError(feedback);
1365
+ }
1366
+ return false;
1367
+ }
1368
+ catch (e) {
1369
+ if (e instanceof errors.AuthenticationError) {
1370
+ const messageHash = 'authenticated';
1371
+ client.reject(e, messageHash);
1372
+ if (messageHash in client.subscriptions) {
1373
+ delete client.subscriptions[messageHash];
1374
+ }
1375
+ }
1376
+ else {
1377
+ client.reject(e);
1378
+ }
1379
+ return true;
1380
+ }
1381
+ }
1382
+ handleMessage(client, message) {
1383
+ // todo handle with subscribe and unsubscribe
1384
+ if (this.handleErrorMessage(client, message)) {
1385
+ return;
1386
+ }
1387
+ const content = this.safeString(message, 'message');
1388
+ if (content === 'pong') {
1389
+ this.handlePong(client, message);
1390
+ return;
1391
+ }
1392
+ if (message === 'pong') {
1393
+ this.handlePong(client, message);
1394
+ return;
1395
+ }
1396
+ const event = this.safeString(message, 'event');
1397
+ if (event === 'login') {
1398
+ this.handleAuthenticate(client, message);
1399
+ return;
1400
+ }
1401
+ if (event === 'subscribe') {
1402
+ this.handleSubscriptionStatus(client, message);
1403
+ return;
1404
+ }
1405
+ if (event === 'unsubscribe') {
1406
+ this.handleUnSubscriptionStatus(client, message);
1407
+ return;
1408
+ }
1409
+ const data = this.safeDict(message, 'arg', {});
1410
+ const channel = this.safeString(data, 'channel');
1411
+ if (channel === 'ticker') {
1412
+ this.handleTicker(client, message);
1413
+ }
1414
+ if (channel.indexOf('candle') >= 0) {
1415
+ this.handleOHLCV(client, message);
1416
+ }
1417
+ if (channel.indexOf('books') >= 0) {
1418
+ this.handleOrderBook(client, message);
1419
+ }
1420
+ if (channel === 'trade') {
1421
+ this.handleTrades(client, message);
1422
+ }
1423
+ if (channel === 'account') {
1424
+ this.handleBalance(client, message);
1425
+ }
1426
+ if ((channel === 'orders') || (channel === 'ordersAlgo')) {
1427
+ this.handleOrder(client, message);
1428
+ }
1429
+ if (channel === 'positions') {
1430
+ this.handlePositions(client, message);
1431
+ }
1432
+ }
1433
+ ping(client) {
1434
+ return 'ping';
1435
+ }
1436
+ handlePong(client, message) {
1437
+ client.lastPong = this.milliseconds();
1438
+ return message;
1439
+ }
1440
+ handleSubscriptionStatus(client, message) {
1441
+ return message;
1442
+ }
1443
+ handleUnSubscriptionStatus(client, message) {
1444
+ let argsList = this.safeList(message, 'args');
1445
+ if (argsList === undefined) {
1446
+ argsList = [this.safeDict(message, 'arg', {})];
1447
+ }
1448
+ for (let i = 0; i < argsList.length; i++) {
1449
+ const arg = argsList[i];
1450
+ const channel = this.safeString(arg, 'channel');
1451
+ if (channel === 'books') {
1452
+ this.handleOrderBookUnSubscription(client, message);
1453
+ }
1454
+ else if (channel === 'trade') {
1455
+ this.handleTradesUnSubscription(client, message);
1456
+ }
1457
+ else if (channel === 'ticker') {
1458
+ this.handleTickerUnSubscription(client, message);
1459
+ }
1460
+ else if (channel.startsWith('candle')) {
1461
+ this.handleOHLCVUnSubscription(client, message);
1462
+ }
1463
+ }
1464
+ return message;
1465
+ }
1466
+ handleOrderBookUnSubscription(client, message) {
1467
+ const arg = this.safeDict(message, 'arg', {});
1468
+ const instType = this.safeStringLower(arg, 'instType');
1469
+ const type = (instType === 'sp') ? 'spot' : 'swap';
1470
+ const instId = this.safeString(arg, 'instId');
1471
+ const market = this.safeMarket(instId, undefined, undefined, type);
1472
+ const symbol = market['symbol'];
1473
+ const messageHash = 'unsubscribe:orderbook:' + market['symbol'];
1474
+ const subMessageHash = 'orderbook:' + symbol;
1475
+ if (symbol in this.orderbooks) {
1476
+ delete this.orderbooks[symbol];
1477
+ }
1478
+ if (subMessageHash in client.subscriptions) {
1479
+ delete client.subscriptions[subMessageHash];
1480
+ }
1481
+ if (messageHash in client.subscriptions) {
1482
+ delete client.subscriptions[messageHash];
1483
+ }
1484
+ const error = new errors.UnsubscribeError(this.id + 'orderbook ' + symbol);
1485
+ client.reject(error, subMessageHash);
1486
+ client.resolve(true, messageHash);
1487
+ }
1488
+ handleTradesUnSubscription(client, message) {
1489
+ const arg = this.safeDict(message, 'arg', {});
1490
+ const instType = this.safeStringLower(arg, 'instType');
1491
+ const type = (instType === 'sp') ? 'spot' : 'swap';
1492
+ const instId = this.safeString(arg, 'instId');
1493
+ const market = this.safeMarket(instId, undefined, undefined, type);
1494
+ const symbol = market['symbol'];
1495
+ const messageHash = 'unsubscribe:trade:' + market['symbol'];
1496
+ const subMessageHash = 'trade:' + symbol;
1497
+ if (symbol in this.trades) {
1498
+ delete this.trades[symbol];
1499
+ }
1500
+ if (subMessageHash in client.subscriptions) {
1501
+ delete client.subscriptions[subMessageHash];
1502
+ }
1503
+ if (messageHash in client.subscriptions) {
1504
+ delete client.subscriptions[messageHash];
1505
+ }
1506
+ const error = new errors.UnsubscribeError(this.id + 'trades ' + symbol);
1507
+ client.reject(error, subMessageHash);
1508
+ client.resolve(true, messageHash);
1509
+ }
1510
+ handleTickerUnSubscription(client, message) {
1511
+ const arg = this.safeDict(message, 'arg', {});
1512
+ const instType = this.safeStringLower(arg, 'instType');
1513
+ const type = (instType === 'sp') ? 'spot' : 'swap';
1514
+ const instId = this.safeString(arg, 'instId');
1515
+ const market = this.safeMarket(instId, undefined, undefined, type);
1516
+ const symbol = market['symbol'];
1517
+ const messageHash = 'unsubscribe:ticker:' + market['symbol'];
1518
+ const subMessageHash = 'ticker:' + symbol;
1519
+ if (symbol in this.tickers) {
1520
+ delete this.tickers[symbol];
1521
+ }
1522
+ if (subMessageHash in client.subscriptions) {
1523
+ delete client.subscriptions[subMessageHash];
1524
+ }
1525
+ if (messageHash in client.subscriptions) {
1526
+ delete client.subscriptions[messageHash];
1527
+ }
1528
+ const error = new errors.UnsubscribeError(this.id + 'ticker ' + symbol);
1529
+ client.reject(error, subMessageHash);
1530
+ client.resolve(true, messageHash);
1531
+ }
1532
+ handleOHLCVUnSubscription(client, message) {
1533
+ const arg = this.safeDict(message, 'arg', {});
1534
+ const instType = this.safeStringLower(arg, 'instType');
1535
+ const type = (instType === 'sp') ? 'spot' : 'swap';
1536
+ const instId = this.safeString(arg, 'instId');
1537
+ const channel = this.safeString(arg, 'channel');
1538
+ const interval = channel.replace('candle', '');
1539
+ const timeframes = this.safeDict(this.options, 'timeframesForWs');
1540
+ const timeframe = this.findTimeframe(interval, timeframes);
1541
+ const market = this.safeMarket(instId, undefined, undefined, type);
1542
+ const symbol = market['symbol'];
1543
+ const messageHash = 'unsubscribe:ohlcv:' + timeframe + ':' + market['symbol'];
1544
+ const subMessageHash = 'ohlcv:' + symbol + ':' + timeframe;
1545
+ if (symbol in this.ohlcvs) {
1546
+ if (timeframe in this.ohlcvs[symbol]) {
1547
+ delete this.ohlcvs[symbol][timeframe];
1548
+ }
1549
+ }
1550
+ this.cleanUnsubscription(client, subMessageHash, messageHash);
1551
+ }
1552
+ }
1553
+
1554
+ module.exports = coincatch;