ccxt 4.2.95 → 4.2.97

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