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/__init__.py +1 -1
- ccxt/abstract/coinbase.py +1 -0
- ccxt/async_support/__init__.py +1 -1
- ccxt/async_support/base/exchange.py +1 -1
- ccxt/async_support/binance.py +9 -9
- ccxt/async_support/coinbase.py +590 -102
- ccxt/async_support/deribit.py +7 -3
- ccxt/async_support/gemini.py +26 -11
- ccxt/async_support/okx.py +3 -2
- ccxt/async_support/poloniexfutures.py +4 -1
- ccxt/base/exchange.py +17 -5
- ccxt/binance.py +9 -9
- ccxt/coinbase.py +590 -102
- ccxt/deribit.py +7 -3
- ccxt/gemini.py +26 -11
- ccxt/okx.py +3 -2
- ccxt/poloniexfutures.py +4 -1
- ccxt/pro/__init__.py +1 -1
- ccxt/pro/binance.py +9 -5
- ccxt/pro/coinbase.py +19 -4
- ccxt/pro/poloniexfutures.py +5 -2
- ccxt/test/base/test_order_book.py +21 -15
- ccxt/test/test_async.py +32 -6
- ccxt/test/test_sync.py +32 -6
- {ccxt-4.2.95.dist-info → ccxt-4.2.97.dist-info}/METADATA +4 -4
- {ccxt-4.2.95.dist-info → ccxt-4.2.97.dist-info}/RECORD +28 -28
- {ccxt-4.2.95.dist-info → ccxt-4.2.97.dist-info}/WHEEL +0 -0
- {ccxt-4.2.95.dist-info → ccxt-4.2.97.dist-info}/top_level.txt +0 -0
ccxt/async_support/coinbase.py
CHANGED
@@ -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':
|
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':
|
111
|
+
'fetchPosition': True,
|
111
112
|
'fetchPositionMode': False,
|
112
|
-
'fetchPositions':
|
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
|
-
|
1144
|
+
spotUnresolvedPromises = [
|
1120
1145
|
self.v3PrivateGetBrokerageProducts(params),
|
1121
1146
|
self.v3PrivateGetBrokerageTransactionSummary(params),
|
1122
1147
|
]
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
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
|
-
|
1161
|
-
|
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
|
-
#
|
1165
|
-
#
|
1166
|
-
#
|
1167
|
-
#
|
1168
|
-
#
|
1169
|
-
#
|
1170
|
-
#
|
1171
|
-
#
|
1172
|
-
#
|
1173
|
-
#
|
1174
|
-
#
|
1175
|
-
#
|
1176
|
-
#
|
1177
|
-
#
|
1178
|
-
#
|
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
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
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
|
-
'
|
1225
|
-
'
|
1226
|
-
|
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
|
-
'
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
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
|
-
|
1736
|
-
|
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
|
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'] =
|
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
|
-
|
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
|
-
|
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 = {
|