ccxt 4.2.94 → 4.2.96

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 (43) hide show
  1. package/README.md +3 -3
  2. package/build.sh +1 -1
  3. package/dist/ccxt.browser.js +964 -406
  4. package/dist/ccxt.browser.min.js +2 -2
  5. package/dist/cjs/ccxt.js +1 -1
  6. package/dist/cjs/src/base/errors.js +25 -64
  7. package/dist/cjs/src/base/functions/crypto.js +15 -2
  8. package/dist/cjs/src/base/functions/rsa.js +2 -2
  9. package/dist/cjs/src/base/ws/OrderBookSide.js +5 -0
  10. package/dist/cjs/src/bitstamp.js +6 -0
  11. package/dist/cjs/src/coinbase.js +615 -102
  12. package/dist/cjs/src/coinex.js +61 -55
  13. package/dist/cjs/src/gemini.js +29 -10
  14. package/dist/cjs/src/htx.js +127 -125
  15. package/dist/cjs/src/okx.js +40 -40
  16. package/dist/cjs/src/pro/coinbase.js +37 -4
  17. package/js/ccxt.d.ts +3 -3
  18. package/js/ccxt.js +3 -3
  19. package/js/src/abstract/bitstamp.d.ts +6 -0
  20. package/js/src/abstract/coinbase.d.ts +1 -0
  21. package/js/src/base/errorHierarchy.d.ts +1 -1
  22. package/js/src/base/errorHierarchy.js +1 -1
  23. package/js/src/base/errors.d.ts +26 -26
  24. package/js/src/base/errors.js +26 -66
  25. package/js/src/base/functions/crypto.d.ts +1 -1
  26. package/js/src/base/functions/crypto.js +15 -2
  27. package/js/src/base/functions/rsa.js +2 -2
  28. package/js/src/base/types.d.ts +1 -0
  29. package/js/src/base/ws/OrderBook.d.ts +8 -0
  30. package/js/src/base/ws/OrderBook.js +1 -6
  31. package/js/src/base/ws/OrderBookSide.d.ts +9 -3
  32. package/js/src/base/ws/OrderBookSide.js +6 -1
  33. package/js/src/bitstamp.js +6 -0
  34. package/js/src/coinbase.d.ts +8 -1
  35. package/js/src/coinbase.js +616 -103
  36. package/js/src/coinex.js +61 -55
  37. package/js/src/gemini.js +29 -10
  38. package/js/src/htx.d.ts +1 -0
  39. package/js/src/htx.js +128 -126
  40. package/js/src/okx.js +40 -40
  41. package/js/src/pro/coinbase.js +37 -4
  42. package/package.json +1 -1
  43. package/skip-tests.json +2 -0
@@ -42,7 +42,7 @@ class coinbase extends coinbase$1 {
42
42
  'cancelOrder': true,
43
43
  'cancelOrders': true,
44
44
  'closeAllPositions': false,
45
- 'closePosition': false,
45
+ 'closePosition': true,
46
46
  'createDepositAddress': true,
47
47
  'createLimitBuyOrder': true,
48
48
  'createLimitSellOrder': true,
@@ -97,9 +97,9 @@ class coinbase extends coinbase$1 {
97
97
  'fetchOrder': true,
98
98
  'fetchOrderBook': true,
99
99
  'fetchOrders': true,
100
- 'fetchPosition': false,
100
+ 'fetchPosition': true,
101
101
  'fetchPositionMode': false,
102
- 'fetchPositions': false,
102
+ 'fetchPositions': true,
103
103
  'fetchPositionsRisk': false,
104
104
  'fetchPremiumIndexOHLCV': false,
105
105
  'fetchTicker': true,
@@ -242,6 +242,8 @@ class coinbase extends coinbase$1 {
242
242
  'brokerage/convert/trade/{trade_id}': 1,
243
243
  'brokerage/cfm/sweeps/schedule': 1,
244
244
  'brokerage/intx/allocate': 1,
245
+ // futures
246
+ 'brokerage/orders/close_position': 1,
245
247
  },
246
248
  'put': {
247
249
  'brokerage/portfolios/{portfolio_uuid}': 1,
@@ -308,6 +310,8 @@ class coinbase extends coinbase$1 {
308
310
  'internal_server_error': errors.ExchangeError,
309
311
  'UNSUPPORTED_ORDER_CONFIGURATION': errors.BadRequest,
310
312
  'INSUFFICIENT_FUND': errors.BadRequest,
313
+ 'PERMISSION_DENIED': errors.PermissionDenied,
314
+ 'INVALID_ARGUMENT': errors.BadRequest,
311
315
  },
312
316
  'broad': {
313
317
  'request timestamp expired': errors.InvalidNonce,
@@ -533,6 +537,29 @@ class coinbase extends coinbase$1 {
533
537
  }
534
538
  return this.parseAccounts(accounts, params);
535
539
  }
540
+ async fetchPortfolios(params = {}) {
541
+ /**
542
+ * @method
543
+ * @name coinbase#fetchPortfolios
544
+ * @description fetch all the portfolios
545
+ * @see https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getportfolios
546
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
547
+ * @returns {object} a dictionary of [account structures]{@link https://docs.ccxt.com/#/?id=account-structure} indexed by the account type
548
+ */
549
+ const response = await this.v3PrivateGetBrokeragePortfolios(params);
550
+ const portfolios = this.safeList(response, 'portfolios', []);
551
+ const result = [];
552
+ for (let i = 0; i < portfolios.length; i++) {
553
+ const portfolio = portfolios[i];
554
+ result.push({
555
+ 'id': this.safeString(portfolio, 'uuid'),
556
+ 'type': this.safeString(portfolio, 'type'),
557
+ 'code': undefined,
558
+ 'info': portfolio,
559
+ });
560
+ }
561
+ return result;
562
+ }
536
563
  parseAccount(account) {
537
564
  //
538
565
  // fetchAccountsV2
@@ -1149,15 +1176,69 @@ class coinbase extends coinbase$1 {
1149
1176
  return result;
1150
1177
  }
1151
1178
  async fetchMarketsV3(params = {}) {
1152
- const promisesUnresolved = [
1179
+ const spotUnresolvedPromises = [
1153
1180
  this.v3PrivateGetBrokerageProducts(params),
1154
1181
  this.v3PrivateGetBrokerageTransactionSummary(params),
1155
1182
  ];
1156
- // const response = await this.v3PrivateGetBrokerageProducts (params);
1157
- const promises = await Promise.all(promisesUnresolved);
1158
- const response = this.safeDict(promises, 0, {});
1183
+ const unresolvedContractPromises = [
1184
+ this.v3PrivateGetBrokerageProducts(this.extend(params, { 'product_type': 'FUTURE' })),
1185
+ this.v3PrivateGetBrokerageProducts(this.extend(params, { 'product_type': 'FUTURE', 'contract_expiry_type': 'PERPETUAL' })),
1186
+ this.v3PrivateGetBrokerageTransactionSummary(this.extend(params, { 'product_type': 'FUTURE' })),
1187
+ this.v3PrivateGetBrokerageTransactionSummary(this.extend(params, { 'product_type': 'FUTURE', 'contract_expiry_type': 'PERPETUAL' })),
1188
+ ];
1189
+ const promises = await Promise.all(spotUnresolvedPromises);
1190
+ let contractPromises = undefined;
1191
+ try {
1192
+ contractPromises = await Promise.all(unresolvedContractPromises); // some users don't have access to contracts
1193
+ }
1194
+ catch (e) {
1195
+ contractPromises = [];
1196
+ }
1197
+ const spot = this.safeDict(promises, 0, {});
1198
+ const fees = this.safeDict(promises, 1, {});
1199
+ const expiringFutures = this.safeDict(contractPromises, 0, {});
1200
+ const perpetualFutures = this.safeDict(contractPromises, 1, {});
1201
+ const expiringFees = this.safeDict(contractPromises, 2, {});
1202
+ const perpetualFees = this.safeDict(contractPromises, 3, {});
1203
+ //
1204
+ // {
1205
+ // "total_volume": 0,
1206
+ // "total_fees": 0,
1207
+ // "fee_tier": {
1208
+ // "pricing_tier": "",
1209
+ // "usd_from": "0",
1210
+ // "usd_to": "10000",
1211
+ // "taker_fee_rate": "0.006",
1212
+ // "maker_fee_rate": "0.004"
1213
+ // },
1214
+ // "margin_rate": null,
1215
+ // "goods_and_services_tax": null,
1216
+ // "advanced_trade_only_volume": 0,
1217
+ // "advanced_trade_only_fees": 0,
1218
+ // "coinbase_pro_volume": 0,
1219
+ // "coinbase_pro_fees": 0
1220
+ // }
1221
+ //
1222
+ const feeTier = this.safeDict(fees, 'fee_tier', {});
1223
+ const expiringFeeTier = this.safeDict(expiringFees, 'fee_tier', {}); // fee tier null?
1224
+ const perpetualFeeTier = this.safeDict(perpetualFees, 'fee_tier', {}); // fee tier null?
1225
+ const data = this.safeList(spot, 'products', []);
1226
+ const result = [];
1227
+ for (let i = 0; i < data.length; i++) {
1228
+ result.push(this.parseSpotMarket(data[i], feeTier));
1229
+ }
1230
+ const futureData = this.safeList(expiringFutures, 'products', []);
1231
+ for (let i = 0; i < futureData.length; i++) {
1232
+ result.push(this.parseContractMarket(futureData[i], expiringFeeTier));
1233
+ }
1234
+ const perpetualData = this.safeList(perpetualFutures, 'products', []);
1235
+ for (let i = 0; i < perpetualData.length; i++) {
1236
+ result.push(this.parseContractMarket(perpetualData[i], perpetualFeeTier));
1237
+ }
1238
+ return result;
1239
+ }
1240
+ parseSpotMarket(market, feeTier) {
1159
1241
  //
1160
- // [
1161
1242
  // {
1162
1243
  // "product_id": "TONE-USD",
1163
1244
  // "price": "0.01523",
@@ -1186,97 +1267,262 @@ class coinbase extends coinbase$1 {
1186
1267
  // "base_currency_id": "TONE",
1187
1268
  // "fcm_trading_session_details": null,
1188
1269
  // "mid_market_price": ""
1189
- // },
1190
- // ...
1191
- // ]
1270
+ // }
1192
1271
  //
1193
- // const fees = await this.v3PrivateGetBrokerageTransactionSummary (params);
1194
- const fees = this.safeDict(promises, 1, {});
1272
+ const id = this.safeString(market, 'product_id');
1273
+ const baseId = this.safeString(market, 'base_currency_id');
1274
+ const quoteId = this.safeString(market, 'quote_currency_id');
1275
+ const base = this.safeCurrencyCode(baseId);
1276
+ const quote = this.safeCurrencyCode(quoteId);
1277
+ const marketType = this.safeStringLower(market, 'product_type');
1278
+ const tradingDisabled = this.safeBool(market, 'trading_disabled');
1279
+ const stablePairs = this.safeList(this.options, 'stablePairs', []);
1280
+ return this.safeMarketStructure({
1281
+ 'id': id,
1282
+ 'symbol': base + '/' + quote,
1283
+ 'base': base,
1284
+ 'quote': quote,
1285
+ 'settle': undefined,
1286
+ 'baseId': baseId,
1287
+ 'quoteId': quoteId,
1288
+ 'settleId': undefined,
1289
+ 'type': marketType,
1290
+ 'spot': (marketType === 'spot'),
1291
+ 'margin': undefined,
1292
+ 'swap': false,
1293
+ 'future': false,
1294
+ 'option': false,
1295
+ 'active': !tradingDisabled,
1296
+ 'contract': false,
1297
+ 'linear': undefined,
1298
+ 'inverse': undefined,
1299
+ 'taker': this.inArray(id, stablePairs) ? 0.00001 : this.safeNumber(feeTier, 'taker_fee_rate'),
1300
+ 'maker': this.inArray(id, stablePairs) ? 0.0 : this.safeNumber(feeTier, 'maker_fee_rate'),
1301
+ 'contractSize': undefined,
1302
+ 'expiry': undefined,
1303
+ 'expiryDatetime': undefined,
1304
+ 'strike': undefined,
1305
+ 'optionType': undefined,
1306
+ 'precision': {
1307
+ 'amount': this.safeNumber(market, 'base_increment'),
1308
+ 'price': this.safeNumber2(market, 'price_increment', 'quote_increment'),
1309
+ },
1310
+ 'limits': {
1311
+ 'leverage': {
1312
+ 'min': undefined,
1313
+ 'max': undefined,
1314
+ },
1315
+ 'amount': {
1316
+ 'min': this.safeNumber(market, 'base_min_size'),
1317
+ 'max': this.safeNumber(market, 'base_max_size'),
1318
+ },
1319
+ 'price': {
1320
+ 'min': undefined,
1321
+ 'max': undefined,
1322
+ },
1323
+ 'cost': {
1324
+ 'min': this.safeNumber(market, 'quote_min_size'),
1325
+ 'max': this.safeNumber(market, 'quote_max_size'),
1326
+ },
1327
+ },
1328
+ 'created': undefined,
1329
+ 'info': market,
1330
+ });
1331
+ }
1332
+ parseContractMarket(market, feeTier) {
1333
+ // expiring
1195
1334
  //
1196
- // {
1197
- // "total_volume": 0,
1198
- // "total_fees": 0,
1199
- // "fee_tier": {
1200
- // "pricing_tier": "",
1201
- // "usd_from": "0",
1202
- // "usd_to": "10000",
1203
- // "taker_fee_rate": "0.006",
1204
- // "maker_fee_rate": "0.004"
1205
- // },
1206
- // "margin_rate": null,
1207
- // "goods_and_services_tax": null,
1208
- // "advanced_trade_only_volume": 0,
1209
- // "advanced_trade_only_fees": 0,
1210
- // "coinbase_pro_volume": 0,
1211
- // "coinbase_pro_fees": 0
1212
- // }
1335
+ // {
1336
+ // "product_id":"BIT-26APR24-CDE",
1337
+ // "price":"71145",
1338
+ // "price_percentage_change_24h":"-2.36722931247427",
1339
+ // "volume_24h":"108549",
1340
+ // "volume_percentage_change_24h":"155.78255337197794",
1341
+ // "base_increment":"1",
1342
+ // "quote_increment":"0.01",
1343
+ // "quote_min_size":"0",
1344
+ // "quote_max_size":"100000000",
1345
+ // "base_min_size":"1",
1346
+ // "base_max_size":"100000000",
1347
+ // "base_name":"",
1348
+ // "quote_name":"US Dollar",
1349
+ // "watched":false,
1350
+ // "is_disabled":false,
1351
+ // "new":false,
1352
+ // "status":"",
1353
+ // "cancel_only":false,
1354
+ // "limit_only":false,
1355
+ // "post_only":false,
1356
+ // "trading_disabled":false,
1357
+ // "auction_mode":false,
1358
+ // "product_type":"FUTURE",
1359
+ // "quote_currency_id":"USD",
1360
+ // "base_currency_id":"",
1361
+ // "fcm_trading_session_details":{
1362
+ // "is_session_open":true,
1363
+ // "open_time":"2024-04-08T22:00:00Z",
1364
+ // "close_time":"2024-04-09T21:00:00Z"
1365
+ // },
1366
+ // "mid_market_price":"71105",
1367
+ // "alias":"",
1368
+ // "alias_to":[
1369
+ // ],
1370
+ // "base_display_symbol":"",
1371
+ // "quote_display_symbol":"USD",
1372
+ // "view_only":false,
1373
+ // "price_increment":"5",
1374
+ // "display_name":"BTC 26 APR 24",
1375
+ // "product_venue":"FCM",
1376
+ // "future_product_details":{
1377
+ // "venue":"cde",
1378
+ // "contract_code":"BIT",
1379
+ // "contract_expiry":"2024-04-26T15:00:00Z",
1380
+ // "contract_size":"0.01",
1381
+ // "contract_root_unit":"BTC",
1382
+ // "group_description":"Nano Bitcoin Futures",
1383
+ // "contract_expiry_timezone":"Europe/London",
1384
+ // "group_short_description":"Nano BTC",
1385
+ // "risk_managed_by":"MANAGED_BY_FCM",
1386
+ // "contract_expiry_type":"EXPIRING",
1387
+ // "contract_display_name":"BTC 26 APR 24"
1388
+ // }
1389
+ // }
1213
1390
  //
1214
- const feeTier = this.safeDict(fees, 'fee_tier', {});
1215
- const data = this.safeList(response, 'products', []);
1216
- const result = [];
1217
- for (let i = 0; i < data.length; i++) {
1218
- const market = data[i];
1219
- const id = this.safeString(market, 'product_id');
1220
- const baseId = this.safeString(market, 'base_currency_id');
1221
- const quoteId = this.safeString(market, 'quote_currency_id');
1222
- const base = this.safeCurrencyCode(baseId);
1223
- const quote = this.safeCurrencyCode(quoteId);
1224
- const marketType = this.safeStringLower(market, 'product_type');
1225
- const tradingDisabled = this.safeBool(market, 'trading_disabled');
1226
- const stablePairs = this.safeList(this.options, 'stablePairs', []);
1227
- result.push({
1228
- 'id': id,
1229
- 'symbol': base + '/' + quote,
1230
- 'base': base,
1231
- 'quote': quote,
1232
- 'settle': undefined,
1233
- 'baseId': baseId,
1234
- 'quoteId': quoteId,
1235
- 'settleId': undefined,
1236
- 'type': marketType,
1237
- 'spot': (marketType === 'spot'),
1238
- 'margin': undefined,
1239
- 'swap': false,
1240
- 'future': false,
1241
- 'option': false,
1242
- 'active': !tradingDisabled,
1243
- 'contract': false,
1244
- 'linear': undefined,
1245
- 'inverse': undefined,
1246
- 'taker': this.inArray(id, stablePairs) ? 0.00001 : this.safeNumber(feeTier, 'taker_fee_rate'),
1247
- 'maker': this.inArray(id, stablePairs) ? 0.0 : this.safeNumber(feeTier, 'maker_fee_rate'),
1248
- 'contractSize': undefined,
1249
- 'expiry': undefined,
1250
- 'expiryDatetime': undefined,
1251
- 'strike': undefined,
1252
- 'optionType': undefined,
1253
- 'precision': {
1254
- 'amount': this.safeNumber(market, 'base_increment'),
1255
- 'price': this.safeNumber2(market, 'price_increment', 'quote_increment'),
1391
+ // perpetual
1392
+ //
1393
+ // {
1394
+ // "product_id":"ETH-PERP-INTX",
1395
+ // "price":"3630.98",
1396
+ // "price_percentage_change_24h":"0.65142426292038",
1397
+ // "volume_24h":"114020.1501",
1398
+ // "volume_percentage_change_24h":"63.33650787154869",
1399
+ // "base_increment":"0.0001",
1400
+ // "quote_increment":"0.01",
1401
+ // "quote_min_size":"10",
1402
+ // "quote_max_size":"50000000",
1403
+ // "base_min_size":"0.0001",
1404
+ // "base_max_size":"50000",
1405
+ // "base_name":"",
1406
+ // "quote_name":"USDC",
1407
+ // "watched":false,
1408
+ // "is_disabled":false,
1409
+ // "new":false,
1410
+ // "status":"",
1411
+ // "cancel_only":false,
1412
+ // "limit_only":false,
1413
+ // "post_only":false,
1414
+ // "trading_disabled":false,
1415
+ // "auction_mode":false,
1416
+ // "product_type":"FUTURE",
1417
+ // "quote_currency_id":"USDC",
1418
+ // "base_currency_id":"",
1419
+ // "fcm_trading_session_details":null,
1420
+ // "mid_market_price":"3630.975",
1421
+ // "alias":"",
1422
+ // "alias_to":[],
1423
+ // "base_display_symbol":"",
1424
+ // "quote_display_symbol":"USDC",
1425
+ // "view_only":false,
1426
+ // "price_increment":"0.01",
1427
+ // "display_name":"ETH PERP",
1428
+ // "product_venue":"INTX",
1429
+ // "future_product_details":{
1430
+ // "venue":"",
1431
+ // "contract_code":"ETH",
1432
+ // "contract_expiry":null,
1433
+ // "contract_size":"1",
1434
+ // "contract_root_unit":"ETH",
1435
+ // "group_description":"",
1436
+ // "contract_expiry_timezone":"",
1437
+ // "group_short_description":"",
1438
+ // "risk_managed_by":"MANAGED_BY_VENUE",
1439
+ // "contract_expiry_type":"PERPETUAL",
1440
+ // "perpetual_details":{
1441
+ // "open_interest":"0",
1442
+ // "funding_rate":"0.000016",
1443
+ // "funding_time":"2024-04-09T09:00:00.000008Z",
1444
+ // "max_leverage":"10"
1445
+ // },
1446
+ // "contract_display_name":"ETH PERPETUAL"
1447
+ // }
1448
+ // }
1449
+ //
1450
+ const id = this.safeString(market, 'product_id');
1451
+ const futureProductDetails = this.safeDict(market, 'future_product_details', {});
1452
+ const contractExpiryType = this.safeString(futureProductDetails, 'contract_expiry_type');
1453
+ const contractSize = this.safeNumber(futureProductDetails, 'contract_size');
1454
+ const contractExpire = this.safeString(futureProductDetails, 'contract_expiry');
1455
+ const isSwap = (contractExpiryType === 'PERPETUAL');
1456
+ const baseId = this.safeString(futureProductDetails, 'contract_root_unit');
1457
+ const quoteId = this.safeString(market, 'quote_currency_id');
1458
+ const base = this.safeCurrencyCode(baseId);
1459
+ const quote = this.safeCurrencyCode(quoteId);
1460
+ const tradingDisabled = this.safeBool(market, 'is_disabled');
1461
+ let symbol = base + '/' + quote;
1462
+ let type = undefined;
1463
+ if (isSwap) {
1464
+ type = 'swap';
1465
+ symbol = symbol + ':' + quote;
1466
+ }
1467
+ else {
1468
+ type = 'future';
1469
+ symbol = symbol + ':' + quote + '-' + this.yymmdd(contractExpire);
1470
+ }
1471
+ const takerFeeRate = this.safeNumber(feeTier, 'taker_fee_rate');
1472
+ const makerFeeRate = this.safeNumber(feeTier, 'maker_fee_rate');
1473
+ const taker = takerFeeRate ? takerFeeRate : this.parseNumber('0.06');
1474
+ const maker = makerFeeRate ? makerFeeRate : this.parseNumber('0.04');
1475
+ return this.safeMarketStructure({
1476
+ 'id': id,
1477
+ 'symbol': symbol,
1478
+ 'base': base,
1479
+ 'quote': quote,
1480
+ 'settle': quote,
1481
+ 'baseId': baseId,
1482
+ 'quoteId': quoteId,
1483
+ 'settleId': quoteId,
1484
+ 'type': type,
1485
+ 'spot': false,
1486
+ 'margin': false,
1487
+ 'swap': isSwap,
1488
+ 'future': !isSwap,
1489
+ 'option': false,
1490
+ 'active': !tradingDisabled,
1491
+ 'contract': true,
1492
+ 'linear': true,
1493
+ 'inverse': false,
1494
+ 'taker': taker,
1495
+ 'maker': maker,
1496
+ 'contractSize': contractSize,
1497
+ 'expiry': this.parse8601(contractExpire),
1498
+ 'expiryDatetime': contractExpire,
1499
+ 'strike': undefined,
1500
+ 'optionType': undefined,
1501
+ 'precision': {
1502
+ 'amount': this.safeNumber(market, 'base_increment'),
1503
+ 'price': this.safeNumber2(market, 'price_increment', 'quote_increment'),
1504
+ },
1505
+ 'limits': {
1506
+ 'leverage': {
1507
+ 'min': undefined,
1508
+ 'max': undefined,
1256
1509
  },
1257
- 'limits': {
1258
- 'leverage': {
1259
- 'min': undefined,
1260
- 'max': undefined,
1261
- },
1262
- 'amount': {
1263
- 'min': this.safeNumber(market, 'base_min_size'),
1264
- 'max': this.safeNumber(market, 'base_max_size'),
1265
- },
1266
- 'price': {
1267
- 'min': undefined,
1268
- 'max': undefined,
1269
- },
1270
- 'cost': {
1271
- 'min': this.safeNumber(market, 'quote_min_size'),
1272
- 'max': this.safeNumber(market, 'quote_max_size'),
1273
- },
1510
+ 'amount': {
1511
+ 'min': this.safeNumber(market, 'base_min_size'),
1512
+ 'max': this.safeNumber(market, 'base_max_size'),
1274
1513
  },
1275
- 'created': undefined,
1276
- 'info': market,
1277
- });
1278
- }
1279
- return result;
1514
+ 'price': {
1515
+ 'min': undefined,
1516
+ 'max': undefined,
1517
+ },
1518
+ 'cost': {
1519
+ 'min': this.safeNumber(market, 'quote_min_size'),
1520
+ 'max': this.safeNumber(market, 'quote_max_size'),
1521
+ },
1522
+ },
1523
+ 'created': undefined,
1524
+ 'info': market,
1525
+ });
1280
1526
  }
1281
1527
  async fetchCurrenciesFromCache(params = {}) {
1282
1528
  const options = this.safeDict(this.options, 'fetchCurrencies', {});
@@ -1783,19 +2029,24 @@ class coinbase extends coinbase$1 {
1783
2029
  * @description query for balance and get the amount of funds available for trading or funds locked in orders
1784
2030
  * @see https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getaccounts
1785
2031
  * @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-accounts#list-accounts
2032
+ * @see https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmbalancesummary
1786
2033
  * @param {object} [params] extra parameters specific to the exchange API endpoint
1787
2034
  * @param {boolean} [params.v3] default false, set true to use v3 api endpoint
1788
- * @param {object} [params.type] "spot" (default) or "swap"
2035
+ * @param {object} [params.type] "spot" (default) or "swap" or "future"
1789
2036
  * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
1790
2037
  */
1791
2038
  await this.loadMarkets();
1792
2039
  const request = {};
1793
2040
  let response = undefined;
1794
2041
  const isV3 = this.safeBool(params, 'v3', false);
1795
- const type = this.safeString(params, 'type');
1796
- params = this.omit(params, ['v3', 'type']);
2042
+ params = this.omit(params, ['v3']);
2043
+ let marketType = undefined;
2044
+ [marketType, params] = this.handleMarketTypeAndParams('fetchBalance', undefined, params);
1797
2045
  const method = this.safeString(this.options, 'fetchBalance', 'v3PrivateGetBrokerageAccounts');
1798
- if ((isV3) || (method === 'v3PrivateGetBrokerageAccounts')) {
2046
+ if (marketType === 'future') {
2047
+ response = await this.v3PrivateGetBrokerageCfmBalanceSummary(this.extend(request, params));
2048
+ }
2049
+ else if ((isV3) || (method === 'v3PrivateGetBrokerageAccounts')) {
1799
2050
  request['limit'] = 250;
1800
2051
  response = await this.v3PrivateGetBrokerageAccounts(this.extend(request, params));
1801
2052
  }
@@ -1874,7 +2125,7 @@ class coinbase extends coinbase$1 {
1874
2125
  // "size": 9
1875
2126
  // }
1876
2127
  //
1877
- params['type'] = type;
2128
+ params['type'] = marketType;
1878
2129
  return this.parseCustomBalance(response, params);
1879
2130
  }
1880
2131
  async fetchLedger(code = undefined, since = undefined, limit = undefined, params = {}) {
@@ -2335,6 +2586,11 @@ class coinbase extends coinbase$1 {
2335
2586
  * @param {string} [params.end_time] '2023-05-25T17:01:05.092Z' for 'GTD' orders
2336
2587
  * @param {float} [params.cost] *spot market buy only* the quote quantity that can be used as an alternative for the amount
2337
2588
  * @param {boolean} [params.preview] default to false, wether to use the test/preview endpoint or not
2589
+ * @param {float} [params.leverage] default to 1, the leverage to use for the order
2590
+ * @param {string} [params.marginMode] 'cross' or 'isolated'
2591
+ * @param {string} [params.retail_portfolio_id] portfolio uid
2592
+ * @param {boolean} [params.is_max] Used in conjunction with tradable_balance to indicate the user wants to use their entire tradable balance
2593
+ * @param {string} [params.tradable_balance] amount of tradable balance
2338
2594
  * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
2339
2595
  */
2340
2596
  await this.loadMarkets();
@@ -2445,7 +2701,7 @@ class coinbase extends coinbase$1 {
2445
2701
  if (isStop || isStopLoss || isTakeProfit) {
2446
2702
  throw new errors.NotSupported(this.id + ' createOrder() only stop limit orders are supported');
2447
2703
  }
2448
- if (side === 'buy') {
2704
+ if (market['spot'] && (side === 'buy')) {
2449
2705
  let total = undefined;
2450
2706
  let createMarketBuyOrderRequiresPrice = true;
2451
2707
  [createMarketBuyOrderRequiresPrice, params] = this.handleOptionAndParams(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
@@ -2482,7 +2738,16 @@ class coinbase extends coinbase$1 {
2482
2738
  };
2483
2739
  }
2484
2740
  }
2485
- params = this.omit(params, ['timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'stopPrice', 'stop_price', 'stopDirection', 'stop_direction', 'clientOrderId', 'postOnly', 'post_only', 'end_time']);
2741
+ const marginMode = this.safeString(params, 'marginMode');
2742
+ if (marginMode !== undefined) {
2743
+ if (marginMode === 'isolated') {
2744
+ request['margin_type'] = 'ISOLATED';
2745
+ }
2746
+ else if (marginMode === 'cross') {
2747
+ request['margin_type'] = 'CROSS';
2748
+ }
2749
+ }
2750
+ params = this.omit(params, ['timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'stopPrice', 'stop_price', 'stopDirection', 'stop_direction', 'clientOrderId', 'postOnly', 'post_only', 'end_time', 'marginMode']);
2486
2751
  const preview = this.safeBool2(params, 'preview', 'test', false);
2487
2752
  let response = undefined;
2488
2753
  if (preview) {
@@ -3770,6 +4035,252 @@ class coinbase extends coinbase$1 {
3770
4035
  const data = this.safeDict(response, 'data', {});
3771
4036
  return this.parseTransaction(data);
3772
4037
  }
4038
+ async closePosition(symbol, side = undefined, params = {}) {
4039
+ /**
4040
+ * @method
4041
+ * @name coinbase#closePosition
4042
+ * @description *futures only* closes open positions for a market
4043
+ * @see https://coinbase-api.github.io/docs/#/en-us/swapV2/trade-api.html#One-Click%20Close%20All%20Positions
4044
+ * @param {string} symbol Unified CCXT market symbol
4045
+ * @param {string} [side] not used by coinbase
4046
+ * @param {object} [params] extra parameters specific to the coinbase api endpoint
4047
+ * @param {string} params.clientOrderId *mandatory* the client order id of the position to close
4048
+ * @param {float} [params.size] the size of the position to close, optional
4049
+ * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
4050
+ */
4051
+ await this.loadMarkets();
4052
+ const market = this.market(symbol);
4053
+ if (!market['future']) {
4054
+ throw new errors.NotSupported(this.id + ' closePosition() only supported for futures markets');
4055
+ }
4056
+ const clientOrderId = this.safeString2(params, 'client_order_id', 'clientOrderId');
4057
+ params = this.omit(params, 'clientOrderId');
4058
+ const request = {
4059
+ 'product_id': market['id'],
4060
+ };
4061
+ if (clientOrderId === undefined) {
4062
+ throw new errors.ArgumentsRequired(this.id + ' closePosition() requires a clientOrderId parameter');
4063
+ }
4064
+ request['client_order_id'] = clientOrderId;
4065
+ const response = await this.v3PrivatePostBrokerageOrdersClosePosition(this.extend(request, params));
4066
+ const order = this.safeDict(response, 'success_response', {});
4067
+ return this.parseOrder(order);
4068
+ }
4069
+ async fetchPositions(symbols = undefined, params = {}) {
4070
+ /**
4071
+ * @method
4072
+ * @name coinbase#fetchPositions
4073
+ * @description fetch all open positions
4074
+ * @see https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmpositions
4075
+ * @see https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getintxpositions
4076
+ * @param {string[]} [symbols] list of unified market symbols
4077
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
4078
+ * @param {string} [params.portfolio] the portfolio UUID to fetch positions for
4079
+ * @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/#/?id=position-structure}
4080
+ */
4081
+ await this.loadMarkets();
4082
+ symbols = this.marketSymbols(symbols);
4083
+ let market = undefined;
4084
+ if (symbols !== undefined) {
4085
+ market = this.market(symbols[0]);
4086
+ }
4087
+ let type = undefined;
4088
+ [type, params] = this.handleMarketTypeAndParams('fetchPositions', market, params);
4089
+ let response = undefined;
4090
+ if (type === 'future') {
4091
+ response = await this.v3PrivateGetBrokerageCfmPositions(params);
4092
+ }
4093
+ else {
4094
+ let portfolio = undefined;
4095
+ [portfolio, params] = this.handleOptionAndParams(params, 'fetchPositions', 'portfolio');
4096
+ if (portfolio === undefined) {
4097
+ throw new errors.ArgumentsRequired(this.id + ' fetchPositions() requires a "portfolio" value in params (eg: dbcb91e7-2bc9-515), or set as exchange.options["portfolio"]. You can get a list of portfolios with fetchPortfolios()');
4098
+ }
4099
+ const request = {
4100
+ 'portfolio_uuid': portfolio,
4101
+ };
4102
+ response = await this.v3PrivateGetBrokerageIntxPositionsPortfolioUuid(this.extend(request, params));
4103
+ }
4104
+ const positions = this.safeList(response, 'positions', []);
4105
+ return this.parsePositions(positions, symbols);
4106
+ }
4107
+ async fetchPosition(symbol, params = {}) {
4108
+ /**
4109
+ * @method
4110
+ * @name coinbase#fetchPosition
4111
+ * @description fetch data on a single open contract trade position
4112
+ * @see https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getintxposition
4113
+ * @see https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmposition
4114
+ * @param {string} symbol unified market symbol of the market the position is held in, default is undefined
4115
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
4116
+ * @param {string} [params.product_id] *futures only* the product id of the position to fetch, required for futures markets only
4117
+ * @param {string} [params.portfolio] *perpetual/swaps only* the portfolio UUID to fetch the position for, required for perpetual/swaps markets only
4118
+ * @returns {object} a [position structure]{@link https://docs.ccxt.com/#/?id=position-structure}
4119
+ */
4120
+ await this.loadMarkets();
4121
+ const market = this.market(symbol);
4122
+ let response = undefined;
4123
+ if (market['future']) {
4124
+ const productId = this.safeString(market, 'product_id');
4125
+ if (productId === undefined) {
4126
+ throw new errors.ArgumentsRequired(this.id + ' fetchPosition() requires a "product_id" in params');
4127
+ }
4128
+ const futureRequest = {
4129
+ 'product_id': productId,
4130
+ };
4131
+ response = await this.v3PrivateGetBrokerageCfmPositionsProductId(this.extend(futureRequest, params));
4132
+ }
4133
+ else {
4134
+ let portfolio = undefined;
4135
+ [portfolio, params] = this.handleOptionAndParams(params, 'fetchPositions', 'portfolio');
4136
+ if (portfolio === undefined) {
4137
+ throw new errors.ArgumentsRequired(this.id + ' fetchPosition() requires a "portfolio" value in params (eg: dbcb91e7-2bc9-515), or set as exchange.options["portfolio"]. You can get a list of portfolios with fetchPortfolios()');
4138
+ }
4139
+ const request = {
4140
+ 'symbol': market['id'],
4141
+ 'portfolio_uuid': portfolio,
4142
+ };
4143
+ response = await this.v3PrivateGetBrokerageIntxPositionsPortfolioUuidSymbol(this.extend(request, params));
4144
+ }
4145
+ const position = this.safeDict(response, 'position', {});
4146
+ return this.parsePosition(position, market);
4147
+ }
4148
+ parsePosition(position, market = undefined) {
4149
+ //
4150
+ // {
4151
+ // "product_id": "1r4njf84-0-0",
4152
+ // "product_uuid": "cd34c18b-3665-4ed8-9305-3db277c49fc5",
4153
+ // "symbol": "ADA-PERP-INTX",
4154
+ // "vwap": {
4155
+ // "value": "0.6171",
4156
+ // "currency": "USDC"
4157
+ // },
4158
+ // "position_side": "POSITION_SIDE_LONG",
4159
+ // "net_size": "20",
4160
+ // "buy_order_size": "0",
4161
+ // "sell_order_size": "0",
4162
+ // "im_contribution": "0.1",
4163
+ // "unrealized_pnl": {
4164
+ // "value": "0.074",
4165
+ // "currency": "USDC"
4166
+ // },
4167
+ // "mark_price": {
4168
+ // "value": "0.6208",
4169
+ // "currency": "USDC"
4170
+ // },
4171
+ // "liquidation_price": {
4172
+ // "value": "0",
4173
+ // "currency": "USDC"
4174
+ // },
4175
+ // "leverage": "1",
4176
+ // "im_notional": {
4177
+ // "value": "12.342",
4178
+ // "currency": "USDC"
4179
+ // },
4180
+ // "mm_notional": {
4181
+ // "value": "0.814572",
4182
+ // "currency": "USDC"
4183
+ // },
4184
+ // "position_notional": {
4185
+ // "value": "12.342",
4186
+ // "currency": "USDC"
4187
+ // },
4188
+ // "margin_type": "MARGIN_TYPE_CROSS",
4189
+ // "liquidation_buffer": "19.677828",
4190
+ // "liquidation_percentage": "4689.3506",
4191
+ // "portfolio_summary": {
4192
+ // "portfolio_uuid": "018ebd63-1f6d-7c8e-ada9-0761c5a2235f",
4193
+ // "collateral": "20.4184",
4194
+ // "position_notional": "12.342",
4195
+ // "open_position_notional": "12.342",
4196
+ // "pending_fees": "0",
4197
+ // "borrow": "0",
4198
+ // "accrued_interest": "0",
4199
+ // "rolling_debt": "0",
4200
+ // "portfolio_initial_margin": "0.1",
4201
+ // "portfolio_im_notional": {
4202
+ // "value": "12.342",
4203
+ // "currency": "USDC"
4204
+ // },
4205
+ // "portfolio_maintenance_margin": "0.066",
4206
+ // "portfolio_mm_notional": {
4207
+ // "value": "0.814572",
4208
+ // "currency": "USDC"
4209
+ // },
4210
+ // "liquidation_percentage": "4689.3506",
4211
+ // "liquidation_buffer": "19.677828",
4212
+ // "margin_type": "MARGIN_TYPE_CROSS",
4213
+ // "margin_flags": "PORTFOLIO_MARGIN_FLAGS_UNSPECIFIED",
4214
+ // "liquidation_status": "PORTFOLIO_LIQUIDATION_STATUS_NOT_LIQUIDATING",
4215
+ // "unrealized_pnl": {
4216
+ // "value": "0.074",
4217
+ // "currency": "USDC"
4218
+ // },
4219
+ // "buying_power": {
4220
+ // "value": "8.1504",
4221
+ // "currency": "USDC"
4222
+ // },
4223
+ // "total_balance": {
4224
+ // "value": "20.4924",
4225
+ // "currency": "USDC"
4226
+ // },
4227
+ // "max_withdrawal": {
4228
+ // "value": "8.0764",
4229
+ // "currency": "USDC"
4230
+ // }
4231
+ // },
4232
+ // "entry_vwap": {
4233
+ // "value": "0.6091",
4234
+ // "currency": "USDC"
4235
+ // }
4236
+ // }
4237
+ //
4238
+ const marketId = this.safeString(position, 'symbol', '');
4239
+ market = this.safeMarket(marketId, market);
4240
+ const rawMargin = this.safeString(position, 'margin_type');
4241
+ let marginMode = undefined;
4242
+ if (rawMargin !== undefined) {
4243
+ marginMode = (rawMargin === 'MARGIN_TYPE_CROSS') ? 'cross' : 'isolated';
4244
+ }
4245
+ const notionalObject = this.safeDict(position, 'position_notional', {});
4246
+ const positionSide = this.safeString(position, 'position_side');
4247
+ const side = (positionSide === 'POSITION_SIDE_LONG') ? 'long' : 'short';
4248
+ const unrealizedPNLObject = this.safeDict(position, 'unrealized_pnl', {});
4249
+ const liquidationPriceObject = this.safeDict(position, 'liquidation_price', {});
4250
+ const liquidationPrice = this.safeNumber(liquidationPriceObject, 'value');
4251
+ const vwapObject = this.safeDict(position, 'vwap', {});
4252
+ const summaryObject = this.safeDict(position, 'portfolio_summary', {});
4253
+ return this.safePosition({
4254
+ 'info': position,
4255
+ 'id': this.safeString(position, 'product_id'),
4256
+ 'symbol': this.safeSymbol(marketId, market),
4257
+ 'notional': this.safeNumber(notionalObject, 'value'),
4258
+ 'marginMode': marginMode,
4259
+ 'liquidationPrice': liquidationPrice,
4260
+ 'entryPrice': this.safeNumber(vwapObject, 'value'),
4261
+ 'unrealizedPnl': this.safeNumber(unrealizedPNLObject, 'value'),
4262
+ 'realizedPnl': undefined,
4263
+ 'percentage': undefined,
4264
+ 'contracts': this.safeNumber(position, 'net_size'),
4265
+ 'contractSize': market['contractSize'],
4266
+ 'markPrice': undefined,
4267
+ 'lastPrice': undefined,
4268
+ 'side': side,
4269
+ 'hedged': undefined,
4270
+ 'timestamp': undefined,
4271
+ 'datetime': undefined,
4272
+ 'lastUpdateTimestamp': undefined,
4273
+ 'maintenanceMargin': undefined,
4274
+ 'maintenanceMarginPercentage': undefined,
4275
+ 'collateral': this.safeNumber(summaryObject, 'collateral'),
4276
+ 'initialMargin': undefined,
4277
+ 'initialMarginPercentage': undefined,
4278
+ 'leverage': this.safeNumber(position, 'leverage'),
4279
+ 'marginRatio': undefined,
4280
+ 'stopLossPrice': undefined,
4281
+ 'takeProfitPrice': undefined,
4282
+ });
4283
+ }
3773
4284
  sign(path, api = [], method = 'GET', params = {}, headers = undefined, body = undefined) {
3774
4285
  const version = api[0];
3775
4286
  const signed = api[1] === 'private';
@@ -3822,7 +4333,9 @@ class coinbase extends coinbase$1 {
3822
4333
  // it may not work for v2
3823
4334
  let uri = method + ' ' + url.replace('https://', '');
3824
4335
  const quesPos = uri.indexOf('?');
3825
- if (quesPos >= 0) {
4336
+ // Due to we use mb_strpos, quesPos could be false in php. In that case, the quesPos >= 0 is true
4337
+ // Also it's not possible that the question mark is first character, only check > 0 here.
4338
+ if (quesPos > 0) {
3826
4339
  uri = uri.slice(0, quesPos);
3827
4340
  }
3828
4341
  const nonce = this.randomBytes(16);