ccxt 4.2.26 → 4.2.28

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 (41) hide show
  1. package/README.md +6 -5
  2. package/dist/ccxt.browser.js +2382 -337
  3. package/dist/ccxt.browser.min.js +7 -7
  4. package/dist/cjs/ccxt.js +4 -1
  5. package/dist/cjs/src/abstract/coinmetro.js +9 -0
  6. package/dist/cjs/src/base/Exchange.js +3 -0
  7. package/dist/cjs/src/bingx.js +4 -0
  8. package/dist/cjs/src/bit2c.js +1 -0
  9. package/dist/cjs/src/bitbank.js +1 -0
  10. package/dist/cjs/src/bitbns.js +1 -0
  11. package/dist/cjs/src/bitfinex2.js +49 -1
  12. package/dist/cjs/src/bitflyer.js +1 -0
  13. package/dist/cjs/src/bitforex.js +29 -0
  14. package/dist/cjs/src/coinbase.js +6 -1
  15. package/dist/cjs/src/coinmetro.js +1909 -0
  16. package/dist/cjs/src/kucoinfutures.js +4 -0
  17. package/dist/cjs/src/upbit.js +21 -11
  18. package/js/ccxt.d.ts +5 -2
  19. package/js/ccxt.js +4 -2
  20. package/js/src/abstract/coinmetro.d.ts +36 -0
  21. package/js/src/abstract/coinmetro.js +11 -0
  22. package/js/src/base/Exchange.js +3 -0
  23. package/js/src/bingx.d.ts +4 -0
  24. package/js/src/bingx.js +4 -0
  25. package/js/src/bit2c.js +1 -0
  26. package/js/src/bitbank.js +1 -0
  27. package/js/src/bitbns.js +1 -0
  28. package/js/src/bitfinex2.d.ts +16 -0
  29. package/js/src/bitfinex2.js +49 -1
  30. package/js/src/bitflyer.js +1 -0
  31. package/js/src/bitforex.d.ts +1 -0
  32. package/js/src/bitforex.js +29 -0
  33. package/js/src/coinbase.js +6 -1
  34. package/js/src/coinmetro.d.ts +79 -0
  35. package/js/src/coinmetro.js +1910 -0
  36. package/js/src/kucoinfutures.d.ts +4 -0
  37. package/js/src/kucoinfutures.js +4 -0
  38. package/js/src/upbit.d.ts +3 -3
  39. package/js/src/upbit.js +21 -11
  40. package/package.json +1 -1
  41. package/skip-tests.json +7 -0
@@ -0,0 +1,1910 @@
1
+ // ----------------------------------------------------------------------------
2
+
3
+ // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
4
+ // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
5
+ // EDIT THE CORRESPONDENT .ts FILE INSTEAD
6
+
7
+ // ---------------------------------------------------------------------------
8
+ import Exchange from './abstract/coinmetro.js';
9
+ import { ArgumentsRequired, BadRequest, BadSymbol, InsufficientFunds, InvalidOrder, ExchangeError, OrderNotFound, PermissionDenied, RateLimitExceeded } from './base/errors.js';
10
+ import { DECIMAL_PLACES } from './base/functions/number.js';
11
+ import { Precise } from './base/Precise.js';
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * @class coinmetro
15
+ * @augments Exchange
16
+ */
17
+ export default class coinmetro extends Exchange {
18
+ describe() {
19
+ return this.deepExtend(super.describe(), {
20
+ 'id': 'coinmetro',
21
+ 'name': 'Coinmetro',
22
+ 'countries': ['EE'],
23
+ 'version': 'v1',
24
+ 'rateLimit': 200,
25
+ 'certified': false,
26
+ 'pro': false,
27
+ 'has': {
28
+ 'CORS': undefined,
29
+ 'spot': true,
30
+ 'margin': true,
31
+ 'swap': false,
32
+ 'future': false,
33
+ 'option': false,
34
+ 'addMargin': false,
35
+ 'borrowCrossMargin': true,
36
+ 'borrowIsolatedMargin': false,
37
+ 'cancelAllOrders': false,
38
+ 'cancelOrder': true,
39
+ 'cancelOrders': false,
40
+ 'closeAllPositions': false,
41
+ 'closePosition': true,
42
+ 'createDepositAddress': false,
43
+ 'createOrder': true,
44
+ 'createPostOnlyOrder': false,
45
+ 'createReduceOnlyOrder': false,
46
+ 'createStopLimitOrder': true,
47
+ 'createStopMarketOrder': true,
48
+ 'createStopOrder': true,
49
+ 'deposit': false,
50
+ 'editOrder': false,
51
+ 'fetchAccounts': false,
52
+ 'fetchBalance': true,
53
+ 'fetchBidsAsks': true,
54
+ 'fetchBorrowInterest': false,
55
+ 'fetchBorrowRateHistories': false,
56
+ 'fetchBorrowRateHistory': false,
57
+ 'fetchCanceledAndClosedOrders': true,
58
+ 'fetchCanceledOrders': false,
59
+ 'fetchClosedOrder': false,
60
+ 'fetchClosedOrders': false,
61
+ 'fetchCrossBorrowRate': false,
62
+ 'fetchCrossBorrowRates': false,
63
+ 'fetchCurrencies': true,
64
+ 'fetchDeposit': false,
65
+ 'fetchDepositAddress': false,
66
+ 'fetchDepositAddresses': false,
67
+ 'fetchDepositAddressesByNetwork': false,
68
+ 'fetchDeposits': false,
69
+ 'fetchDepositsWithdrawals': false,
70
+ 'fetchDepositWithdrawFee': false,
71
+ 'fetchDepositWithdrawFees': false,
72
+ 'fetchFundingHistory': false,
73
+ 'fetchFundingRate': false,
74
+ 'fetchFundingRateHistory': false,
75
+ 'fetchFundingRates': false,
76
+ 'fetchIndexOHLCV': false,
77
+ 'fetchIsolatedBorrowRate': false,
78
+ 'fetchIsolatedBorrowRates': false,
79
+ 'fetchL3OrderBook': false,
80
+ 'fetchLedger': true,
81
+ 'fetchLeverage': false,
82
+ 'fetchLeverageTiers': false,
83
+ 'fetchMarketLeverageTiers': false,
84
+ 'fetchMarkets': true,
85
+ 'fetchMarkOHLCV': false,
86
+ 'fetchMyTrades': true,
87
+ 'fetchOHLCV': true,
88
+ 'fetchOpenInterestHistory': false,
89
+ 'fetchOpenOrder': false,
90
+ 'fetchOpenOrders': true,
91
+ 'fetchOrder': true,
92
+ 'fetchOrderBook': true,
93
+ 'fetchOrderBooks': false,
94
+ 'fetchOrders': false,
95
+ 'fetchOrderTrades': false,
96
+ 'fetchPosition': false,
97
+ 'fetchPositions': false,
98
+ 'fetchPositionsRisk': false,
99
+ 'fetchPremiumIndexOHLCV': false,
100
+ 'fetchStatus': false,
101
+ 'fetchTicker': false,
102
+ 'fetchTickers': true,
103
+ 'fetchTime': false,
104
+ 'fetchTrades': true,
105
+ 'fetchTradingFee': false,
106
+ 'fetchTradingFees': false,
107
+ 'fetchTradingLimits': false,
108
+ 'fetchTransactionFee': false,
109
+ 'fetchTransactionFees': false,
110
+ 'fetchTransactions': false,
111
+ 'fetchTransfers': false,
112
+ 'fetchWithdrawal': false,
113
+ 'fetchWithdrawals': false,
114
+ 'fetchWithdrawalWhitelist': false,
115
+ 'reduceMargin': false,
116
+ 'repayCrossMargin': false,
117
+ 'repayIsolatedMargin': false,
118
+ 'setLeverage': false,
119
+ 'setMargin': false,
120
+ 'setMarginMode': false,
121
+ 'setPositionMode': false,
122
+ 'signIn': false,
123
+ 'transfer': false,
124
+ 'withdraw': false,
125
+ 'ws': false,
126
+ },
127
+ 'timeframes': {
128
+ '1m': '60000',
129
+ '5m': '300000',
130
+ '30m': '1800000',
131
+ '4h': '14400000',
132
+ '1d': '86400000',
133
+ },
134
+ 'urls': {
135
+ 'logo': 'https://github.com/ccxt/ccxt/assets/43336371/e86f87ec-6ba3-4410-962b-f7988c5db539',
136
+ 'api': {
137
+ 'public': 'https://api.coinmetro.com',
138
+ 'private': 'https://api.coinmetro.com',
139
+ },
140
+ 'test': {
141
+ 'public': 'https://api.coinmetro.com',
142
+ 'private': 'https://api.coinmetro.com/open',
143
+ },
144
+ 'www': 'https://coinmetro.com/',
145
+ 'doc': [
146
+ 'https://documenter.getpostman.com/view/3653795/SVfWN6KS',
147
+ ],
148
+ 'fees': 'https://help.coinmetro.com/hc/en-gb/articles/6844007317789-What-are-the-fees-on-Coinmetro-',
149
+ 'referral': 'https://go.coinmetro.com/?ref=crypto24',
150
+ },
151
+ 'api': {
152
+ 'public': {
153
+ 'get': {
154
+ 'demo/temp': 1,
155
+ 'exchange/candles/{pair}/{timeframe}/{from}/{to}': 3,
156
+ 'exchange/prices': 1,
157
+ 'exchange/ticks/{pair}/{from}': 3,
158
+ 'assets': 1,
159
+ 'markets': 1,
160
+ 'exchange/book/{pair}': 3,
161
+ 'exchange/bookUpdates/{pair}/{from}': 1, // not unified
162
+ },
163
+ },
164
+ 'private': {
165
+ 'get': {
166
+ 'users/balances': 1,
167
+ 'users/wallets/history/{since}': 1.67,
168
+ 'exchange/orders/status/{orderID}': 1,
169
+ 'exchange/orders/active': 1,
170
+ 'exchange/orders/history/{since}': 1.67,
171
+ 'exchange/fills/{since}': 1.67,
172
+ 'exchange/margin': 1, // not unified
173
+ },
174
+ 'post': {
175
+ 'jwt': 1,
176
+ 'jwtDevice': 1,
177
+ 'devices': 1,
178
+ 'jwt-read-only': 1,
179
+ 'exchange/orders/create': 1,
180
+ 'exchange/orders/modify/{orderID}': 1,
181
+ 'exchange/swap': 1,
182
+ 'exchange/swap/confirm/{swapId}': 1,
183
+ 'exchange/orders/close/{orderID}': 1,
184
+ 'exchange/orders/hedge': 1, // not unified
185
+ },
186
+ 'put': {
187
+ 'jwt': 1,
188
+ 'exchange/orders/cancel/{orderID}': 1,
189
+ 'users/margin/collateral': 1,
190
+ 'users/margin/primary/{currency}': 1, // not unified
191
+ },
192
+ },
193
+ },
194
+ 'requiredCredentials': {
195
+ 'apiKey': false,
196
+ 'secret': false,
197
+ 'uid': true,
198
+ 'token': true,
199
+ },
200
+ 'fees': {
201
+ 'trading': {
202
+ 'feeSide': 'get',
203
+ 'tierBased': false,
204
+ 'percentage': true,
205
+ 'taker': this.parseNumber('0.001'),
206
+ 'maker': this.parseNumber('0'),
207
+ },
208
+ },
209
+ 'precisionMode': DECIMAL_PLACES,
210
+ // exchange-specific options
211
+ 'options': {
212
+ 'currenciesByIdForParseMarket': undefined,
213
+ 'currencyIdsListForParseMarket': undefined,
214
+ },
215
+ 'exceptions': {
216
+ // https://trade-docs.coinmetro.co/?javascript--nodejs#message-codes
217
+ 'exact': {
218
+ 'Both buyingCurrency and sellingCurrency are required': InvalidOrder,
219
+ 'One and only one of buyingQty and sellingQty is required': InvalidOrder,
220
+ 'Invalid buyingCurrency': InvalidOrder,
221
+ 'Invalid \'from\'': BadRequest,
222
+ 'Invalid sellingCurrency': InvalidOrder,
223
+ 'Invalid buyingQty': InvalidOrder,
224
+ 'Invalid sellingQty': InvalidOrder,
225
+ 'Insufficient balance': InsufficientFunds,
226
+ 'Expiration date is in the past or too near in the future': InvalidOrder,
227
+ 'Forbidden': PermissionDenied,
228
+ 'Order Not Found': OrderNotFound,
229
+ 'since must be a millisecond timestamp': BadRequest,
230
+ 'This pair is disabled on margin': BadSymbol, // 422 Unprocessable Entity {"message":"This pair is disabled on margin"}
231
+ },
232
+ 'broad': {
233
+ 'accessing from a new IP': PermissionDenied,
234
+ 'available to allocate as collateral': InsufficientFunds,
235
+ 'At least': BadRequest,
236
+ 'collateral is not allowed': BadRequest,
237
+ 'Insufficient liquidity': InvalidOrder,
238
+ 'Insufficient order size': InvalidOrder,
239
+ 'Invalid quantity': InvalidOrder,
240
+ 'Invalid Stop Loss': InvalidOrder,
241
+ 'Invalid stop price!': InvalidOrder,
242
+ 'Not enough balance': InsufficientFunds,
243
+ 'Not enough margin': InsufficientFunds,
244
+ 'orderType missing': BadRequest,
245
+ 'Server Timeout': ExchangeError,
246
+ 'Time in force has to be IOC or FOK for market orders': InvalidOrder,
247
+ 'Too many attempts': RateLimitExceeded, // 429 Too Many Requests {"message":"Too many attempts. Try again in 3 seconds"}
248
+ },
249
+ },
250
+ });
251
+ }
252
+ async fetchCurrencies(params = {}) {
253
+ /**
254
+ * @method
255
+ * @name coinmetro#fetchCurrencies
256
+ * @description fetches all available currencies on an exchange
257
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#d5876d43-a3fe-4479-8c58-24d0f044edfb
258
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
259
+ * @returns {object} an associative dictionary of currencies
260
+ */
261
+ const response = await this.publicGetAssets(params);
262
+ //
263
+ // [
264
+ // {
265
+ // "symbol": "BTC",
266
+ // "name": "Bitcoin",
267
+ // "color": "#FFA500",
268
+ // "type": "coin",
269
+ // "canDeposit": true,
270
+ // "canWithdraw": true,
271
+ // "canTrade": true,
272
+ // "notabeneDecimals": 8,
273
+ // "canMarket": true,
274
+ // "maxSwap": 10000,
275
+ // "digits": 6,
276
+ // "multiplier": 1000000,
277
+ // "bookDigits": 8,
278
+ // "bookMultiplier": 100000000,
279
+ // "sentimentData": {
280
+ // "sentiment": 51.59555555555555,
281
+ // "interest": 1.127511216044664
282
+ // },
283
+ // "minQty": 0.0001
284
+ // },
285
+ // {
286
+ // "symbol": "EUR",
287
+ // "name": "Euro",
288
+ // "color": "#1246FF",
289
+ // "type": "fiat",
290
+ // "canDeposit": true,
291
+ // "canWithdraw": true,
292
+ // "canTrade": true,
293
+ // "canMarket": true,
294
+ // "maxSwap": 10000,
295
+ // "digits": 2,
296
+ // "multiplier": 100,
297
+ // "bookDigits": 3,
298
+ // "bookMultiplier": 1000,
299
+ // "minQty": 5
300
+ // }
301
+ // ...
302
+ // ]
303
+ //
304
+ const result = {};
305
+ for (let i = 0; i < response.length; i++) {
306
+ const currency = response[i];
307
+ const id = this.safeString(currency, 'symbol');
308
+ const code = this.safeCurrencyCode(id);
309
+ const withdraw = this.safeValue(currency, 'canWithdraw');
310
+ const deposit = this.safeValue(currency, 'canDeposit');
311
+ const canTrade = this.safeValue(currency, 'canTrade');
312
+ const active = canTrade ? withdraw : true;
313
+ const precision = this.safeInteger(currency, 'digits');
314
+ const minAmount = this.safeNumber(currency, 'minQty');
315
+ result[code] = this.safeCurrencyStructure({
316
+ 'id': id,
317
+ 'code': code,
318
+ 'name': code,
319
+ 'info': currency,
320
+ 'active': active,
321
+ 'deposit': deposit,
322
+ 'withdraw': withdraw,
323
+ 'fee': undefined,
324
+ 'precision': precision,
325
+ 'limits': {
326
+ 'amount': { 'min': minAmount, 'max': undefined },
327
+ 'withdraw': { 'min': undefined, 'max': undefined },
328
+ },
329
+ 'networks': {},
330
+ });
331
+ }
332
+ if (this.safeValue(this.options, 'currenciesByIdForParseMarket') === undefined) {
333
+ const currenciesById = this.indexBy(result, 'id');
334
+ this.options['currenciesByIdForParseMarket'] = currenciesById;
335
+ this.options['currencyIdsListForParseMarket'] = Object.keys(currenciesById);
336
+ }
337
+ return result;
338
+ }
339
+ async fetchMarkets(params = {}) {
340
+ /**
341
+ * @method
342
+ * @name coinmetro#fetchMarkets
343
+ * @description retrieves data on all markets for coinmetro
344
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#9fd18008-338e-4863-b07d-722878a46832
345
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
346
+ * @returns {object[]} an array of objects representing market data
347
+ */
348
+ const response = await this.publicGetMarkets(params);
349
+ if (this.safeValue(this.options, 'currenciesByIdForParseMarket') === undefined) {
350
+ await this.fetchCurrencies();
351
+ }
352
+ //
353
+ // [
354
+ // {
355
+ // "pair": "PERPEUR",
356
+ // "precision": 5,
357
+ // "margin": false
358
+ // },
359
+ // {
360
+ // "pair": "PERPUSD",
361
+ // "precision": 5,
362
+ // "margin": false
363
+ // },
364
+ // {
365
+ // "pair": "YFIEUR",
366
+ // "precision": 5,
367
+ // "margin": false
368
+ // },
369
+ // ...
370
+ // ]
371
+ //
372
+ return this.parseMarkets(response);
373
+ }
374
+ parseMarket(market) {
375
+ const id = this.safeString(market, 'pair');
376
+ const parsedMarketId = this.parseMarketId(id);
377
+ const baseId = this.safeString(parsedMarketId, 'baseId');
378
+ const quoteId = this.safeString(parsedMarketId, 'quoteId');
379
+ const base = this.safeCurrencyCode(baseId);
380
+ const quote = this.safeCurrencyCode(quoteId);
381
+ const basePrecisionAndLimits = this.parseMarketPrecisionAndLimits(baseId);
382
+ const quotePrecisionAndLimits = this.parseMarketPrecisionAndLimits(quoteId);
383
+ const margin = this.safeValue(market, 'margin', false);
384
+ const tradingFees = this.safeValue(this.fees, 'trading', {});
385
+ return this.safeMarketStructure({
386
+ 'id': id,
387
+ 'symbol': base + '/' + quote,
388
+ 'base': base,
389
+ 'quote': quote,
390
+ 'settle': undefined,
391
+ 'baseId': baseId,
392
+ 'quoteId': quoteId,
393
+ 'settleId': undefined,
394
+ 'type': 'spot',
395
+ 'spot': true,
396
+ 'margin': margin,
397
+ 'swap': false,
398
+ 'future': false,
399
+ 'option': false,
400
+ 'active': true,
401
+ 'contract': false,
402
+ 'linear': undefined,
403
+ 'inverse': undefined,
404
+ 'taker': this.safeNumber(tradingFees, 'taker'),
405
+ 'maker': this.safeNumber(tradingFees, 'maker'),
406
+ 'contractSize': undefined,
407
+ 'expiry': undefined,
408
+ 'expiryDatetime': undefined,
409
+ 'strike': undefined,
410
+ 'optionType': undefined,
411
+ 'precision': {
412
+ 'amount': basePrecisionAndLimits['precision'],
413
+ 'price': quotePrecisionAndLimits['precision'],
414
+ 'base': basePrecisionAndLimits['precision'],
415
+ 'quote': quotePrecisionAndLimits['precision'],
416
+ },
417
+ 'limits': {
418
+ 'leverage': {
419
+ 'min': undefined,
420
+ 'max': undefined,
421
+ },
422
+ 'amount': {
423
+ 'min': basePrecisionAndLimits['minLimit'],
424
+ 'max': undefined,
425
+ },
426
+ 'price': {
427
+ 'min': undefined,
428
+ 'max': undefined,
429
+ },
430
+ 'cost': {
431
+ 'min': quotePrecisionAndLimits['minLimit'],
432
+ 'max': undefined,
433
+ },
434
+ },
435
+ 'created': undefined,
436
+ 'info': market,
437
+ });
438
+ }
439
+ parseMarketId(marketId) {
440
+ let baseId = undefined;
441
+ let quoteId = undefined;
442
+ const currencyIds = this.safeValue(this.options, 'currencyIdsListForParseMarket', []);
443
+ for (let i = 0; i < currencyIds.length; i++) {
444
+ const currencyId = currencyIds[i];
445
+ const entryIndex = marketId.indexOf(currencyId);
446
+ if (entryIndex !== -1) {
447
+ const restId = marketId.replace(currencyId, '');
448
+ if (this.inArray(restId, currencyIds)) {
449
+ if (entryIndex === 0) {
450
+ baseId = currencyId;
451
+ quoteId = restId;
452
+ }
453
+ else {
454
+ baseId = restId;
455
+ quoteId = currencyId;
456
+ }
457
+ break;
458
+ }
459
+ }
460
+ }
461
+ const result = {
462
+ 'baseId': baseId,
463
+ 'quoteId': quoteId,
464
+ };
465
+ return result;
466
+ }
467
+ parseMarketPrecisionAndLimits(currencyId) {
468
+ const currencies = this.safeValue(this.options, 'currenciesByIdForParseMarket', {});
469
+ const currency = this.safeValue(currencies, currencyId, {});
470
+ const precision = this.safeInteger(currency, 'precision');
471
+ const limits = this.safeValue(currency, 'limits', {});
472
+ const amountLimits = this.safeValue(limits, 'amount', {});
473
+ const minLimit = this.safeNumber(amountLimits, 'min');
474
+ const result = {
475
+ 'precision': precision,
476
+ 'minLimit': minLimit,
477
+ };
478
+ return result;
479
+ }
480
+ async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
481
+ /**
482
+ * @method
483
+ * @name coinmetro#fetchOHLCV
484
+ * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
485
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#13cfb5bc-7bfb-4847-85e1-e0f35dfb3573
486
+ * @param {string} symbol unified symbol of the market to fetch OHLCV data for
487
+ * @param {string} timeframe the length of time each candle represents
488
+ * @param {int} [since] timestamp in ms of the earliest candle to fetch
489
+ * @param {int} [limit] the maximum amount of candles to fetch
490
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
491
+ * @param {int} [params.until] the latest time in ms to fetch entries for
492
+ * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
493
+ */
494
+ await this.loadMarkets();
495
+ const market = this.market(symbol);
496
+ const request = {
497
+ 'pair': market['id'],
498
+ 'timeframe': this.safeString(this.timeframes, timeframe, timeframe),
499
+ };
500
+ let until = undefined;
501
+ if (since !== undefined) {
502
+ request['from'] = since;
503
+ if (limit !== undefined) {
504
+ const duration = this.parseTimeframe(timeframe) * 1000;
505
+ until = this.sum(since, duration * (limit));
506
+ }
507
+ }
508
+ else {
509
+ request['from'] = ':from'; // this endpoint doesn't accept empty from and to params (setting them into the value described in the documentation)
510
+ }
511
+ until = this.safeInteger2(params, 'till', 'until', until);
512
+ if (until !== undefined) {
513
+ params = this.omit(params, ['till', 'until']);
514
+ request['to'] = until;
515
+ }
516
+ else {
517
+ request['to'] = ':to';
518
+ }
519
+ const response = await this.publicGetExchangeCandlesPairTimeframeFromTo(this.extend(request, params));
520
+ //
521
+ // {
522
+ // "candleHistory": [
523
+ // {
524
+ // "pair": "ETHUSDT",
525
+ // "timeframe": 86400000,
526
+ // "timestamp": 1697673600000,
527
+ // "c": 1567.4409353098604,
528
+ // "h": 1566.7514068472303,
529
+ // "l": 1549.4563666936847,
530
+ // "o": 1563.4490341395904,
531
+ // "v": 0
532
+ // },
533
+ // {
534
+ // "pair": "ETHUSDT",
535
+ // "timeframe": 86400000,
536
+ // "timestamp": 1697760000000,
537
+ // "c": 1603.7831363339324,
538
+ // "h": 1625.0356823666407,
539
+ // "l": 1565.4629390011505,
540
+ // "o": 1566.8387619426028,
541
+ // "v": 0
542
+ // },
543
+ // ...
544
+ // ]
545
+ // }
546
+ //
547
+ const candleHistory = this.safeValue(response, 'candleHistory', []);
548
+ return this.parseOHLCVs(candleHistory, market, timeframe, since, limit);
549
+ }
550
+ parseOHLCV(ohlcv, market = undefined) {
551
+ return [
552
+ this.safeInteger(ohlcv, 'timestamp'),
553
+ this.safeNumber(ohlcv, 'o'),
554
+ this.safeNumber(ohlcv, 'h'),
555
+ this.safeNumber(ohlcv, 'l'),
556
+ this.safeNumber(ohlcv, 'c'),
557
+ this.safeNumber(ohlcv, 'v'),
558
+ ];
559
+ }
560
+ async fetchTrades(symbol, since = undefined, limit = undefined, params = {}) {
561
+ /**
562
+ * @method
563
+ * @name coinmetro#fetchTrades
564
+ * @description get the list of most recent trades for a particular symbol
565
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#6ee5d698-06da-4570-8c84-914185e05065
566
+ * @param {string} symbol unified symbol of the market to fetch trades for
567
+ * @param {int} [since] timestamp in ms of the earliest trade to fetch
568
+ * @param {int} [limit] the maximum amount of trades to fetch (default 200, max 500)
569
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
570
+ * @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
571
+ */
572
+ await this.loadMarkets();
573
+ const market = this.market(symbol);
574
+ const request = {
575
+ 'pair': market['id'],
576
+ };
577
+ if (since !== undefined) {
578
+ request['from'] = since;
579
+ }
580
+ else {
581
+ // this endpoint accepts empty from param
582
+ request['from'] = '';
583
+ }
584
+ const response = await this.publicGetExchangeTicksPairFrom(this.extend(request, params));
585
+ //
586
+ // {
587
+ // "tickHistory": [
588
+ // {
589
+ // "pair": "ETHUSDT",
590
+ // "price": 2077.5623,
591
+ // "qty": 0.002888,
592
+ // "timestamp": 1700684689420,
593
+ // "seqNum": 10644554718
594
+ // },
595
+ // {
596
+ // "pair": "ETHUSDT",
597
+ // "price": 2078.3848,
598
+ // "qty": 0.003368,
599
+ // "timestamp": 1700684738410,
600
+ // "seqNum": 10644559561
601
+ // },
602
+ // {
603
+ // "pair": "ETHUSDT",
604
+ // "price": 2077.1513,
605
+ // "qty": 0.00337,
606
+ // "timestamp": 1700684816853,
607
+ // "seqNum": 10644567113
608
+ // },
609
+ // ...
610
+ // ]
611
+ // }
612
+ //
613
+ const tickHistory = this.safeValue(response, 'tickHistory', []);
614
+ return this.parseTrades(tickHistory, market, since, limit);
615
+ }
616
+ async fetchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
617
+ /**
618
+ * @method
619
+ * @name coinmetro#fetchMyTrades
620
+ * @description fetch all trades made by the user
621
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#4d48ae69-8ee2-44d1-a268-71f84e557b7b
622
+ * @param {string} symbol unified market symbol
623
+ * @param {int} [since] the earliest time in ms to fetch trades for
624
+ * @param {int} [limit] the maximum number of trades structures to retrieve (default 500, max 1000)
625
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
626
+ * @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure}
627
+ */
628
+ await this.loadMarkets();
629
+ let market = undefined;
630
+ if (symbol !== undefined) {
631
+ market = this.market(symbol);
632
+ }
633
+ const request = {};
634
+ if (since !== undefined) {
635
+ request['since'] = since;
636
+ }
637
+ else {
638
+ // the exchange requires a value for the since param
639
+ request['since'] = 0;
640
+ }
641
+ const response = await this.privateGetExchangeFillsSince(this.extend(request, params));
642
+ //
643
+ // [
644
+ // {
645
+ // "pair": "ETHUSDC",
646
+ // "seqNumber": 10873722343,
647
+ // "timestamp": 1702570610747,
648
+ // "qty": 0.002,
649
+ // "price": 2282,
650
+ // "side": "buy",
651
+ // "orderID": "65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c"
652
+ // },
653
+ // ...
654
+ // ]
655
+ //
656
+ return this.parseTrades(response, market, since, limit);
657
+ }
658
+ parseTrade(trade, market = undefined) {
659
+ //
660
+ // fetchTrades
661
+ // {
662
+ // "pair": "ETHUSDT",
663
+ // "price": 2077.1513,
664
+ // "qty": 0.00337,
665
+ // "timestamp": 1700684816853,
666
+ // "seqNum": 10644567113
667
+ // },
668
+ //
669
+ // fetchMyTrades
670
+ // {
671
+ // "pair": "ETHUSDC",
672
+ // "seqNumber": 10873722343,
673
+ // "timestamp": 1702570610747,
674
+ // "qty": 0.002,
675
+ // "price": 2282,
676
+ // "side": "buy",
677
+ // "orderID": "65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c"
678
+ // }
679
+ //
680
+ // fetchOrders
681
+ // {
682
+ // "_id": "657b31d360a9542449381bdc",
683
+ // "seqNumber": 10873722343,
684
+ // "timestamp": 1702570610747,
685
+ // "qty": 0.002,
686
+ // "price": 2282,
687
+ // "side": "buy"
688
+ // }
689
+ //
690
+ // {
691
+ // "pair":"ETHUSDC",
692
+ // "seqNumber":"10873722343",
693
+ // "timestamp":"1702570610747",
694
+ // "qty":"0.002",
695
+ // "price":"2282",
696
+ // "side":"buy",
697
+ // "orderID":"65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c",
698
+ // "userID":"65671262d93d9525ac009e36"
699
+ // }
700
+ //
701
+ const marketId = this.safeString2(trade, 'symbol', 'pair');
702
+ market = this.safeMarket(marketId, market);
703
+ const symbol = market['symbol'];
704
+ const id = this.safeStringN(trade, ['_id', 'seqNum', 'seqNumber']);
705
+ const timestamp = this.safeInteger(trade, 'timestamp');
706
+ const priceString = this.safeString(trade, 'price');
707
+ const amountString = this.safeString(trade, 'qty');
708
+ const order = this.safeString(trade, 'orderID');
709
+ const side = this.safeString(trade, 'side');
710
+ return this.safeTrade({
711
+ 'id': id,
712
+ 'order': order,
713
+ 'timestamp': timestamp,
714
+ 'datetime': this.iso8601(timestamp),
715
+ 'symbol': symbol,
716
+ 'type': undefined,
717
+ 'side': side,
718
+ 'takerOrMaker': undefined,
719
+ 'price': priceString,
720
+ 'amount': amountString,
721
+ 'cost': undefined,
722
+ 'fee': undefined,
723
+ 'info': trade,
724
+ }, market);
725
+ }
726
+ async fetchOrderBook(symbol, limit = undefined, params = {}) {
727
+ /**
728
+ * @method
729
+ * @name coinmetro#fetchOrderBook
730
+ * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
731
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#26ad80d7-8c46-41b5-9208-386f439a8b87
732
+ * @param {string} symbol unified symbol of the market to fetch the order book for
733
+ * @param {int} [limit] the maximum amount of order book entries to return (default 100, max 200)
734
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
735
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
736
+ */
737
+ await this.loadMarkets();
738
+ const market = this.market(symbol);
739
+ const request = {
740
+ 'pair': market['id'],
741
+ };
742
+ const response = await this.publicGetExchangeBookPair(this.extend(request, params));
743
+ //
744
+ // {
745
+ // "book": {
746
+ // "pair": "ETHUSDT",
747
+ // "seqNumber": 10800409239,
748
+ // "ask": {
749
+ // "2354.2861": 3.75,
750
+ // "2354.3138": 19,
751
+ // "2354.7538": 80,
752
+ // "2355.5430": 260,
753
+ // "2356.4611": 950,
754
+ // "2361.7150": 1500,
755
+ // "206194.0000": 0.01
756
+ // },
757
+ // "bid": {
758
+ // "2352.6339": 3.75,
759
+ // "2352.6002": 19,
760
+ // "2352.2402": 80,
761
+ // "2351.4582": 260,
762
+ // "2349.3111": 950,
763
+ // "2343.8601": 1500,
764
+ // "1.0000": 5
765
+ // },
766
+ // "checksum": 2108177337
767
+ // }
768
+ // }
769
+ //
770
+ const book = this.safeValue(response, 'book', {});
771
+ const rawBids = this.safeValue(book, 'bid', {});
772
+ const rawAsks = this.safeValue(book, 'ask', {});
773
+ const rawOrderbook = {
774
+ 'bids': rawBids,
775
+ 'asks': rawAsks,
776
+ };
777
+ const orderbook = this.parseOrderBook(rawOrderbook, symbol);
778
+ orderbook['nonce'] = this.safeInteger(book, 'seqNumber');
779
+ return orderbook;
780
+ }
781
+ parseBidsAsks(bidasks, priceKey = 0, amountKey = 1, countOrIdKey = 2) {
782
+ const prices = Object.keys(bidasks);
783
+ const result = [];
784
+ for (let i = 0; i < prices.length; i++) {
785
+ const priceString = this.safeString(prices, i);
786
+ const price = this.safeNumber(prices, i);
787
+ const volume = this.safeNumber(bidasks, priceString);
788
+ result.push([price, volume]);
789
+ }
790
+ return result;
791
+ }
792
+ async fetchTickers(symbols = undefined, params = {}) {
793
+ /**
794
+ * @method
795
+ * @name coinmetro#fetchTickers
796
+ * @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
797
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#6ecd1cd1-f162-45a3-8b3b-de690332a485
798
+ * @param {string[]} [symbols] unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
799
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
800
+ * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
801
+ */
802
+ await this.loadMarkets();
803
+ const response = await this.publicGetExchangePrices(params);
804
+ //
805
+ // {
806
+ // "latestPrices": [
807
+ // {
808
+ // "pair": "PERPEUR",
809
+ // "timestamp": 1702549840393,
810
+ // "price": 0.7899997816001223,
811
+ // "qty": 1e-12,
812
+ // "ask": 0.8,
813
+ // "bid": 0.7799995632002446
814
+ // },
815
+ // {
816
+ // "pair": "PERPUSD",
817
+ // "timestamp": 1702549841973,
818
+ // "price": 0.8615317721366659,
819
+ // "qty": 1e-12,
820
+ // "ask": 0.8742333599999257,
821
+ // "bid": 0.8490376365388491
822
+ // },
823
+ // ...
824
+ // ],
825
+ // "24hInfo": [
826
+ // {
827
+ // "delta": 0.25396444229149906,
828
+ // "h": 0.78999978160012,
829
+ // "l": 0.630001740844,
830
+ // "v": 54.910000002833996,
831
+ // "pair": "PERPEUR",
832
+ // "sentimentData": {
833
+ // "sentiment": 36.71333333333333,
834
+ // "interest": 0.47430830039525695
835
+ // }
836
+ // },
837
+ // {
838
+ // "delta": 0.26915154078134096,
839
+ // "h": 0.86220315458898,
840
+ // "l": 0.67866757035154,
841
+ // "v": 2.835000000000001e-9,
842
+ // "pair": "PERPUSD",
843
+ // "sentimentData": {
844
+ // "sentiment": 36.71333333333333,
845
+ // "interest": 0.47430830039525695
846
+ // }
847
+ // },
848
+ // ...
849
+ // ]
850
+ // }
851
+ //
852
+ const latestPrices = this.safeValue(response, 'latestPrices', []);
853
+ const twentyFourHInfos = this.safeValue(response, '24hInfo', []);
854
+ const tickersObject = {};
855
+ // merging info from two lists into one
856
+ for (let i = 0; i < latestPrices.length; i++) {
857
+ const latestPrice = latestPrices[i];
858
+ const marketId = this.safeString(latestPrice, 'pair');
859
+ if (marketId !== undefined) {
860
+ tickersObject[marketId] = latestPrice;
861
+ }
862
+ }
863
+ for (let i = 0; i < twentyFourHInfos.length; i++) {
864
+ const twentyFourHInfo = twentyFourHInfos[i];
865
+ const marketId = this.safeString(twentyFourHInfo, 'pair');
866
+ if (marketId !== undefined) {
867
+ const latestPrice = this.safeValue(tickersObject, marketId, {});
868
+ tickersObject[marketId] = this.extend(twentyFourHInfo, latestPrice);
869
+ }
870
+ }
871
+ const tickers = Object.values(tickersObject);
872
+ return this.parseTickers(tickers, symbols);
873
+ }
874
+ async fetchBidsAsks(symbols = undefined, params = {}) {
875
+ /**
876
+ * @method
877
+ * @name coinmetro#fetchBidsAsks
878
+ * @description fetches the bid and ask price and volume for multiple markets
879
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#6ecd1cd1-f162-45a3-8b3b-de690332a485
880
+ * @param {string[]} [symbols] unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
881
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
882
+ * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
883
+ */
884
+ await this.loadMarkets();
885
+ const response = await this.publicGetExchangePrices(params);
886
+ const latestPrices = this.safeValue(response, 'latestPrices', []);
887
+ return this.parseTickers(latestPrices, symbols);
888
+ }
889
+ parseTicker(ticker, market = undefined) {
890
+ //
891
+ // {
892
+ // "pair": "PERPUSD",
893
+ // "timestamp": 1702549841973,
894
+ // "price": 0.8615317721366659,
895
+ // "qty": 1e-12,
896
+ // "ask": 0.8742333599999257,
897
+ // "bid": 0.8490376365388491
898
+ // "delta": 0.26915154078134096,
899
+ // "h": 0.86220315458898,
900
+ // "l": 0.67866757035154,
901
+ // "v": 2.835000000000001e-9,
902
+ // "sentimentData": {
903
+ // "sentiment": 36.71333333333333,
904
+ // "interest": 0.47430830039525695
905
+ // }
906
+ // }
907
+ //
908
+ const marketId = this.safeString(ticker, 'pair');
909
+ market = this.safeMarket(marketId, market);
910
+ const timestamp = this.safeInteger(ticker, 'timestamp');
911
+ const bid = this.safeString(ticker, 'bid');
912
+ const ask = this.safeString(ticker, 'ask');
913
+ const high = this.safeString(ticker, 'h');
914
+ const low = this.safeString(ticker, 'l');
915
+ const last = this.safeString(ticker, 'price');
916
+ const baseVolume = this.safeString(ticker, 'v');
917
+ const delta = this.safeString(ticker, 'delta');
918
+ const percentage = Precise.stringMul(delta, '100');
919
+ return this.safeTicker({
920
+ 'symbol': market['symbol'],
921
+ 'timestamp': timestamp,
922
+ 'datetime': this.iso8601(timestamp),
923
+ 'open': undefined,
924
+ 'high': high,
925
+ 'low': low,
926
+ 'close': undefined,
927
+ 'last': last,
928
+ 'bid': bid,
929
+ 'bidVolume': undefined,
930
+ 'ask': ask,
931
+ 'askVolume': undefined,
932
+ 'vwap': undefined,
933
+ 'previousClose': undefined,
934
+ 'change': undefined,
935
+ 'percentage': percentage,
936
+ 'average': undefined,
937
+ 'baseVolume': baseVolume,
938
+ 'quoteVolume': undefined,
939
+ 'info': ticker,
940
+ }, market);
941
+ }
942
+ async fetchBalance(params = {}) {
943
+ /**
944
+ * @method
945
+ * @name coinmetro#fetchBalance
946
+ * @description query for balance and get the amount of funds available for trading or funds locked in orders
947
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#698ae067-43dd-4e19-a0ac-d9ba91381816
948
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
949
+ * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
950
+ */
951
+ await this.loadMarkets();
952
+ const response = await this.privateGetUsersBalances(params);
953
+ return this.parseBalance(response);
954
+ }
955
+ parseBalance(response) {
956
+ //
957
+ // {
958
+ // "USDC": {
959
+ // "USDC": 99,
960
+ // "EUR": 91.16,
961
+ // "BTC": 0.002334
962
+ // },
963
+ // "XCM": {
964
+ // "XCM": 0,
965
+ // "EUR": 0,
966
+ // "BTC": 0
967
+ // },
968
+ // "TOTAL": {
969
+ // "EUR": 91.16,
970
+ // "BTC": 0.002334
971
+ // },
972
+ // "REF": {
973
+ // "XCM": 0,
974
+ // "EUR": 0,
975
+ // "BTC": 0
976
+ // }
977
+ // }
978
+ //
979
+ const result = {
980
+ 'info': response,
981
+ };
982
+ const balances = this.omit(response, ['TOTAL', 'REF']);
983
+ const currencyIds = Object.keys(balances);
984
+ for (let i = 0; i < currencyIds.length; i++) {
985
+ const currencyId = currencyIds[i];
986
+ const code = this.safeCurrencyCode(currencyId);
987
+ const account = this.account();
988
+ const currency = this.safeValue(balances, currencyId, {});
989
+ account['total'] = this.safeString(currency, currencyId);
990
+ result[code] = account;
991
+ }
992
+ return this.safeBalance(result);
993
+ }
994
+ async fetchLedger(code = undefined, since = undefined, limit = undefined, params = {}) {
995
+ /**
996
+ * @method
997
+ * @name coinmetro#fetchLedger
998
+ * @description fetch the history of changes, actions done by the user or operations that altered balance of the user
999
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#4e7831f7-a0e7-4c3e-9336-1d0e5dcb15cf
1000
+ * @param {string} code unified currency code, default is undefined
1001
+ * @param {int} [since] timestamp in ms of the earliest ledger entry, default is undefined
1002
+ * @param {int} [limit] max number of ledger entrys to return (default 200, max 500)
1003
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1004
+ * @param {int} [params.until] the latest time in ms to fetch entries for
1005
+ * @returns {object} a [ledger structure]{@link https://docs.ccxt.com/#/?id=ledger-structure}
1006
+ */
1007
+ await this.loadMarkets();
1008
+ const request = {};
1009
+ if (since !== undefined) {
1010
+ request['since'] = since;
1011
+ }
1012
+ else {
1013
+ // this endpoint accepts empty since param
1014
+ request['since'] = '';
1015
+ }
1016
+ let currency = undefined;
1017
+ if (code !== undefined) {
1018
+ currency = this.currency(code);
1019
+ }
1020
+ const response = await this.privateGetUsersWalletsHistorySince(this.extend(request, params));
1021
+ //
1022
+ // {
1023
+ // "list": [
1024
+ // {
1025
+ // "currency": "USDC",
1026
+ // "label": "USDC",
1027
+ // "userId": "65671262d93d9525ac009e36",
1028
+ // "balance": 0,
1029
+ // "disabled": false,
1030
+ // "balanceHistory": [
1031
+ // {
1032
+ // "description": "Deposit - 657973a9b6eadf0f33d70100",
1033
+ // "JSONdata": {
1034
+ // "fees": 0,
1035
+ // "notes": "Via Crypto",
1036
+ // "txHash": "0x2e4875185b0f312d8e24b2d26d46bf9877db798b608ad2ff97b2b8bc7d8134e5",
1037
+ // "last4Digits": null,
1038
+ // "IBAN": null,
1039
+ // "alternativeChain": "polygon",
1040
+ // "referenceId": "657973a9b6eadf0f33d70100",
1041
+ // "status": "completed",
1042
+ // "tracked": true
1043
+ // },
1044
+ // "amount": 99,
1045
+ // "timestamp": "2023-12-13T09:04:51.270Z",
1046
+ // "amountEUR": 91.79310117335974
1047
+ // },
1048
+ // {
1049
+ // "description": "Order 65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c SeqNum 10873722342",
1050
+ // "JSONdata": {
1051
+ // "price": "2282.00 ETH/USDC",
1052
+ // "fees": 0,
1053
+ // "notes": "Order 3a8c5b4d6c"
1054
+ // },
1055
+ // "amount": -4.564,
1056
+ // "timestamp": "2023-12-14T16:16:50.760Z",
1057
+ // "amountEUR": -4.150043849187587
1058
+ // },
1059
+ // ...
1060
+ // ]
1061
+ // },
1062
+ // {
1063
+ // "currency": "ETH",
1064
+ // "label": "ETH",
1065
+ // "userId": "65671262d93d9525ac009e36",
1066
+ // "balance": 0,
1067
+ // "disabled": false,
1068
+ // "balanceHistory": [
1069
+ // {
1070
+ // "description": "Order 65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c SeqNum 10873722342",
1071
+ // "JSONdata": {
1072
+ // "price": "2282.00 ETH/USDC",
1073
+ // "fees": 0.000002,
1074
+ // "notes": "Order 3a8c5b4d6c"
1075
+ // },
1076
+ // "amount": 0.001998,
1077
+ // "timestamp": "2023-12-14T16:16:50.761Z",
1078
+ // "amountEUR": 4.144849415806856
1079
+ // },
1080
+ // ...
1081
+ // ]
1082
+ // },
1083
+ // {
1084
+ // "currency": "DOGE",
1085
+ // "label": "DOGE",
1086
+ // "userId": "65671262d93d9525ac009e36",
1087
+ // "balance": 0,
1088
+ // "disabled": false,
1089
+ // "balanceHistory": [
1090
+ // {
1091
+ // "description": "Order 65671262d93d9525ac009e361702905785319b5d9016dc20736034d13ca6a - Swap",
1092
+ // "JSONdata": {
1093
+ // "swap": true,
1094
+ // "subtype": "swap",
1095
+ // "fees": 0,
1096
+ // "price": "0.0905469 DOGE/USDC",
1097
+ // "notes": "Swap 034d13ca6a"
1098
+ // },
1099
+ // "amount": 70,
1100
+ // "timestamp": "2023-12-18T13:23:05.836Z",
1101
+ // "amountEUR": 5.643627624549227
1102
+ // }
1103
+ // ]
1104
+ // },
1105
+ // ...
1106
+ // ]
1107
+ // }
1108
+ //
1109
+ const ledgerByCurrencies = this.safeValue(response, 'list', []);
1110
+ const ledger = [];
1111
+ for (let i = 0; i < ledgerByCurrencies.length; i++) {
1112
+ const currencyLedger = ledgerByCurrencies[i];
1113
+ const currencyId = this.safeString(currencyLedger, 'currency');
1114
+ const balanceHistory = this.safeValue(currencyLedger, 'balanceHistory', []);
1115
+ for (let j = 0; j < balanceHistory.length; j++) {
1116
+ const rawLedgerEntry = balanceHistory[j];
1117
+ rawLedgerEntry['currencyId'] = currencyId;
1118
+ ledger.push(rawLedgerEntry);
1119
+ }
1120
+ }
1121
+ return this.parseLedger(ledger, currency, since, limit);
1122
+ }
1123
+ parseLedgerEntry(item, currency = undefined) {
1124
+ const datetime = this.safeString(item, 'timestamp');
1125
+ const currencyId = this.safeString(item, 'currencyId');
1126
+ item = this.omit(item, 'currencyId');
1127
+ currency = this.safeCurrency(currencyId, currency);
1128
+ const description = this.safeString(item, 'description', '');
1129
+ const [type, referenceId] = this.parseLedgerEntryDescription(description);
1130
+ const JSONdata = this.safeValue(item, 'JSONdata', {});
1131
+ const feeCost = this.safeString(JSONdata, 'fees');
1132
+ const fee = {
1133
+ 'cost': feeCost,
1134
+ 'currency': undefined,
1135
+ };
1136
+ let amount = this.safeString(item, 'amount');
1137
+ let direction = undefined;
1138
+ if (amount !== undefined) {
1139
+ if (Precise.stringLt(amount, '0')) {
1140
+ direction = 'out';
1141
+ amount = Precise.stringAbs(amount);
1142
+ }
1143
+ else if (Precise.stringGt(amount, '0')) {
1144
+ direction = 'in';
1145
+ }
1146
+ }
1147
+ return this.safeLedgerEntry({
1148
+ 'info': item,
1149
+ 'id': undefined,
1150
+ 'timestamp': this.parse8601(datetime),
1151
+ 'datetime': datetime,
1152
+ 'direction': direction,
1153
+ 'account': undefined,
1154
+ 'referenceId': referenceId,
1155
+ 'referenceAccount': undefined,
1156
+ 'type': type,
1157
+ 'currency': currency,
1158
+ 'amount': amount,
1159
+ 'before': undefined,
1160
+ 'after': undefined,
1161
+ 'status': undefined,
1162
+ 'fee': fee,
1163
+ }, currency);
1164
+ }
1165
+ parseLedgerEntryDescription(description) {
1166
+ let descriptionArray = [];
1167
+ if (description !== undefined) {
1168
+ descriptionArray = description.split(' ');
1169
+ }
1170
+ let type = undefined;
1171
+ let referenceId = undefined;
1172
+ if (descriptionArray.length > 1) {
1173
+ type = this.parseLedgerEntryType(descriptionArray[0]);
1174
+ if (descriptionArray[1] !== '-') {
1175
+ referenceId = descriptionArray[1];
1176
+ }
1177
+ else {
1178
+ referenceId = this.safeString(descriptionArray, 2);
1179
+ }
1180
+ }
1181
+ return [type, referenceId];
1182
+ }
1183
+ parseLedgerEntryType(type) {
1184
+ const types = {
1185
+ 'Deposit': 'transaction',
1186
+ 'Withdraw': 'transaction',
1187
+ 'Order': 'trade',
1188
+ };
1189
+ return this.safeString(types, type, type);
1190
+ }
1191
+ async createOrder(symbol, type, side, amount, price = undefined, params = {}) {
1192
+ /**
1193
+ * @method
1194
+ * @name coinmetro#createOrder
1195
+ * @description create a trade order
1196
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#a4895a1d-3f50-40ae-8231-6962ef06c771
1197
+ * @param {string} symbol unified symbol of the market to create an order in
1198
+ * @param {string} type 'market' or 'limit'
1199
+ * @param {string} side 'buy' or 'sell'
1200
+ * @param {float} amount how much of currency you want to trade in units of base currency
1201
+ * @param {float} [price] the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
1202
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1203
+ * @param {float} [params.cost] the quote quantity that can be used as an alternative for the amount in market orders
1204
+ * @param {string} [params.timeInForce] "GTC", "IOC", "FOK", "GTD"
1205
+ * @param {number} [params.expirationTime] timestamp in millisecond, for GTD orders only
1206
+ * @param {float} [params.triggerPrice] the price at which a trigger order is triggered at
1207
+ * @param {float} [params.stopLossPrice] *margin only* The price at which a stop loss order is triggered at
1208
+ * @param {float} [params.takeProfitPrice] *margin only* The price at which a take profit order is triggered at
1209
+ * @param {bool} [params.margin] true for creating a margin order
1210
+ * @param {string} [params.fillStyle] fill style of the limit order: "sell" fulfills selling quantity "buy" fulfills buying quantity "base" fulfills base currency quantity "quote" fulfills quote currency quantity
1211
+ * @param {string} [params.clientOrderId] client's comment
1212
+ * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
1213
+ */
1214
+ await this.loadMarkets();
1215
+ const market = this.market(symbol);
1216
+ let request = {};
1217
+ request['orderType'] = type;
1218
+ let precisedAmount = undefined;
1219
+ if (amount !== undefined) {
1220
+ precisedAmount = this.amountToPrecision(symbol, amount);
1221
+ }
1222
+ let cost = this.safeValue(params, 'cost');
1223
+ params = this.omit(params, 'cost');
1224
+ if (type === 'limit') {
1225
+ if ((price === undefined) && (cost === undefined)) {
1226
+ throw new ArgumentsRequired(this.id + ' createOrder() requires a price or params.cost argument for a ' + type + ' order');
1227
+ }
1228
+ else if ((price !== undefined) && (amount !== undefined)) {
1229
+ const costString = Precise.stringMul(this.numberToString(price), this.numberToString(precisedAmount));
1230
+ cost = this.parseToNumeric(costString);
1231
+ }
1232
+ }
1233
+ let precisedCost = undefined;
1234
+ if (cost !== undefined) {
1235
+ precisedCost = this.costToPrecision(symbol, cost);
1236
+ }
1237
+ if (side === 'sell') {
1238
+ request = this.handleCreateOrderSide(market['baseId'], market['quoteId'], precisedAmount, precisedCost, request);
1239
+ }
1240
+ else if (side === 'buy') {
1241
+ request = this.handleCreateOrderSide(market['quoteId'], market['baseId'], precisedCost, precisedAmount, request);
1242
+ }
1243
+ const timeInForce = this.safeValue(params, 'timeInForce');
1244
+ if (timeInForce !== undefined) {
1245
+ params = this.omit(params, 'timeInForce');
1246
+ request['timeInForce'] = this.encodeOrderTimeInForce(timeInForce);
1247
+ }
1248
+ const stopPrice = this.safeString2(params, 'triggerPrice', 'stopPrice');
1249
+ if (stopPrice !== undefined) {
1250
+ params = this.omit(params, ['triggerPrice']);
1251
+ request['stopPrice'] = this.priceToPrecision(symbol, stopPrice);
1252
+ }
1253
+ const userData = this.safeValue(params, 'userData', {});
1254
+ const comment = this.safeString2(params, 'clientOrderId', 'comment');
1255
+ if (comment !== undefined) {
1256
+ params = this.omit(params, ['clientOrderId']);
1257
+ userData['comment'] = comment;
1258
+ }
1259
+ const stopLossPrice = this.safeString(params, 'stopLossPrice');
1260
+ if (stopLossPrice !== undefined) {
1261
+ params = this.omit(params, 'stopLossPrice');
1262
+ userData['stopLoss'] = this.priceToPrecision(symbol, stopLossPrice);
1263
+ }
1264
+ const takeProfitPrice = this.safeString(params, 'takeProfitPrice');
1265
+ if (takeProfitPrice !== undefined) {
1266
+ params = this.omit(params, 'takeProfitPrice');
1267
+ userData['takeProfit'] = this.priceToPrecision(symbol, takeProfitPrice);
1268
+ }
1269
+ if (!this.isEmpty(userData)) {
1270
+ request['userData'] = userData;
1271
+ }
1272
+ const response = await this.privatePostExchangeOrdersCreate(this.extend(request, params));
1273
+ //
1274
+ // {
1275
+ // "userID": "65671262d93d9525ac009e36",
1276
+ // "orderID": "65671262d93d9525ac009e36170257448481749b7ee2893bafec2",
1277
+ // "orderType": "market",
1278
+ // "buyingCurrency": "ETH",
1279
+ // "sellingCurrency": "USDC",
1280
+ // "buyingQty": 0.002,
1281
+ // "timeInForce": 4,
1282
+ // "boughtQty": 0.002,
1283
+ // "soldQty": 4.587,
1284
+ // "creationTime": 1702574484829,
1285
+ // "seqNumber": 10874285330,
1286
+ // "firstFillTime": 1702574484831,
1287
+ // "lastFillTime": 1702574484831,
1288
+ // "fills": [
1289
+ // {
1290
+ // "seqNumber": 10874285329,
1291
+ // "timestamp": 1702574484831,
1292
+ // "qty": 0.002,
1293
+ // "price": 2293.5,
1294
+ // "side": "buy"
1295
+ // }
1296
+ // ],
1297
+ // "completionTime": 1702574484831,
1298
+ // "takerQty": 0.002
1299
+ // }
1300
+ //
1301
+ return this.parseOrder(response, market);
1302
+ }
1303
+ handleCreateOrderSide(sellingCurrency, buyingCurrency, sellingQty, buyingQty, request = {}) {
1304
+ request['sellingCurrency'] = sellingCurrency;
1305
+ request['buyingCurrency'] = buyingCurrency;
1306
+ if (sellingQty !== undefined) {
1307
+ request['sellingQty'] = sellingQty;
1308
+ }
1309
+ if (buyingQty !== undefined) {
1310
+ request['buyingQty'] = buyingQty;
1311
+ }
1312
+ return request;
1313
+ }
1314
+ encodeOrderTimeInForce(timeInForce) {
1315
+ const timeInForceTypes = {
1316
+ 'GTC': 1,
1317
+ 'IOC': 2,
1318
+ 'GTD': 3,
1319
+ 'FOK': 4,
1320
+ };
1321
+ return this.safeValue(timeInForceTypes, timeInForce, timeInForce);
1322
+ }
1323
+ async cancelOrder(id, symbol = undefined, params = {}) {
1324
+ /**
1325
+ * @method
1326
+ * @name coinmetro#cancelOrder
1327
+ * @description cancels an open order
1328
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#eaea86da-16ca-4c56-9f00-5b1cb2ad89f8
1329
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#47f913fb-8cab-49f4-bc78-d980e6ced316
1330
+ * @param {string} id order id
1331
+ * @param {string} symbol not used by coinmetro cancelOrder ()
1332
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1333
+ * @param {string} [params.margin] true for cancelling a margin order
1334
+ * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
1335
+ */
1336
+ await this.loadMarkets();
1337
+ const request = {
1338
+ 'orderID': id,
1339
+ };
1340
+ const marginMode = undefined;
1341
+ [params, params] = this.handleMarginModeAndParams('cancelOrder', params);
1342
+ const isMargin = this.safeValue(params, 'margin', false);
1343
+ params = this.omit(params, 'margin');
1344
+ let response = undefined;
1345
+ if (isMargin || (marginMode !== undefined)) {
1346
+ response = await this.privatePostExchangeOrdersCloseOrderID(this.extend(request, params));
1347
+ }
1348
+ else {
1349
+ response = await this.privatePutExchangeOrdersCancelOrderID(this.extend(request, params));
1350
+ }
1351
+ //
1352
+ // {
1353
+ // "userID": "65671262d93d9525ac009e36",
1354
+ // "orderID": "65671262d93d9525ac009e3617026635256739c996fe17d7cd5d4",
1355
+ // "orderType": "limit",
1356
+ // "buyingCurrency": "ETH",
1357
+ // "sellingCurrency": "USDC",
1358
+ // "fillStyle": "sell",
1359
+ // "orderPlatform": "trade-v3",
1360
+ // "timeInForce": 1,
1361
+ // "buyingQty": 0.005655,
1362
+ // "sellingQty": 11.31,
1363
+ // "boughtQty": 0,
1364
+ // "soldQty": 0,
1365
+ // "creationTime": 1702663525713,
1366
+ // "seqNumber": 10915220048,
1367
+ // "completionTime": 1702928369053
1368
+ // }
1369
+ //
1370
+ return this.parseOrder(response);
1371
+ }
1372
+ async closePosition(symbol, side = undefined, params = {}) {
1373
+ /**
1374
+ * @method
1375
+ * @name coinmetro#cancelOrder
1376
+ * @description closes an open position
1377
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#47f913fb-8cab-49f4-bc78-d980e6ced316
1378
+ * @param {string} symbol not used by coinmetro closePosition ()
1379
+ * @param {string} [side] not used by coinmetro closePosition ()
1380
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1381
+ * @param {string} [params.orderID] order id
1382
+ * @param {number} [params.fraction] fraction of order to close, between 0 and 1 (defaults to 1)
1383
+ * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
1384
+ */
1385
+ await this.loadMarkets();
1386
+ const orderId = this.safeString(params, 'orderId');
1387
+ if (orderId === undefined) {
1388
+ throw new ArgumentsRequired(this.id + ' closePosition() requires a orderId parameter');
1389
+ }
1390
+ const request = {
1391
+ 'orderID': orderId,
1392
+ };
1393
+ const response = await this.privatePostExchangeOrdersCloseOrderID(this.extend(request, params));
1394
+ //
1395
+ // {
1396
+ // "userID": "65671262d93d9525ac009e36",
1397
+ // "orderID": "65671262d93d9525ac009e3617030152811996e5b352556d3d7d8_CL",
1398
+ // "orderType": "market",
1399
+ // "buyingCurrency": "ETH",
1400
+ // "sellingCurrency": "EUR",
1401
+ // "margin": true,
1402
+ // "buyingQty": 0.03,
1403
+ // "timeInForce": 4,
1404
+ // "boughtQty": 0.03,
1405
+ // "soldQty": 59.375,
1406
+ // "creationTime": 1703015488482,
1407
+ // "seqNumber": 10925321179,
1408
+ // "firstFillTime": 1703015488483,
1409
+ // "lastFillTime": 1703015488483,
1410
+ // "fills": [
1411
+ // {
1412
+ // "seqNumber": 10925321178,
1413
+ // "timestamp": 1703015488483,
1414
+ // "qty": 0.03,
1415
+ // "price": 1979.1666666666667,
1416
+ // "side": "buy"
1417
+ // }
1418
+ // ],
1419
+ // "completionTime": 1703015488483,
1420
+ // "takerQty": 0.03
1421
+ // }
1422
+ //
1423
+ return this.parseOrder(response);
1424
+ }
1425
+ async fetchOpenOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
1426
+ /**
1427
+ * @method
1428
+ * @name coinmetro#fetchOpenOrders
1429
+ * @description fetch all unfilled currently open orders
1430
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#518afd7a-4338-439c-a651-d4fdaa964138
1431
+ * @param {string} symbol unified market symbol
1432
+ * @param {int} [since] the earliest time in ms to fetch open orders for
1433
+ * @param {int} [limit] the maximum number of open order structures to retrieve
1434
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1435
+ * @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
1436
+ */
1437
+ await this.loadMarkets();
1438
+ let market = undefined;
1439
+ if (symbol !== undefined) {
1440
+ market = this.market(symbol);
1441
+ }
1442
+ const response = await this.privateGetExchangeOrdersActive(params);
1443
+ const orders = this.parseOrders(response, market, since, limit);
1444
+ for (let i = 0; i < orders.length; i++) {
1445
+ const order = orders[i];
1446
+ order['status'] = 'open';
1447
+ }
1448
+ return orders;
1449
+ }
1450
+ async fetchCanceledAndClosedOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
1451
+ /**
1452
+ * @method
1453
+ * @name coinmetro#fetchCanceledAndClosedOrders
1454
+ * @description fetches information on multiple canceled and closed orders made by the user
1455
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#4d48ae69-8ee2-44d1-a268-71f84e557b7b
1456
+ * @param {string} symbol unified market symbol of the market orders were made in
1457
+ * @param {int} [since] the earliest time in ms to fetch orders for
1458
+ * @param {int} [limit] the maximum number of order structures to retrieve
1459
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1460
+ * @returns {Order[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
1461
+ */
1462
+ await this.loadMarkets();
1463
+ let market = undefined;
1464
+ if (symbol !== undefined) {
1465
+ market = this.market(symbol);
1466
+ }
1467
+ const request = {};
1468
+ if (since !== undefined) {
1469
+ request['since'] = since;
1470
+ }
1471
+ const response = await this.privateGetExchangeOrdersHistorySince(this.extend(request, params));
1472
+ return this.parseOrders(response, market, since, limit);
1473
+ }
1474
+ async fetchOrder(id, symbol = undefined, params = {}) {
1475
+ /**
1476
+ * @method
1477
+ * @name coinmetro#fetchOrder
1478
+ * @description fetches information on an order made by the user
1479
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#95bbed87-db1c-47a7-a03e-aa247e91d5a6
1480
+ * @param {int|string} id order id
1481
+ * @param {string} symbol not used by coinmetro fetchOrder ()
1482
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1483
+ * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
1484
+ */
1485
+ await this.loadMarkets();
1486
+ const request = {
1487
+ 'orderID': id,
1488
+ };
1489
+ const response = await this.privateGetExchangeOrdersStatusOrderID(this.extend(request, params));
1490
+ //
1491
+ // {
1492
+ // "_id": "657b4e6d60a954244939ac6f",
1493
+ // "userID": "65671262d93d9525ac009e36",
1494
+ // "orderID": "65671262d93d9525ac009e361702576531985b78465468b9cc544",
1495
+ // "orderType": "market",
1496
+ // "buyingCurrency": "ETH",
1497
+ // "sellingCurrency": "USDC",
1498
+ // "buyingQty": 0.004,
1499
+ // "timeInForce": 4,
1500
+ // "boughtQty": 0.004,
1501
+ // "soldQty": 9.236,
1502
+ // "creationTime": 1702576531995,
1503
+ // "seqNumber": 10874644062,
1504
+ // "firstFillTime": 1702576531995,
1505
+ // "lastFillTime": 1702576531995,
1506
+ // "fills": [
1507
+ // {
1508
+ // "_id": "657b4e6d60a954244939ac70",
1509
+ // "seqNumber": 10874644061,
1510
+ // "timestamp": 1702576531995,
1511
+ // "qty": 0.004,
1512
+ // "price": 2309,
1513
+ // "side": "buy"
1514
+ // }
1515
+ // ],
1516
+ // "completionTime": 1702576531995,
1517
+ // "takerQty": 0.004,
1518
+ // "fees": 0.000004,
1519
+ // "isAncillary": false,
1520
+ // "margin": false,
1521
+ // "trade": false,
1522
+ // "canceled": false
1523
+ // }
1524
+ //
1525
+ return this.parseOrder(response);
1526
+ }
1527
+ parseOrder(order, market = undefined) {
1528
+ //
1529
+ // createOrder market
1530
+ // {
1531
+ // "userID": "65671262d93d9525ac009e36",
1532
+ // "orderID": "65671262d93d9525ac009e36170257448481749b7ee2893bafec2",
1533
+ // "orderType": "market",
1534
+ // "buyingCurrency": "ETH",
1535
+ // "sellingCurrency": "USDC",
1536
+ // "buyingQty": 0.002,
1537
+ // "timeInForce": 4,
1538
+ // "boughtQty": 0.002,
1539
+ // "soldQty": 4.587,
1540
+ // "creationTime": 1702574484829,
1541
+ // "seqNumber": 10874285330,
1542
+ // "firstFillTime": 1702574484831,
1543
+ // "lastFillTime": 1702574484831,
1544
+ // "fills": [
1545
+ // {
1546
+ // "seqNumber": 10874285329,
1547
+ // "timestamp": 1702574484831,
1548
+ // "qty": 0.002,
1549
+ // "price": 2293.5,
1550
+ // "side": "buy"
1551
+ // }
1552
+ // ],
1553
+ // "completionTime": 1702574484831,
1554
+ // "takerQty": 0.002
1555
+ // }
1556
+ //
1557
+ // createOrder limit
1558
+ // {
1559
+ // "userID": "65671262d93d9525ac009e36",
1560
+ // "orderID": "65671262d93d9525ac009e3617026635256739c996fe17d7cd5d4",
1561
+ // "orderType": "limit",
1562
+ // "buyingCurrency": "ETH",
1563
+ // "sellingCurrency": "USDC",
1564
+ // "fillStyle": "sell",
1565
+ // "orderPlatform": "trade-v3",
1566
+ // "timeInForce": 1,
1567
+ // "buyingQty": 0.005655,
1568
+ // "sellingQty": 11.31,
1569
+ // "boughtQty": 0,
1570
+ // "soldQty": 0,
1571
+ // "creationTime": 1702663525713,
1572
+ // "seqNumber": 10885528683,
1573
+ // "fees": 0,
1574
+ // "fills": [],
1575
+ // "isAncillary": false,
1576
+ // "margin": false,
1577
+ // "trade": false
1578
+ // }
1579
+ //
1580
+ // fetchOrders market
1581
+ // {
1582
+ // "userID": "65671262d93d9525ac009e36",
1583
+ // "orderID": "65671262d93d9525ac009e36170257061073952c6423a8c5b4d6c",
1584
+ // "orderType": "market",
1585
+ // "buyingCurrency": "ETH",
1586
+ // "sellingCurrency": "USDC",
1587
+ // "buyingQty": 0.002,
1588
+ // "timeInForce": 4,
1589
+ // "boughtQty": 0.002,
1590
+ // "soldQty": 4.564,
1591
+ // "creationTime": 1702570610746,
1592
+ // "seqNumber": 10873722344,
1593
+ // "firstFillTime": 1702570610747,
1594
+ // "lastFillTime": 1702570610747,
1595
+ // "fills": [
1596
+ // {
1597
+ // "_id": "657b31d360a9542449381bdc",
1598
+ // "seqNumber": 10873722343,
1599
+ // "timestamp": 1702570610747,
1600
+ // "qty": 0.002,
1601
+ // "price": 2282,
1602
+ // "side": "buy"
1603
+ // }
1604
+ // ],
1605
+ // "completionTime": 1702570610747,
1606
+ // "takerQty": 0.002,
1607
+ // "fees": 0.000002,
1608
+ // "isAncillary": false,
1609
+ // "margin": false,
1610
+ // "trade": false,
1611
+ // "canceled": false,
1612
+ // "__v": 0
1613
+ // }
1614
+ //
1615
+ // fetchOrders margin
1616
+ // {
1617
+ // "userData": {
1618
+ // "takeProfit": 1700,
1619
+ // "stopLoss": 2100
1620
+ // },
1621
+ // "_id": "658201d060a95424499394a2",
1622
+ // "seqNumber": 10925300213,
1623
+ // "orderType": "limit",
1624
+ // "buyingCurrency": "EUR",
1625
+ // "sellingCurrency": "ETH",
1626
+ // "userID": "65671262d93d9525ac009e36",
1627
+ // "closedQty": 0.03,
1628
+ // "sellingQty": 0.03,
1629
+ // "buyingQty": 58.8,
1630
+ // "creationTime": 1703015281205,
1631
+ // "margin": true,
1632
+ // "timeInForce": 1,
1633
+ // "boughtQty": 59.31,
1634
+ // "orderID": "65671262d93d9525ac009e3617030152811996e5b352556d3d7d8",
1635
+ // "lastFillTime": 1703015281206,
1636
+ // "soldQty": 0.03,
1637
+ // "closedTime": 1703015488488,
1638
+ // "closedVal": 59.375,
1639
+ // "trade": true,
1640
+ // "takerQty": 59.31,
1641
+ // "firstFillTime": 1703015281206,
1642
+ // "completionTime": 1703015281206,
1643
+ // "fills": [
1644
+ // {
1645
+ // "_id": "658201d060a95424499394a3",
1646
+ // "seqNumber": 10925300212,
1647
+ // "side": "sell",
1648
+ // "price": 1977,
1649
+ // "qty": 0.03,
1650
+ // "timestamp": 1703015281206
1651
+ // },
1652
+ // {
1653
+ // "_id": "658201d060a95424499394a4",
1654
+ // "seqNumber": 10925321178,
1655
+ // "timestamp": 1703015488483,
1656
+ // "qty": 0.03,
1657
+ // "price": 1979.1666666666667,
1658
+ // "side": "buy"
1659
+ // }
1660
+ // ],
1661
+ // "fees": 0.11875000200000001,
1662
+ // "settledQtys": {
1663
+ // "ETH": -0.000092842104710025
1664
+ // },
1665
+ // "isAncillary": false,
1666
+ // "canceled": false
1667
+ // }
1668
+ //
1669
+ // fetchOrder
1670
+ // {
1671
+ // "_id": "657b4e6d60a954244939ac6f",
1672
+ // "userID": "65671262d93d9525ac009e36",
1673
+ // "orderID": "65671262d93d9525ac009e361702576531985b78465468b9cc544",
1674
+ // "orderType": "market",
1675
+ // "buyingCurrency": "ETH",
1676
+ // "sellingCurrency": "USDC",
1677
+ // "buyingQty": 0.004,
1678
+ // "timeInForce": 4,
1679
+ // "boughtQty": 0.004,
1680
+ // "soldQty": 9.236,
1681
+ // "creationTime": 1702576531995,
1682
+ // "seqNumber": 10874644062,
1683
+ // "firstFillTime": 1702576531995,
1684
+ // "lastFillTime": 1702576531995,
1685
+ // "fills": [
1686
+ // {
1687
+ // "_id": "657b4e6d60a954244939ac70",
1688
+ // "seqNumber": 10874644061,
1689
+ // "timestamp": 1702576531995,
1690
+ // "qty": 0.004,
1691
+ // "price": 2309,
1692
+ // "side": "buy"
1693
+ // }
1694
+ // ],
1695
+ // "completionTime": 1702576531995,
1696
+ // "takerQty": 0.004,
1697
+ // "fees": 0.000004,
1698
+ // "isAncillary": false,
1699
+ // "margin": false,
1700
+ // "trade": false,
1701
+ // "canceled": false
1702
+ // }
1703
+ //
1704
+ let timestamp = this.safeInteger(order, 'creationTime');
1705
+ const isCanceled = this.safeValue(order, 'canceled');
1706
+ let status = undefined;
1707
+ if (isCanceled === true) {
1708
+ if (timestamp === undefined) {
1709
+ timestamp = this.safeInteger(order, 'completionTime'); // market orders with bad price gain IOC - we mark them as 'rejected'?
1710
+ status = 'rejected'; // these orders don't have the 'creationTime` param and have 'canceled': true
1711
+ }
1712
+ else {
1713
+ status = 'canceled';
1714
+ }
1715
+ }
1716
+ else {
1717
+ status = this.safeString(order, 'status');
1718
+ order = this.omit(order, 'status'); // we mark orders from fetchOpenOrders with param 'status': 'open'
1719
+ }
1720
+ const type = this.safeString(order, 'orderType');
1721
+ let buyingQty = this.safeString(order, 'buyingQty');
1722
+ let sellingQty = this.safeString(order, 'sellingQty');
1723
+ const boughtQty = this.safeString(order, 'boughtQty');
1724
+ const soldQty = this.safeString(order, 'soldQty');
1725
+ if (type === 'market') {
1726
+ if ((buyingQty === undefined) && (boughtQty !== undefined) && (boughtQty !== '0')) {
1727
+ buyingQty = boughtQty;
1728
+ }
1729
+ if ((sellingQty === undefined) && (soldQty !== undefined) && (soldQty !== '0')) {
1730
+ sellingQty = soldQty;
1731
+ }
1732
+ }
1733
+ const buyingCurrencyId = this.safeString(order, 'buyingCurrency', '');
1734
+ const sellingCurrencyId = this.safeString(order, 'sellingCurrency', '');
1735
+ const byuingIdPlusSellingId = buyingCurrencyId + sellingCurrencyId;
1736
+ const sellingIdPlusBuyingId = sellingCurrencyId + buyingCurrencyId;
1737
+ let side = undefined;
1738
+ let marketId = undefined;
1739
+ let baseAmount = buyingQty;
1740
+ let quoteAmount = buyingQty;
1741
+ let filled = undefined;
1742
+ let cost = undefined;
1743
+ let feeInBaseOrQuote = undefined;
1744
+ const marketsById = this.indexBy(this.markets, 'id');
1745
+ if (this.safeValue(marketsById, byuingIdPlusSellingId) !== undefined) {
1746
+ side = 'buy';
1747
+ marketId = byuingIdPlusSellingId;
1748
+ quoteAmount = sellingQty;
1749
+ filled = boughtQty;
1750
+ cost = soldQty;
1751
+ feeInBaseOrQuote = 'base';
1752
+ }
1753
+ else if (this.safeValue(marketsById, sellingIdPlusBuyingId) !== undefined) {
1754
+ side = 'sell';
1755
+ marketId = sellingIdPlusBuyingId;
1756
+ baseAmount = sellingQty;
1757
+ filled = soldQty;
1758
+ cost = boughtQty;
1759
+ feeInBaseOrQuote = 'quote';
1760
+ }
1761
+ let price = undefined;
1762
+ if ((baseAmount !== undefined) && (quoteAmount !== undefined)) {
1763
+ price = Precise.stringDiv(quoteAmount, baseAmount);
1764
+ }
1765
+ market = this.safeMarket(marketId, market);
1766
+ let fee = undefined;
1767
+ const feeCost = this.safeString(order, 'fees');
1768
+ if ((feeCost !== undefined) && (feeInBaseOrQuote !== undefined)) {
1769
+ fee = {
1770
+ 'currency': market[feeInBaseOrQuote],
1771
+ 'cost': feeCost,
1772
+ 'rate': undefined,
1773
+ };
1774
+ }
1775
+ const trades = this.safeValue(order, 'fills', []);
1776
+ const userData = this.safeValue(order, 'userData', {});
1777
+ const triggerPrice = this.safeString(order, 'stopPrice');
1778
+ const clientOrderId = this.safeString(userData, 'comment');
1779
+ const takeProfitPrice = this.safeString(userData, 'takeProfit');
1780
+ const stopLossPrice = this.safeString(userData, 'stopLoss');
1781
+ return this.safeOrder({
1782
+ 'id': this.safeString(order, 'orderID'),
1783
+ 'clientOrderId': clientOrderId,
1784
+ 'timestamp': timestamp,
1785
+ 'datetime': this.iso8601(timestamp),
1786
+ 'lastTradeTimestamp': this.safeInteger(order, 'lastFillTime'),
1787
+ 'status': status,
1788
+ 'symbol': market['symbol'],
1789
+ 'type': type,
1790
+ 'timeInForce': this.parseOrderTimeInForce(this.safeInteger(order, 'timeInForce')),
1791
+ 'side': side,
1792
+ 'price': price,
1793
+ 'triggerPrice': triggerPrice,
1794
+ 'takeProfitPrice': takeProfitPrice,
1795
+ 'stopLossPrice': stopLossPrice,
1796
+ 'average': undefined,
1797
+ 'amount': baseAmount,
1798
+ 'cost': cost,
1799
+ 'filled': filled,
1800
+ 'remaining': undefined,
1801
+ 'fee': fee,
1802
+ 'fees': undefined,
1803
+ 'trades': trades,
1804
+ 'info': order,
1805
+ }, market);
1806
+ }
1807
+ parseOrderTimeInForce(timeInForce) {
1808
+ const timeInForceTypes = [
1809
+ undefined,
1810
+ 'GTC',
1811
+ 'IOC',
1812
+ 'GTD',
1813
+ 'FOK',
1814
+ ];
1815
+ return this.safeValue(timeInForceTypes, timeInForce, timeInForce);
1816
+ }
1817
+ async borrowCrossMargin(code, amount, params = {}) {
1818
+ /**
1819
+ * @method
1820
+ * @name coinmetro#borrowCrossMargin
1821
+ * @description create a loan to borrow margin
1822
+ * @see https://documenter.getpostman.com/view/3653795/SVfWN6KS#5b90b3b9-e5db-4d07-ac9d-d680a06fd110
1823
+ * @param {string} code unified currency code of the currency to borrow
1824
+ * @param {float} amount the amount to borrow
1825
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
1826
+ * @returns {object} a [margin loan structure]{@link https://docs.ccxt.com/#/?id=margin-loan-structure}
1827
+ */
1828
+ await this.loadMarkets();
1829
+ const currency = this.currency(code);
1830
+ const currencyId = currency['id'];
1831
+ const request = {};
1832
+ request[currencyId] = this.currencyToPrecision(code, amount);
1833
+ const response = await this.privatePutUsersMarginCollateral(this.extend(request, params));
1834
+ //
1835
+ // { "message": "OK" }
1836
+ //
1837
+ const result = this.safeValue(response, 'result', {});
1838
+ const transaction = this.parseMarginLoan(result, currency);
1839
+ return this.extend(transaction, {
1840
+ 'amount': amount,
1841
+ });
1842
+ }
1843
+ parseMarginLoan(info, currency = undefined) {
1844
+ const currencyId = this.safeString(info, 'coin');
1845
+ return {
1846
+ 'id': undefined,
1847
+ 'currency': this.safeCurrencyCode(currencyId, currency),
1848
+ 'amount': undefined,
1849
+ 'symbol': undefined,
1850
+ 'timestamp': undefined,
1851
+ 'datetime': undefined,
1852
+ 'info': info,
1853
+ };
1854
+ }
1855
+ sign(path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
1856
+ const request = this.omit(params, this.extractParams(path));
1857
+ const endpoint = '/' + this.implodeParams(path, params);
1858
+ let url = this.urls['api'][api] + endpoint;
1859
+ const query = this.urlencode(request);
1860
+ if (api === 'private') {
1861
+ if (headers === undefined) {
1862
+ headers = {};
1863
+ }
1864
+ headers['CCXT'] = true;
1865
+ if (url === 'https://api.coinmetro.com/jwt') { // handle with headers for login endpoint
1866
+ headers['X-Device-Id'] = 'bypass';
1867
+ if (this.twofa !== undefined) {
1868
+ headers['X-OTP'] = this.twofa;
1869
+ }
1870
+ }
1871
+ else if (url === 'https://api.coinmetro.com/jwtDevice') { // handle with headers for long lived token login endpoint
1872
+ headers['X-Device-Id'] = this.uid;
1873
+ if (this.twofa !== undefined) {
1874
+ headers['X-OTP'] = this.twofa;
1875
+ }
1876
+ }
1877
+ else {
1878
+ headers['Authorization'] = 'Bearer ' + this.token;
1879
+ if (!url.startsWith('https://api.coinmetro.com/open')) { // if not sandbox endpoint
1880
+ this.checkRequiredCredentials();
1881
+ headers['X-Device-Id'] = this.uid;
1882
+ }
1883
+ }
1884
+ if ((method === 'POST') || (method === 'PUT')) {
1885
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
1886
+ body = this.urlencode(request);
1887
+ }
1888
+ }
1889
+ else if (query.length !== 0) {
1890
+ url += '?' + query;
1891
+ }
1892
+ while (url.endsWith('/')) {
1893
+ url = url.slice(0, url.length - 1);
1894
+ }
1895
+ return { 'url': url, 'method': method, 'body': body, 'headers': headers };
1896
+ }
1897
+ handleErrors(code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
1898
+ if (response === undefined) {
1899
+ return undefined;
1900
+ }
1901
+ if ((code !== 200) && (code !== 201) && (code !== 202)) {
1902
+ const feedback = this.id + ' ' + body;
1903
+ const message = this.safeString(response, 'message');
1904
+ this.throwBroadlyMatchedException(this.exceptions['broad'], message, feedback);
1905
+ this.throwExactlyMatchedException(this.exceptions['exact'], message, feedback);
1906
+ throw new ExchangeError(feedback);
1907
+ }
1908
+ return undefined;
1909
+ }
1910
+ }