ccxt 4.2.95__py2.py3-none-any.whl → 4.2.97__py2.py3-none-any.whl

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.
ccxt/coinbase.py CHANGED
@@ -6,10 +6,11 @@
6
6
  from ccxt.base.exchange import Exchange
7
7
  from ccxt.abstract.coinbase import ImplicitAPI
8
8
  import hashlib
9
- from ccxt.base.types import Account, Balances, Currencies, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction
9
+ from ccxt.base.types import Account, Balances, Currencies, Currency, Int, Market, MarketInterface, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction
10
10
  from typing import List
11
11
  from ccxt.base.errors import ExchangeError
12
12
  from ccxt.base.errors import AuthenticationError
13
+ from ccxt.base.errors import PermissionDenied
13
14
  from ccxt.base.errors import ArgumentsRequired
14
15
  from ccxt.base.errors import BadRequest
15
16
  from ccxt.base.errors import InvalidOrder
@@ -51,7 +52,7 @@ class coinbase(Exchange, ImplicitAPI):
51
52
  'cancelOrder': True,
52
53
  'cancelOrders': True,
53
54
  'closeAllPositions': False,
54
- 'closePosition': False,
55
+ 'closePosition': True,
55
56
  'createDepositAddress': True,
56
57
  'createLimitBuyOrder': True,
57
58
  'createLimitSellOrder': True,
@@ -106,9 +107,9 @@ class coinbase(Exchange, ImplicitAPI):
106
107
  'fetchOrder': True,
107
108
  'fetchOrderBook': True,
108
109
  'fetchOrders': True,
109
- 'fetchPosition': False,
110
+ 'fetchPosition': True,
110
111
  'fetchPositionMode': False,
111
- 'fetchPositions': False,
112
+ 'fetchPositions': True,
112
113
  'fetchPositionsRisk': False,
113
114
  'fetchPremiumIndexOHLCV': False,
114
115
  'fetchTicker': True,
@@ -251,6 +252,8 @@ class coinbase(Exchange, ImplicitAPI):
251
252
  'brokerage/convert/trade/{trade_id}': 1,
252
253
  'brokerage/cfm/sweeps/schedule': 1,
253
254
  'brokerage/intx/allocate': 1,
255
+ # futures
256
+ 'brokerage/orders/close_position': 1,
254
257
  },
255
258
  'put': {
256
259
  'brokerage/portfolios/{portfolio_uuid}': 1,
@@ -317,6 +320,8 @@ class coinbase(Exchange, ImplicitAPI):
317
320
  'internal_server_error': ExchangeError, # 500 Internal server error
318
321
  'UNSUPPORTED_ORDER_CONFIGURATION': BadRequest,
319
322
  'INSUFFICIENT_FUND': BadRequest,
323
+ 'PERMISSION_DENIED': PermissionDenied,
324
+ 'INVALID_ARGUMENT': BadRequest,
320
325
  },
321
326
  'broad': {
322
327
  'request timestamp expired': InvalidNonce, # {"errors":[{"id":"authentication_error","message":"request timestamp expired"}]}
@@ -531,6 +536,26 @@ class coinbase(Exchange, ImplicitAPI):
531
536
  accounts[lastIndex] = last
532
537
  return self.parse_accounts(accounts, params)
533
538
 
539
+ def fetch_portfolios(self, params={}) -> List[Account]:
540
+ """
541
+ fetch all the portfolios
542
+ :see: https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getportfolios
543
+ :param dict [params]: extra parameters specific to the exchange API endpoint
544
+ :returns dict: a dictionary of `account structures <https://docs.ccxt.com/#/?id=account-structure>` indexed by the account type
545
+ """
546
+ response = self.v3PrivateGetBrokeragePortfolios(params)
547
+ portfolios = self.safe_list(response, 'portfolios', [])
548
+ result = []
549
+ for i in range(0, len(portfolios)):
550
+ portfolio = portfolios[i]
551
+ result.append({
552
+ 'id': self.safe_string(portfolio, 'uuid'),
553
+ 'type': self.safe_string(portfolio, 'type'),
554
+ 'code': None,
555
+ 'info': portfolio,
556
+ })
557
+ return result
558
+
534
559
  def parse_account(self, account):
535
560
  #
536
561
  # fetchAccountsV2
@@ -1115,15 +1140,68 @@ class coinbase(Exchange, ImplicitAPI):
1115
1140
  return result
1116
1141
 
1117
1142
  def fetch_markets_v3(self, params={}):
1118
- promisesUnresolved = [
1143
+ spotUnresolvedPromises = [
1119
1144
  self.v3PrivateGetBrokerageProducts(params),
1120
1145
  self.v3PrivateGetBrokerageTransactionSummary(params),
1121
1146
  ]
1122
- # response = self.v3PrivateGetBrokerageProducts(params)
1123
- promises = promisesUnresolved
1124
- response = self.safe_dict(promises, 0, {})
1147
+ unresolvedContractPromises = []
1148
+ try:
1149
+ unresolvedContractPromises = [
1150
+ self.v3PrivateGetBrokerageProducts(self.extend(params, {'product_type': 'FUTURE'})),
1151
+ self.v3PrivateGetBrokerageProducts(self.extend(params, {'product_type': 'FUTURE', 'contract_expiry_type': 'PERPETUAL'})),
1152
+ self.v3PrivateGetBrokerageTransactionSummary(self.extend(params, {'product_type': 'FUTURE'})),
1153
+ self.v3PrivateGetBrokerageTransactionSummary(self.extend(params, {'product_type': 'FUTURE', 'contract_expiry_type': 'PERPETUAL'})),
1154
+ ]
1155
+ except Exception as e:
1156
+ unresolvedContractPromises = [] # the sync version of ccxt won't have the promise.all line so the request is made here
1157
+ promises = spotUnresolvedPromises
1158
+ contractPromises = None
1159
+ try:
1160
+ contractPromises = unresolvedContractPromises # some users don't have access to contracts
1161
+ except Exception as e:
1162
+ contractPromises = []
1163
+ spot = self.safe_dict(promises, 0, {})
1164
+ fees = self.safe_dict(promises, 1, {})
1165
+ expiringFutures = self.safe_dict(contractPromises, 0, {})
1166
+ perpetualFutures = self.safe_dict(contractPromises, 1, {})
1167
+ expiringFees = self.safe_dict(contractPromises, 2, {})
1168
+ perpetualFees = self.safe_dict(contractPromises, 3, {})
1169
+ #
1170
+ # {
1171
+ # "total_volume": 0,
1172
+ # "total_fees": 0,
1173
+ # "fee_tier": {
1174
+ # "pricing_tier": "",
1175
+ # "usd_from": "0",
1176
+ # "usd_to": "10000",
1177
+ # "taker_fee_rate": "0.006",
1178
+ # "maker_fee_rate": "0.004"
1179
+ # },
1180
+ # "margin_rate": null,
1181
+ # "goods_and_services_tax": null,
1182
+ # "advanced_trade_only_volume": 0,
1183
+ # "advanced_trade_only_fees": 0,
1184
+ # "coinbase_pro_volume": 0,
1185
+ # "coinbase_pro_fees": 0
1186
+ # }
1187
+ #
1188
+ feeTier = self.safe_dict(fees, 'fee_tier', {})
1189
+ expiringFeeTier = self.safe_dict(expiringFees, 'fee_tier', {}) # fee tier null?
1190
+ perpetualFeeTier = self.safe_dict(perpetualFees, 'fee_tier', {}) # fee tier null?
1191
+ data = self.safe_list(spot, 'products', [])
1192
+ result = []
1193
+ for i in range(0, len(data)):
1194
+ result.append(self.parse_spot_market(data[i], feeTier))
1195
+ futureData = self.safe_list(expiringFutures, 'products', [])
1196
+ for i in range(0, len(futureData)):
1197
+ result.append(self.parse_contract_market(futureData[i], expiringFeeTier))
1198
+ perpetualData = self.safe_list(perpetualFutures, 'products', [])
1199
+ for i in range(0, len(perpetualData)):
1200
+ result.append(self.parse_contract_market(perpetualData[i], perpetualFeeTier))
1201
+ return result
1202
+
1203
+ def parse_spot_market(self, market, feeTier) -> MarketInterface:
1125
1204
  #
1126
- # [
1127
1205
  # {
1128
1206
  # "product_id": "TONE-USD",
1129
1207
  # "price": "0.01523",
@@ -1152,96 +1230,260 @@ class coinbase(Exchange, ImplicitAPI):
1152
1230
  # "base_currency_id": "TONE",
1153
1231
  # "fcm_trading_session_details": null,
1154
1232
  # "mid_market_price": ""
1155
- # },
1156
- # ...
1157
- # ]
1233
+ # }
1158
1234
  #
1159
- # fees = self.v3PrivateGetBrokerageTransactionSummary(params)
1160
- fees = self.safe_dict(promises, 1, {})
1235
+ id = self.safe_string(market, 'product_id')
1236
+ baseId = self.safe_string(market, 'base_currency_id')
1237
+ quoteId = self.safe_string(market, 'quote_currency_id')
1238
+ base = self.safe_currency_code(baseId)
1239
+ quote = self.safe_currency_code(quoteId)
1240
+ marketType = self.safe_string_lower(market, 'product_type')
1241
+ tradingDisabled = self.safe_bool(market, 'trading_disabled')
1242
+ stablePairs = self.safe_list(self.options, 'stablePairs', [])
1243
+ return self.safe_market_structure({
1244
+ 'id': id,
1245
+ 'symbol': base + '/' + quote,
1246
+ 'base': base,
1247
+ 'quote': quote,
1248
+ 'settle': None,
1249
+ 'baseId': baseId,
1250
+ 'quoteId': quoteId,
1251
+ 'settleId': None,
1252
+ 'type': marketType,
1253
+ 'spot': (marketType == 'spot'),
1254
+ 'margin': None,
1255
+ 'swap': False,
1256
+ 'future': False,
1257
+ 'option': False,
1258
+ 'active': not tradingDisabled,
1259
+ 'contract': False,
1260
+ 'linear': None,
1261
+ 'inverse': None,
1262
+ 'taker': 0.00001 if self.in_array(id, stablePairs) else self.safe_number(feeTier, 'taker_fee_rate'),
1263
+ 'maker': 0.0 if self.in_array(id, stablePairs) else self.safe_number(feeTier, 'maker_fee_rate'),
1264
+ 'contractSize': None,
1265
+ 'expiry': None,
1266
+ 'expiryDatetime': None,
1267
+ 'strike': None,
1268
+ 'optionType': None,
1269
+ 'precision': {
1270
+ 'amount': self.safe_number(market, 'base_increment'),
1271
+ 'price': self.safe_number_2(market, 'price_increment', 'quote_increment'),
1272
+ },
1273
+ 'limits': {
1274
+ 'leverage': {
1275
+ 'min': None,
1276
+ 'max': None,
1277
+ },
1278
+ 'amount': {
1279
+ 'min': self.safe_number(market, 'base_min_size'),
1280
+ 'max': self.safe_number(market, 'base_max_size'),
1281
+ },
1282
+ 'price': {
1283
+ 'min': None,
1284
+ 'max': None,
1285
+ },
1286
+ 'cost': {
1287
+ 'min': self.safe_number(market, 'quote_min_size'),
1288
+ 'max': self.safe_number(market, 'quote_max_size'),
1289
+ },
1290
+ },
1291
+ 'created': None,
1292
+ 'info': market,
1293
+ })
1294
+
1295
+ def parse_contract_market(self, market, feeTier) -> MarketInterface:
1296
+ # expiring
1161
1297
  #
1162
- # {
1163
- # "total_volume": 0,
1164
- # "total_fees": 0,
1165
- # "fee_tier": {
1166
- # "pricing_tier": "",
1167
- # "usd_from": "0",
1168
- # "usd_to": "10000",
1169
- # "taker_fee_rate": "0.006",
1170
- # "maker_fee_rate": "0.004"
1171
- # },
1172
- # "margin_rate": null,
1173
- # "goods_and_services_tax": null,
1174
- # "advanced_trade_only_volume": 0,
1175
- # "advanced_trade_only_fees": 0,
1176
- # "coinbase_pro_volume": 0,
1177
- # "coinbase_pro_fees": 0
1178
- # }
1298
+ # {
1299
+ # "product_id":"BIT-26APR24-CDE",
1300
+ # "price":"71145",
1301
+ # "price_percentage_change_24h":"-2.36722931247427",
1302
+ # "volume_24h":"108549",
1303
+ # "volume_percentage_change_24h":"155.78255337197794",
1304
+ # "base_increment":"1",
1305
+ # "quote_increment":"0.01",
1306
+ # "quote_min_size":"0",
1307
+ # "quote_max_size":"100000000",
1308
+ # "base_min_size":"1",
1309
+ # "base_max_size":"100000000",
1310
+ # "base_name":"",
1311
+ # "quote_name":"US Dollar",
1312
+ # "watched":false,
1313
+ # "is_disabled":false,
1314
+ # "new":false,
1315
+ # "status":"",
1316
+ # "cancel_only":false,
1317
+ # "limit_only":false,
1318
+ # "post_only":false,
1319
+ # "trading_disabled":false,
1320
+ # "auction_mode":false,
1321
+ # "product_type":"FUTURE",
1322
+ # "quote_currency_id":"USD",
1323
+ # "base_currency_id":"",
1324
+ # "fcm_trading_session_details":{
1325
+ # "is_session_open":true,
1326
+ # "open_time":"2024-04-08T22:00:00Z",
1327
+ # "close_time":"2024-04-09T21:00:00Z"
1328
+ # },
1329
+ # "mid_market_price":"71105",
1330
+ # "alias":"",
1331
+ # "alias_to":[
1332
+ # ],
1333
+ # "base_display_symbol":"",
1334
+ # "quote_display_symbol":"USD",
1335
+ # "view_only":false,
1336
+ # "price_increment":"5",
1337
+ # "display_name":"BTC 26 APR 24",
1338
+ # "product_venue":"FCM",
1339
+ # "future_product_details":{
1340
+ # "venue":"cde",
1341
+ # "contract_code":"BIT",
1342
+ # "contract_expiry":"2024-04-26T15:00:00Z",
1343
+ # "contract_size":"0.01",
1344
+ # "contract_root_unit":"BTC",
1345
+ # "group_description":"Nano Bitcoin Futures",
1346
+ # "contract_expiry_timezone":"Europe/London",
1347
+ # "group_short_description":"Nano BTC",
1348
+ # "risk_managed_by":"MANAGED_BY_FCM",
1349
+ # "contract_expiry_type":"EXPIRING",
1350
+ # "contract_display_name":"BTC 26 APR 24"
1351
+ # }
1352
+ # }
1179
1353
  #
1180
- feeTier = self.safe_dict(fees, 'fee_tier', {})
1181
- data = self.safe_list(response, 'products', [])
1182
- result = []
1183
- for i in range(0, len(data)):
1184
- market = data[i]
1185
- id = self.safe_string(market, 'product_id')
1186
- baseId = self.safe_string(market, 'base_currency_id')
1187
- quoteId = self.safe_string(market, 'quote_currency_id')
1188
- base = self.safe_currency_code(baseId)
1189
- quote = self.safe_currency_code(quoteId)
1190
- marketType = self.safe_string_lower(market, 'product_type')
1191
- tradingDisabled = self.safe_bool(market, 'trading_disabled')
1192
- stablePairs = self.safe_list(self.options, 'stablePairs', [])
1193
- result.append({
1194
- 'id': id,
1195
- 'symbol': base + '/' + quote,
1196
- 'base': base,
1197
- 'quote': quote,
1198
- 'settle': None,
1199
- 'baseId': baseId,
1200
- 'quoteId': quoteId,
1201
- 'settleId': None,
1202
- 'type': marketType,
1203
- 'spot': (marketType == 'spot'),
1204
- 'margin': None,
1205
- 'swap': False,
1206
- 'future': False,
1207
- 'option': False,
1208
- 'active': not tradingDisabled,
1209
- 'contract': False,
1210
- 'linear': None,
1211
- 'inverse': None,
1212
- 'taker': 0.00001 if self.in_array(id, stablePairs) else self.safe_number(feeTier, 'taker_fee_rate'),
1213
- 'maker': 0.0 if self.in_array(id, stablePairs) else self.safe_number(feeTier, 'maker_fee_rate'),
1214
- 'contractSize': None,
1215
- 'expiry': None,
1216
- 'expiryDatetime': None,
1217
- 'strike': None,
1218
- 'optionType': None,
1219
- 'precision': {
1220
- 'amount': self.safe_number(market, 'base_increment'),
1221
- 'price': self.safe_number_2(market, 'price_increment', 'quote_increment'),
1354
+ # perpetual
1355
+ #
1356
+ # {
1357
+ # "product_id":"ETH-PERP-INTX",
1358
+ # "price":"3630.98",
1359
+ # "price_percentage_change_24h":"0.65142426292038",
1360
+ # "volume_24h":"114020.1501",
1361
+ # "volume_percentage_change_24h":"63.33650787154869",
1362
+ # "base_increment":"0.0001",
1363
+ # "quote_increment":"0.01",
1364
+ # "quote_min_size":"10",
1365
+ # "quote_max_size":"50000000",
1366
+ # "base_min_size":"0.0001",
1367
+ # "base_max_size":"50000",
1368
+ # "base_name":"",
1369
+ # "quote_name":"USDC",
1370
+ # "watched":false,
1371
+ # "is_disabled":false,
1372
+ # "new":false,
1373
+ # "status":"",
1374
+ # "cancel_only":false,
1375
+ # "limit_only":false,
1376
+ # "post_only":false,
1377
+ # "trading_disabled":false,
1378
+ # "auction_mode":false,
1379
+ # "product_type":"FUTURE",
1380
+ # "quote_currency_id":"USDC",
1381
+ # "base_currency_id":"",
1382
+ # "fcm_trading_session_details":null,
1383
+ # "mid_market_price":"3630.975",
1384
+ # "alias":"",
1385
+ # "alias_to":[],
1386
+ # "base_display_symbol":"",
1387
+ # "quote_display_symbol":"USDC",
1388
+ # "view_only":false,
1389
+ # "price_increment":"0.01",
1390
+ # "display_name":"ETH PERP",
1391
+ # "product_venue":"INTX",
1392
+ # "future_product_details":{
1393
+ # "venue":"",
1394
+ # "contract_code":"ETH",
1395
+ # "contract_expiry":null,
1396
+ # "contract_size":"1",
1397
+ # "contract_root_unit":"ETH",
1398
+ # "group_description":"",
1399
+ # "contract_expiry_timezone":"",
1400
+ # "group_short_description":"",
1401
+ # "risk_managed_by":"MANAGED_BY_VENUE",
1402
+ # "contract_expiry_type":"PERPETUAL",
1403
+ # "perpetual_details":{
1404
+ # "open_interest":"0",
1405
+ # "funding_rate":"0.000016",
1406
+ # "funding_time":"2024-04-09T09:00:00.000008Z",
1407
+ # "max_leverage":"10"
1408
+ # },
1409
+ # "contract_display_name":"ETH PERPETUAL"
1410
+ # }
1411
+ # }
1412
+ #
1413
+ id = self.safe_string(market, 'product_id')
1414
+ futureProductDetails = self.safe_dict(market, 'future_product_details', {})
1415
+ contractExpiryType = self.safe_string(futureProductDetails, 'contract_expiry_type')
1416
+ contractSize = self.safe_number(futureProductDetails, 'contract_size')
1417
+ contractExpire = self.safe_string(futureProductDetails, 'contract_expiry')
1418
+ isSwap = (contractExpiryType == 'PERPETUAL')
1419
+ baseId = self.safe_string(futureProductDetails, 'contract_root_unit')
1420
+ quoteId = self.safe_string(market, 'quote_currency_id')
1421
+ base = self.safe_currency_code(baseId)
1422
+ quote = self.safe_currency_code(quoteId)
1423
+ tradingDisabled = self.safe_bool(market, 'is_disabled')
1424
+ symbol = base + '/' + quote
1425
+ type = None
1426
+ if isSwap:
1427
+ type = 'swap'
1428
+ symbol = symbol + ':' + quote
1429
+ else:
1430
+ type = 'future'
1431
+ symbol = symbol + ':' + quote + '-' + self.yymmdd(contractExpire)
1432
+ takerFeeRate = self.safe_number(feeTier, 'taker_fee_rate')
1433
+ makerFeeRate = self.safe_number(feeTier, 'maker_fee_rate')
1434
+ taker = takerFeeRate if takerFeeRate else self.parse_number('0.06')
1435
+ maker = makerFeeRate if makerFeeRate else self.parse_number('0.04')
1436
+ return self.safe_market_structure({
1437
+ 'id': id,
1438
+ 'symbol': symbol,
1439
+ 'base': base,
1440
+ 'quote': quote,
1441
+ 'settle': quote,
1442
+ 'baseId': baseId,
1443
+ 'quoteId': quoteId,
1444
+ 'settleId': quoteId,
1445
+ 'type': type,
1446
+ 'spot': False,
1447
+ 'margin': False,
1448
+ 'swap': isSwap,
1449
+ 'future': not isSwap,
1450
+ 'option': False,
1451
+ 'active': not tradingDisabled,
1452
+ 'contract': True,
1453
+ 'linear': True,
1454
+ 'inverse': False,
1455
+ 'taker': taker,
1456
+ 'maker': maker,
1457
+ 'contractSize': contractSize,
1458
+ 'expiry': self.parse8601(contractExpire),
1459
+ 'expiryDatetime': contractExpire,
1460
+ 'strike': None,
1461
+ 'optionType': None,
1462
+ 'precision': {
1463
+ 'amount': self.safe_number(market, 'base_increment'),
1464
+ 'price': self.safe_number_2(market, 'price_increment', 'quote_increment'),
1465
+ },
1466
+ 'limits': {
1467
+ 'leverage': {
1468
+ 'min': None,
1469
+ 'max': None,
1222
1470
  },
1223
- 'limits': {
1224
- 'leverage': {
1225
- 'min': None,
1226
- 'max': None,
1227
- },
1228
- 'amount': {
1229
- 'min': self.safe_number(market, 'base_min_size'),
1230
- 'max': self.safe_number(market, 'base_max_size'),
1231
- },
1232
- 'price': {
1233
- 'min': None,
1234
- 'max': None,
1235
- },
1236
- 'cost': {
1237
- 'min': self.safe_number(market, 'quote_min_size'),
1238
- 'max': self.safe_number(market, 'quote_max_size'),
1239
- },
1471
+ 'amount': {
1472
+ 'min': self.safe_number(market, 'base_min_size'),
1473
+ 'max': self.safe_number(market, 'base_max_size'),
1240
1474
  },
1241
- 'created': None,
1242
- 'info': market,
1243
- })
1244
- return result
1475
+ 'price': {
1476
+ 'min': None,
1477
+ 'max': None,
1478
+ },
1479
+ 'cost': {
1480
+ 'min': self.safe_number(market, 'quote_min_size'),
1481
+ 'max': self.safe_number(market, 'quote_max_size'),
1482
+ },
1483
+ },
1484
+ 'created': None,
1485
+ 'info': market,
1486
+ })
1245
1487
 
1246
1488
  def fetch_currencies_from_cache(self, params={}):
1247
1489
  options = self.safe_dict(self.options, 'fetchCurrencies', {})
@@ -1722,19 +1964,23 @@ class coinbase(Exchange, ImplicitAPI):
1722
1964
  query for balance and get the amount of funds available for trading or funds locked in orders
1723
1965
  :see: https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getaccounts
1724
1966
  :see: https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-accounts#list-accounts
1967
+ :see: https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmbalancesummary
1725
1968
  :param dict [params]: extra parameters specific to the exchange API endpoint
1726
1969
  :param boolean [params.v3]: default False, set True to use v3 api endpoint
1727
- :param dict [params.type]: "spot"(default) or "swap"
1970
+ :param dict [params.type]: "spot"(default) or "swap" or "future"
1728
1971
  :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
1729
1972
  """
1730
1973
  self.load_markets()
1731
1974
  request = {}
1732
1975
  response = None
1733
1976
  isV3 = self.safe_bool(params, 'v3', False)
1734
- type = self.safe_string(params, 'type')
1735
- params = self.omit(params, ['v3', 'type'])
1977
+ params = self.omit(params, ['v3'])
1978
+ marketType = None
1979
+ marketType, params = self.handle_market_type_and_params('fetchBalance', None, params)
1736
1980
  method = self.safe_string(self.options, 'fetchBalance', 'v3PrivateGetBrokerageAccounts')
1737
- if (isV3) or (method == 'v3PrivateGetBrokerageAccounts'):
1981
+ if marketType == 'future':
1982
+ response = self.v3PrivateGetBrokerageCfmBalanceSummary(self.extend(request, params))
1983
+ elif (isV3) or (method == 'v3PrivateGetBrokerageAccounts'):
1738
1984
  request['limit'] = 250
1739
1985
  response = self.v3PrivateGetBrokerageAccounts(self.extend(request, params))
1740
1986
  else:
@@ -1811,7 +2057,7 @@ class coinbase(Exchange, ImplicitAPI):
1811
2057
  # "size": 9
1812
2058
  # }
1813
2059
  #
1814
- params['type'] = type
2060
+ params['type'] = marketType
1815
2061
  return self.parse_custom_balance(response, params)
1816
2062
 
1817
2063
  def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
@@ -2248,6 +2494,11 @@ class coinbase(Exchange, ImplicitAPI):
2248
2494
  :param str [params.end_time]: '2023-05-25T17:01:05.092Z' for 'GTD' orders
2249
2495
  :param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount
2250
2496
  :param boolean [params.preview]: default to False, wether to use the test/preview endpoint or not
2497
+ :param float [params.leverage]: default to 1, the leverage to use for the order
2498
+ :param str [params.marginMode]: 'cross' or 'isolated'
2499
+ :param str [params.retail_portfolio_id]: portfolio uid
2500
+ :param boolean [params.is_max]: Used in conjunction with tradable_balance to indicate the user wants to use their entire tradable balance
2501
+ :param str [params.tradable_balance]: amount of tradable balance
2251
2502
  :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
2252
2503
  """
2253
2504
  self.load_markets()
@@ -2341,7 +2592,7 @@ class coinbase(Exchange, ImplicitAPI):
2341
2592
  else:
2342
2593
  if isStop or isStopLoss or isTakeProfit:
2343
2594
  raise NotSupported(self.id + ' createOrder() only stop limit orders are supported')
2344
- if side == 'buy':
2595
+ if market['spot'] and (side == 'buy'):
2345
2596
  total = None
2346
2597
  createMarketBuyOrderRequiresPrice = True
2347
2598
  createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
@@ -2370,7 +2621,13 @@ class coinbase(Exchange, ImplicitAPI):
2370
2621
  'base_size': self.amount_to_precision(symbol, amount),
2371
2622
  },
2372
2623
  }
2373
- params = self.omit(params, ['timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'stopPrice', 'stop_price', 'stopDirection', 'stop_direction', 'clientOrderId', 'postOnly', 'post_only', 'end_time'])
2624
+ marginMode = self.safe_string(params, 'marginMode')
2625
+ if marginMode is not None:
2626
+ if marginMode == 'isolated':
2627
+ request['margin_type'] = 'ISOLATED'
2628
+ elif marginMode == 'cross':
2629
+ request['margin_type'] = 'CROSS'
2630
+ params = self.omit(params, ['timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'stopPrice', 'stop_price', 'stopDirection', 'stop_direction', 'clientOrderId', 'postOnly', 'post_only', 'end_time', 'marginMode'])
2374
2631
  preview = self.safe_bool_2(params, 'preview', 'test', False)
2375
2632
  response = None
2376
2633
  if preview:
@@ -3561,6 +3818,235 @@ class coinbase(Exchange, ImplicitAPI):
3561
3818
  data = self.safe_dict(response, 'data', {})
3562
3819
  return self.parse_transaction(data)
3563
3820
 
3821
+ def close_position(self, symbol: str, side: OrderSide = None, params={}) -> Order:
3822
+ """
3823
+ *futures only* closes open positions for a market
3824
+ :see: https://coinbase-api.github.io/docs/#/en-us/swapV2/trade-api.html#One-Click%20Close%20All%20Positions
3825
+ :param str symbol: Unified CCXT market symbol
3826
+ :param str [side]: not used by coinbase
3827
+ :param dict [params]: extra parameters specific to the coinbase api endpoint
3828
+ * @param {str} params.clientOrderId *mandatory* the client order id of the position to close
3829
+ :param float [params.size]: the size of the position to close, optional
3830
+ :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
3831
+ """
3832
+ self.load_markets()
3833
+ market = self.market(symbol)
3834
+ if not market['future']:
3835
+ raise NotSupported(self.id + ' closePosition() only supported for futures markets')
3836
+ clientOrderId = self.safe_string_2(params, 'client_order_id', 'clientOrderId')
3837
+ params = self.omit(params, 'clientOrderId')
3838
+ request = {
3839
+ 'product_id': market['id'],
3840
+ }
3841
+ if clientOrderId is None:
3842
+ raise ArgumentsRequired(self.id + ' closePosition() requires a clientOrderId parameter')
3843
+ request['client_order_id'] = clientOrderId
3844
+ response = self.v3PrivatePostBrokerageOrdersClosePosition(self.extend(request, params))
3845
+ order = self.safe_dict(response, 'success_response', {})
3846
+ return self.parse_order(order)
3847
+
3848
+ def fetch_positions(self, symbols: Strings = None, params={}):
3849
+ """
3850
+ fetch all open positions
3851
+ :see: https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmpositions
3852
+ :see: https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getintxpositions
3853
+ :param str[] [symbols]: list of unified market symbols
3854
+ :param dict [params]: extra parameters specific to the exchange API endpoint
3855
+ :param str [params.portfolio]: the portfolio UUID to fetch positions for
3856
+ :returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
3857
+ """
3858
+ self.load_markets()
3859
+ symbols = self.market_symbols(symbols)
3860
+ market = None
3861
+ if symbols is not None:
3862
+ market = self.market(symbols[0])
3863
+ type = None
3864
+ type, params = self.handle_market_type_and_params('fetchPositions', market, params)
3865
+ response = None
3866
+ if type == 'future':
3867
+ response = self.v3PrivateGetBrokerageCfmPositions(params)
3868
+ else:
3869
+ portfolio = None
3870
+ portfolio, params = self.handle_option_and_params(params, 'fetchPositions', 'portfolio')
3871
+ if portfolio is None:
3872
+ raise ArgumentsRequired(self.id + ' fetchPositions() requires a "portfolio" value in params(eg: dbcb91e7-2bc9-515), or set.options["portfolio"]. You can get a list of portfolios with fetchPortfolios()')
3873
+ request = {
3874
+ 'portfolio_uuid': portfolio,
3875
+ }
3876
+ response = self.v3PrivateGetBrokerageIntxPositionsPortfolioUuid(self.extend(request, params))
3877
+ positions = self.safe_list(response, 'positions', [])
3878
+ return self.parse_positions(positions, symbols)
3879
+
3880
+ def fetch_position(self, symbol: str, params={}):
3881
+ """
3882
+ fetch data on a single open contract trade position
3883
+ :see: https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getintxposition
3884
+ :see: https://docs.cloud.coinbase.com/advanced-trade-api/reference/retailbrokerageapi_getfcmposition
3885
+ :param str symbol: unified market symbol of the market the position is held in, default is None
3886
+ :param dict [params]: extra parameters specific to the exchange API endpoint
3887
+ :param str [params.product_id]: *futures only* the product id of the position to fetch, required for futures markets only
3888
+ :param str [params.portfolio]: *perpetual/swaps only* the portfolio UUID to fetch the position for, required for perpetual/swaps markets only
3889
+ :returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
3890
+ """
3891
+ self.load_markets()
3892
+ market = self.market(symbol)
3893
+ response = None
3894
+ if market['future']:
3895
+ productId = self.safe_string(market, 'product_id')
3896
+ if productId is None:
3897
+ raise ArgumentsRequired(self.id + ' fetchPosition() requires a "product_id" in params')
3898
+ futureRequest = {
3899
+ 'product_id': productId,
3900
+ }
3901
+ response = self.v3PrivateGetBrokerageCfmPositionsProductId(self.extend(futureRequest, params))
3902
+ else:
3903
+ portfolio = None
3904
+ portfolio, params = self.handle_option_and_params(params, 'fetchPositions', 'portfolio')
3905
+ if portfolio is None:
3906
+ raise ArgumentsRequired(self.id + ' fetchPosition() requires a "portfolio" value in params(eg: dbcb91e7-2bc9-515), or set.options["portfolio"]. You can get a list of portfolios with fetchPortfolios()')
3907
+ request = {
3908
+ 'symbol': market['id'],
3909
+ 'portfolio_uuid': portfolio,
3910
+ }
3911
+ response = self.v3PrivateGetBrokerageIntxPositionsPortfolioUuidSymbol(self.extend(request, params))
3912
+ position = self.safe_dict(response, 'position', {})
3913
+ return self.parse_position(position, market)
3914
+
3915
+ def parse_position(self, position, market: Market = None):
3916
+ #
3917
+ # {
3918
+ # "product_id": "1r4njf84-0-0",
3919
+ # "product_uuid": "cd34c18b-3665-4ed8-9305-3db277c49fc5",
3920
+ # "symbol": "ADA-PERP-INTX",
3921
+ # "vwap": {
3922
+ # "value": "0.6171",
3923
+ # "currency": "USDC"
3924
+ # },
3925
+ # "position_side": "POSITION_SIDE_LONG",
3926
+ # "net_size": "20",
3927
+ # "buy_order_size": "0",
3928
+ # "sell_order_size": "0",
3929
+ # "im_contribution": "0.1",
3930
+ # "unrealized_pnl": {
3931
+ # "value": "0.074",
3932
+ # "currency": "USDC"
3933
+ # },
3934
+ # "mark_price": {
3935
+ # "value": "0.6208",
3936
+ # "currency": "USDC"
3937
+ # },
3938
+ # "liquidation_price": {
3939
+ # "value": "0",
3940
+ # "currency": "USDC"
3941
+ # },
3942
+ # "leverage": "1",
3943
+ # "im_notional": {
3944
+ # "value": "12.342",
3945
+ # "currency": "USDC"
3946
+ # },
3947
+ # "mm_notional": {
3948
+ # "value": "0.814572",
3949
+ # "currency": "USDC"
3950
+ # },
3951
+ # "position_notional": {
3952
+ # "value": "12.342",
3953
+ # "currency": "USDC"
3954
+ # },
3955
+ # "margin_type": "MARGIN_TYPE_CROSS",
3956
+ # "liquidation_buffer": "19.677828",
3957
+ # "liquidation_percentage": "4689.3506",
3958
+ # "portfolio_summary": {
3959
+ # "portfolio_uuid": "018ebd63-1f6d-7c8e-ada9-0761c5a2235f",
3960
+ # "collateral": "20.4184",
3961
+ # "position_notional": "12.342",
3962
+ # "open_position_notional": "12.342",
3963
+ # "pending_fees": "0",
3964
+ # "borrow": "0",
3965
+ # "accrued_interest": "0",
3966
+ # "rolling_debt": "0",
3967
+ # "portfolio_initial_margin": "0.1",
3968
+ # "portfolio_im_notional": {
3969
+ # "value": "12.342",
3970
+ # "currency": "USDC"
3971
+ # },
3972
+ # "portfolio_maintenance_margin": "0.066",
3973
+ # "portfolio_mm_notional": {
3974
+ # "value": "0.814572",
3975
+ # "currency": "USDC"
3976
+ # },
3977
+ # "liquidation_percentage": "4689.3506",
3978
+ # "liquidation_buffer": "19.677828",
3979
+ # "margin_type": "MARGIN_TYPE_CROSS",
3980
+ # "margin_flags": "PORTFOLIO_MARGIN_FLAGS_UNSPECIFIED",
3981
+ # "liquidation_status": "PORTFOLIO_LIQUIDATION_STATUS_NOT_LIQUIDATING",
3982
+ # "unrealized_pnl": {
3983
+ # "value": "0.074",
3984
+ # "currency": "USDC"
3985
+ # },
3986
+ # "buying_power": {
3987
+ # "value": "8.1504",
3988
+ # "currency": "USDC"
3989
+ # },
3990
+ # "total_balance": {
3991
+ # "value": "20.4924",
3992
+ # "currency": "USDC"
3993
+ # },
3994
+ # "max_withdrawal": {
3995
+ # "value": "8.0764",
3996
+ # "currency": "USDC"
3997
+ # }
3998
+ # },
3999
+ # "entry_vwap": {
4000
+ # "value": "0.6091",
4001
+ # "currency": "USDC"
4002
+ # }
4003
+ # }
4004
+ #
4005
+ marketId = self.safe_string(position, 'symbol', '')
4006
+ market = self.safe_market(marketId, market)
4007
+ rawMargin = self.safe_string(position, 'margin_type')
4008
+ marginMode = None
4009
+ if rawMargin is not None:
4010
+ marginMode = 'cross' if (rawMargin == 'MARGIN_TYPE_CROSS') else 'isolated'
4011
+ notionalObject = self.safe_dict(position, 'position_notional', {})
4012
+ positionSide = self.safe_string(position, 'position_side')
4013
+ side = 'long' if (positionSide == 'POSITION_SIDE_LONG') else 'short'
4014
+ unrealizedPNLObject = self.safe_dict(position, 'unrealized_pnl', {})
4015
+ liquidationPriceObject = self.safe_dict(position, 'liquidation_price', {})
4016
+ liquidationPrice = self.safe_number(liquidationPriceObject, 'value')
4017
+ vwapObject = self.safe_dict(position, 'vwap', {})
4018
+ summaryObject = self.safe_dict(position, 'portfolio_summary', {})
4019
+ return self.safe_position({
4020
+ 'info': position,
4021
+ 'id': self.safe_string(position, 'product_id'),
4022
+ 'symbol': self.safe_symbol(marketId, market),
4023
+ 'notional': self.safe_number(notionalObject, 'value'),
4024
+ 'marginMode': marginMode,
4025
+ 'liquidationPrice': liquidationPrice,
4026
+ 'entryPrice': self.safe_number(vwapObject, 'value'),
4027
+ 'unrealizedPnl': self.safe_number(unrealizedPNLObject, 'value'),
4028
+ 'realizedPnl': None,
4029
+ 'percentage': None,
4030
+ 'contracts': self.safe_number(position, 'net_size'),
4031
+ 'contractSize': market['contractSize'],
4032
+ 'markPrice': None,
4033
+ 'lastPrice': None,
4034
+ 'side': side,
4035
+ 'hedged': None,
4036
+ 'timestamp': None,
4037
+ 'datetime': None,
4038
+ 'lastUpdateTimestamp': None,
4039
+ 'maintenanceMargin': None,
4040
+ 'maintenanceMarginPercentage': None,
4041
+ 'collateral': self.safe_number(summaryObject, 'collateral'),
4042
+ 'initialMargin': None,
4043
+ 'initialMarginPercentage': None,
4044
+ 'leverage': self.safe_number(position, 'leverage'),
4045
+ 'marginRatio': None,
4046
+ 'stopLossPrice': None,
4047
+ 'takeProfitPrice': None,
4048
+ })
4049
+
3564
4050
  def sign(self, path, api=[], method='GET', params={}, headers=None, body=None):
3565
4051
  version = api[0]
3566
4052
  signed = api[1] == 'private'
@@ -3603,7 +4089,9 @@ class coinbase(Exchange, ImplicitAPI):
3603
4089
  # it may not work for v2
3604
4090
  uri = method + ' ' + url.replace('https://', '')
3605
4091
  quesPos = uri.find('?')
3606
- if quesPos >= 0:
4092
+ # Due to we use mb_strpos, quesPos could be False in php. In that case, the quesPos >= 0 is True
4093
+ # Also it's not possible that the question mark is first character, only check > 0 here.
4094
+ if quesPos > 0:
3607
4095
  uri = uri[0:quesPos]
3608
4096
  nonce = self.random_bytes(16)
3609
4097
  request = {