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