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