2255 lines
99 KiB
Python
2255 lines
99 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
|
|
from ccxt.async_support.base.exchange import Exchange
|
|
from ccxt.abstract.coinbaseinternational import ImplicitAPI
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, Currencies, Currency, DepositAddress, Int, MarginModification, Market, Order, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, Trade, Transaction, TransferEntry
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import PermissionDenied
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import DuplicateOrderId
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class coinbaseinternational(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(coinbaseinternational, self).describe(), {
|
|
'id': 'coinbaseinternational',
|
|
'name': 'Coinbase International',
|
|
'countries': ['US'],
|
|
'certified': False,
|
|
'pro': True,
|
|
'rateLimit': 100, # 10 requests per second
|
|
'version': 'v1',
|
|
'userAgent': self.userAgents['chrome'],
|
|
'headers': {
|
|
'CB-VERSION': '2018-05-30',
|
|
},
|
|
'has': {
|
|
'CORS': True,
|
|
'spot': True,
|
|
'margin': True,
|
|
'swap': True,
|
|
'future': True,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'cancelOrders': False,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createDepositAddress': True,
|
|
'createLimitBuyOrder': True,
|
|
'createLimitSellOrder': True,
|
|
'createMarketBuyOrder': True,
|
|
'createMarketBuyOrderWithCost': False,
|
|
'createMarketOrderWithCost': False,
|
|
'createMarketSellOrder': True,
|
|
'createMarketSellOrderWithCost': False,
|
|
'createOrder': True,
|
|
'createPostOnlyOrder': True,
|
|
'createReduceOnlyOrder': False,
|
|
'createStopLimitOrder': True,
|
|
'createStopMarketOrder': True,
|
|
'createStopOrder': True,
|
|
'editOrder': True,
|
|
'fetchAccounts': True,
|
|
'fetchBalance': True,
|
|
'fetchBidsAsks': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchCanceledOrders': False,
|
|
'fetchClosedOrders': False,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': True,
|
|
'fetchDeposits': True,
|
|
'fetchFundingHistory': True,
|
|
'fetchFundingRate': False,
|
|
'fetchFundingRateHistory': True,
|
|
'fetchFundingRates': False,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchL2OrderBook': False,
|
|
'fetchLedger': False,
|
|
'fetchLeverage': False,
|
|
'fetchLeverageTiers': False,
|
|
'fetchMarginAdjustmentHistory': False,
|
|
'fetchMarginMode': False,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMyBuys': True,
|
|
'fetchMySells': True,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': False,
|
|
'fetchOrders': False,
|
|
'fetchPosition': True,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': True,
|
|
'fetchPositionsHistory': False,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': False,
|
|
'fetchTrades': False,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': False,
|
|
'fetchTransfers': True,
|
|
'fetchWithdrawals': True,
|
|
'reduceMargin': False,
|
|
'sandbox': True,
|
|
'setLeverage': False,
|
|
'setMargin': True,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'withdraw': True,
|
|
},
|
|
'urls': {
|
|
'logo': 'https://github.com/ccxt/ccxt/assets/43336371/866ae638-6ab5-4ebf-ab2c-cdcce9545625',
|
|
'api': {
|
|
'rest': 'https://api.international.coinbase.com/api',
|
|
},
|
|
'test': {
|
|
'rest': 'https://api-n5e1.coinbase.com/api',
|
|
},
|
|
'www': 'https://international.coinbase.com',
|
|
'doc': [
|
|
'https://docs.cloud.coinbase.com/intx/docs',
|
|
],
|
|
'fees': [
|
|
'https://help.coinbase.com/en/international-exchange/trading-deposits-withdrawals/international-exchange-fees',
|
|
],
|
|
'referral': '',
|
|
},
|
|
'requiredCredentials': {
|
|
'apiKey': True,
|
|
'secret': True,
|
|
'password': True,
|
|
},
|
|
'api': {
|
|
'v1': {
|
|
'public': {
|
|
'get': [
|
|
'assets',
|
|
'assets/{assets}',
|
|
'assets/{asset}/networks',
|
|
'instruments',
|
|
'instruments/{instrument}',
|
|
'instruments/{instrument}/quote',
|
|
'instruments/{instrument}/funding',
|
|
'instruments/{instrument}/candles',
|
|
],
|
|
},
|
|
'private': {
|
|
'get': [
|
|
'orders',
|
|
'orders/{id}',
|
|
'portfolios',
|
|
'portfolios/{portfolio}',
|
|
'portfolios/{portfolio}/detail',
|
|
'portfolios/{portfolio}/summary',
|
|
'portfolios/{portfolio}/balances',
|
|
'portfolios/{portfolio}/balances/{asset}',
|
|
'portfolios/{portfolio}/positions',
|
|
'portfolios/{portfolio}/positions/{instrument}',
|
|
'portfolios/fills',
|
|
'portfolios/{portfolio}/fills',
|
|
'transfers',
|
|
'transfers/{transfer_uuid}',
|
|
],
|
|
'post': [
|
|
'orders',
|
|
'portfolios',
|
|
'portfolios/margin',
|
|
'portfolios/transfer',
|
|
'transfers/withdraw',
|
|
'transfers/address',
|
|
'transfers/create-counterparty-id',
|
|
'transfers/validate-counterparty-id',
|
|
'transfers/withdraw/counterparty',
|
|
],
|
|
'put': [
|
|
'orders/{id}',
|
|
'portfolios/{portfolio}',
|
|
],
|
|
'delete': [
|
|
'orders',
|
|
'orders/{id}',
|
|
],
|
|
},
|
|
},
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'taker': self.parse_number('0.004'),
|
|
'maker': self.parse_number('0.002'),
|
|
'tierBased': True,
|
|
'percentage': True,
|
|
'tiers': {
|
|
'taker': [
|
|
[self.parse_number('0'), self.parse_number('0.004')],
|
|
[self.parse_number('1000000'), self.parse_number('0.004')],
|
|
[self.parse_number('5000000'), self.parse_number('0.0035')],
|
|
[self.parse_number('10000000'), self.parse_number('0.0035')],
|
|
[self.parse_number('50000000'), self.parse_number('0.003')],
|
|
[self.parse_number('250000000'), self.parse_number('0.0025')],
|
|
],
|
|
'maker': [
|
|
[self.parse_number('0'), self.parse_number('0.002')],
|
|
[self.parse_number('1000000'), self.parse_number('0.0016')],
|
|
[self.parse_number('5000000'), self.parse_number('0.001')],
|
|
[self.parse_number('10000000'), self.parse_number('0.0008')],
|
|
[self.parse_number('50000000'), self.parse_number('0.0005')],
|
|
[self.parse_number('250000000'), self.parse_number('0')],
|
|
],
|
|
},
|
|
},
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'exceptions': {
|
|
'exact': {},
|
|
'broad': {
|
|
'DUPLICATE_CLIENT_ORDER_ID': DuplicateOrderId,
|
|
'Order rejected': InvalidOrder,
|
|
'market orders must be IoC': InvalidOrder,
|
|
'tif is required': InvalidOrder,
|
|
'Invalid replace order request': InvalidOrder,
|
|
'Unauthorized': PermissionDenied,
|
|
'invalid result_limit': BadRequest,
|
|
'is a required field': BadRequest,
|
|
'Not Found': BadRequest,
|
|
'ip not allowed': AuthenticationError,
|
|
},
|
|
},
|
|
'timeframes': {
|
|
'1m': 'ONE_MINUTE',
|
|
'5m': 'FIVE_MINUTE',
|
|
'15m': 'FIFTEEN_MINUTE',
|
|
'30m': 'THIRTY_MINUTE',
|
|
'1h': 'ONE_HOUR',
|
|
'2h': 'TWO_HOUR',
|
|
'6h': 'SIX_HOUR',
|
|
'1d': 'ONE_DAY',
|
|
},
|
|
'options': {
|
|
'brokerId': 'nfqkvdjp',
|
|
'portfolio': '', # default portfolio id
|
|
'withdraw': {
|
|
'method': 'v1PrivatePostTransfersWithdraw', # use v1PrivatePostTransfersWithdrawCounterparty for counterparty withdrawals
|
|
},
|
|
'networksById': {
|
|
'ethereum': 'ETH',
|
|
'arbitrum': 'ARBITRUM',
|
|
'avacchain': 'AVAX',
|
|
'optimism': 'OPTIMISM',
|
|
'polygon': 'MATIC',
|
|
'solana': 'SOL',
|
|
'bitcoin': 'BTC',
|
|
},
|
|
},
|
|
'features': {
|
|
'default': {
|
|
'sandbox': True,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': True,
|
|
'stopLossPrice': False, # todo implementation
|
|
'takeProfitPrice': False, # todo implementation
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': True,
|
|
'PO': True,
|
|
'GTD': True,
|
|
'GTC': True, # has 30 days max
|
|
},
|
|
'hedged': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyByCost': False,
|
|
'marketBuyRequiresPrice': True,
|
|
'selfTradePrevention': True, # todo: implement
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 100,
|
|
'daysBack': None,
|
|
'untilDays': 10000,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': 100,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': None,
|
|
'fetchClosedOrders': None,
|
|
'fetchOHLCV': {
|
|
'limit': 300,
|
|
},
|
|
},
|
|
'spot': {
|
|
'extends': 'default',
|
|
},
|
|
'swap': {
|
|
'linear': {
|
|
'extends': 'default',
|
|
},
|
|
'inverse': {
|
|
'extends': 'default',
|
|
},
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
})
|
|
|
|
async def handle_portfolio_and_params(self, methodName: str, params={}):
|
|
portfolio = None
|
|
portfolio, params = self.handle_option_and_params(params, methodName, 'portfolio')
|
|
if (portfolio is not None) and (portfolio != ''):
|
|
return [portfolio, params]
|
|
defaultPortfolio = self.safe_string(self.options, 'portfolio')
|
|
if (defaultPortfolio is not None) and (defaultPortfolio != ''):
|
|
return [defaultPortfolio, params]
|
|
accounts = await self.fetch_accounts()
|
|
for i in range(0, len(accounts)):
|
|
account = accounts[i]
|
|
info = self.safe_dict(account, 'info', {})
|
|
if self.safe_bool(info, 'is_default'):
|
|
portfolioId = self.safe_string(info, 'portfolio_id')
|
|
self.options['portfolio'] = portfolioId
|
|
return [portfolioId, params]
|
|
raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a portfolio parameter or set the default portfolio with self.options["portfolio"]')
|
|
|
|
async def handle_network_id_and_params(self, currencyCode: str, methodName: str, params):
|
|
networkId = None
|
|
networkId, params = self.handle_option_and_params(params, methodName, 'network_arn_id')
|
|
if networkId is None:
|
|
await self.load_currency_networks(currencyCode)
|
|
networks = self.currencies[currencyCode]['networks']
|
|
network = self.safe_string_2(params, 'networkCode', 'network')
|
|
if network is None:
|
|
# find default network
|
|
if self.is_empty(networks):
|
|
raise BadRequest(self.id + ' createDepositAddress network not found for currency ' + currencyCode + ' please specify networkId in params')
|
|
defaultNetwork = self.find_default_network(networks)
|
|
networkId = defaultNetwork['id']
|
|
else:
|
|
networkId = self.network_code_to_id(network, currencyCode)
|
|
return [networkId, params]
|
|
|
|
async def fetch_accounts(self, params={}):
|
|
"""
|
|
fetch all the accounts associated with a profile
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getportfolios
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `account structures <https://docs.ccxt.com/#/?id=account-structure>` indexed by the account type
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.v1PrivateGetPortfolios(params)
|
|
#
|
|
# [
|
|
# {
|
|
# "portfolio_id":"1ap32qsc-1-0",
|
|
# "portfolio_uuid":"028d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "name":"CCXT Portfolio 030624-17:16",
|
|
# "user_uuid":"e6cf46b6-a32f-5fa7-addb-3324d4526fbd",
|
|
# "maker_fee_rate":"0",
|
|
# "taker_fee_rate":"0.0002",
|
|
# "trading_lock":false,
|
|
# "borrow_disabled":false,
|
|
# "is_lsp":false,
|
|
# "is_default":true,
|
|
# "cross_collateral_enabled":false
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_accounts(response, params)
|
|
|
|
def parse_account(self, account):
|
|
#
|
|
# {
|
|
# "portfolio_id":"1ap32qsc-1-0",
|
|
# "portfolio_uuid":"028d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "name":"CCXT Portfolio 030624-17:16",
|
|
# "user_uuid":"e6cf46b6-a32f-5fa7-addb-3324d4526fbd",
|
|
# "maker_fee_rate":"0",
|
|
# "taker_fee_rate":"0.0002",
|
|
# "trading_lock":false,
|
|
# "borrow_disabled":false,
|
|
# "is_lsp":false,
|
|
# "is_default":true,
|
|
# "cross_collateral_enabled":false
|
|
# }
|
|
#
|
|
return {
|
|
'id': self.safe_string_2(account, 'portfolio_id', 'portfolio_uuid'),
|
|
'type': None,
|
|
'code': None,
|
|
'info': account,
|
|
}
|
|
|
|
async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = 100, params={}) -> List[list]:
|
|
"""
|
|
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://docs.cdp.coinbase.com/intx/reference/getinstrumentcandles
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str timeframe: the length of time each candle represents
|
|
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch, default 100 max 10000
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
:param int [params.until]: timestamp in ms of the latest candle to fetch
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
"""
|
|
await self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate')
|
|
if paginate:
|
|
return await self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 10000)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'instrument': market['id'],
|
|
'granularity': self.safe_string(self.timeframes, timeframe, timeframe),
|
|
}
|
|
if since is not None:
|
|
request['start'] = self.iso8601(since)
|
|
else:
|
|
raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a since argument')
|
|
unitl = self.safe_integer(params, 'until')
|
|
if unitl is not None:
|
|
params = self.omit(params, 'until')
|
|
request['end'] = self.iso8601(unitl)
|
|
response = await self.v1PublicGetInstrumentsInstrumentCandles(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "aggregations": [
|
|
# {
|
|
# "start": "2024-04-23T00:00:00Z",
|
|
# "open": "62884.4",
|
|
# "high": "64710.6",
|
|
# "low": "62884.4",
|
|
# "close": "63508.4",
|
|
# "volume": "3253.9983"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
candles = self.safe_list(response, 'aggregations', [])
|
|
return self.parse_ohlcvs(candles, market, timeframe, since, limit)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# {
|
|
# "start": "2024-04-23T00:00:00Z",
|
|
# "open": "62884.4",
|
|
# "high": "64710.6",
|
|
# "low": "62884.4",
|
|
# "close": "63508.4",
|
|
# "volume": "3253.9983"
|
|
# }
|
|
#
|
|
return [
|
|
self.parse8601(self.safe_string_2(ohlcv, 'start', 'time')),
|
|
self.safe_number(ohlcv, 'open'),
|
|
self.safe_number(ohlcv, 'high'),
|
|
self.safe_number(ohlcv, 'low'),
|
|
self.safe_number(ohlcv, 'close'),
|
|
self.safe_number(ohlcv, 'volume'),
|
|
]
|
|
|
|
async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches historical funding rate prices
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getinstrumentfunding
|
|
|
|
:param str symbol: unified symbol of the market to fetch the funding rate history for
|
|
:param int [since]: timestamp in ms of the earliest funding rate to fetch
|
|
:param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
|
|
await self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
|
|
maxEntriesPerRequest = None
|
|
maxEntriesPerRequest, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'maxEntriesPerRequest', 100)
|
|
pageKey = 'ccxtPageKey'
|
|
if paginate:
|
|
return await self.fetch_paginated_call_incremental('fetchFundingRateHistory', symbol, since, limit, params, pageKey, maxEntriesPerRequest)
|
|
market = self.market(symbol)
|
|
page = self.safe_integer(params, pageKey, 1) - 1
|
|
request: dict = {
|
|
'instrument': market['id'],
|
|
'result_offset': self.safe_integer_2(params, 'offset', 'result_offset', page * maxEntriesPerRequest),
|
|
}
|
|
if limit is not None:
|
|
request['result_limit'] = limit
|
|
response = await self.v1PublicGetInstrumentsInstrumentFunding(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "pagination":{
|
|
# "result_limit":"25",
|
|
# "result_offset":"0"
|
|
# },
|
|
# "results":[
|
|
# {
|
|
# "instrument_id":"149264167780483072",
|
|
# "funding_rate":"0.000011",
|
|
# "mark_price":"47388.1",
|
|
# "event_time":"2024-02-10T16:00:00Z"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
rawRates = self.safe_list(response, 'results', [])
|
|
return self.parse_funding_rate_histories(rawRates, market, since, limit)
|
|
|
|
def parse_funding_rate_history(self, info, market: Market = None):
|
|
return self.parse_funding_rate(info, market)
|
|
|
|
def parse_funding_rate(self, contract, market: Market = None):
|
|
#
|
|
# {
|
|
# "instrument_id":"149264167780483072",
|
|
# "funding_rate":"0.000011",
|
|
# "mark_price":"47388.1",
|
|
# "event_time":"2024-02-10T16:00:00Z"
|
|
# }
|
|
#
|
|
fundingDatetime = self.safe_string_2(contract, 'event_time', 'time')
|
|
return {
|
|
'info': contract,
|
|
'symbol': self.safe_symbol(None, market),
|
|
'markPrice': self.safe_number(contract, 'mark_price'),
|
|
'indexPrice': None,
|
|
'interestRate': None,
|
|
'estimatedSettlePrice': None,
|
|
'timestamp': self.parse8601(fundingDatetime),
|
|
'datetime': fundingDatetime,
|
|
'fundingRate': self.safe_number(contract, 'funding_rate'),
|
|
'fundingTimestamp': self.parse8601(fundingDatetime),
|
|
'fundingDatetime': fundingDatetime,
|
|
'nextFundingRate': None,
|
|
'nextFundingTimestamp': None,
|
|
'nextFundingDatetime': None,
|
|
'previousFundingRate': None,
|
|
'previousFundingTimestamp': None,
|
|
'previousFundingDatetime': None,
|
|
}
|
|
|
|
async def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch the history of funding payments paid and received on self account
|
|
|
|
https://docs.cdp.coinbase.com/intx/reference/gettransfers
|
|
|
|
:param str [symbol]: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch funding history for
|
|
:param int [limit]: the maximum number of funding history structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `funding history structure <https://docs.ccxt.com/#/?id=funding-history-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'type': 'FUNDING',
|
|
}
|
|
market: Market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
portfolios = None
|
|
portfolios, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'portfolios')
|
|
if portfolios is not None:
|
|
request['portfolios'] = portfolios
|
|
if since is not None:
|
|
request['time_from'] = self.iso8601(since)
|
|
if limit is not None:
|
|
request['result_limit'] = limit
|
|
else:
|
|
request['result_limit'] = 100
|
|
response = await self.v1PrivateGetTransfers(self.extend(request, params))
|
|
fundings = self.safe_list(response, 'results', [])
|
|
return self.parse_incomes(fundings, market, since, limit)
|
|
|
|
def parse_income(self, income, market: Market = None):
|
|
#
|
|
# {
|
|
# "amount":"0.0008",
|
|
# "asset":"USDC",
|
|
# "created_at":"2024-02-22T16:00:00Z",
|
|
# "from_portfolio":{
|
|
# "id":"13yuk1fs-1-0",
|
|
# "name":"Eng Test Portfolio - 2",
|
|
# "uuid":"018712f2-5ff9-7de3-9010-xxxxxxxxx"
|
|
# },
|
|
# "instrument_id":"149264164756389888",
|
|
# "instrument_symbol":"ETH-PERP",
|
|
# "position_id":"1xy4v51m-1-2",
|
|
# "status":"PROCESSED",
|
|
# "to_portfolio":{
|
|
# "name":"CB_FUND"
|
|
# },
|
|
# "transfer_type":"FUNDING",
|
|
# "transfer_uuid":"a6b708df-2c44-32c5-bb98-xxxxxxxxxx",
|
|
# "updated_at":"2024-02-22T16:00:00Z"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(income, 'symbol')
|
|
market = self.safe_market(marketId, market, None, 'contract')
|
|
datetime = self.safe_integer(income, 'created_at')
|
|
timestamp = self.parse8601(datetime)
|
|
currencyId = self.safe_string(income, 'asset')
|
|
code = self.safe_currency_code(currencyId)
|
|
return {
|
|
'info': income,
|
|
'symbol': market['symbol'],
|
|
'code': code,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'id': self.safe_string(income, 'transfer_uuid'),
|
|
'amount': self.safe_number(income, 'amount'),
|
|
'rate': None,
|
|
}
|
|
|
|
async def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[TransferEntry]:
|
|
"""
|
|
fetch a history of internal transfers made on an account
|
|
|
|
https://docs.cdp.coinbase.com/intx/reference/gettransfers
|
|
|
|
:param str code: unified currency code of the currency transferred
|
|
:param int [since]: the earliest time in ms to fetch transfers for
|
|
:param int [limit]: the maximum number of transfers structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'type': 'INTERNAL',
|
|
}
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
portfolios = None
|
|
portfolios, params = self.handle_option_and_params(params, 'fetchTransfers', 'portfolios')
|
|
if portfolios is not None:
|
|
request['portfolios'] = portfolios
|
|
if since is not None:
|
|
request['time_from'] = self.iso8601(since)
|
|
if limit is not None:
|
|
request['result_limit'] = limit
|
|
else:
|
|
request['result_limit'] = 100
|
|
response = await self.v1PrivateGetTransfers(self.extend(request, params))
|
|
transfers = self.safe_list(response, 'results', [])
|
|
return self.parse_transfers(transfers, currency, since, limit)
|
|
|
|
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
|
#
|
|
# {
|
|
# "amount":"0.0008",
|
|
# "asset":"USDC",
|
|
# "created_at":"2024-02-22T16:00:00Z",
|
|
# "from_portfolio":{
|
|
# "id":"13yuk1fs-1-0",
|
|
# "name":"Eng Test Portfolio - 2",
|
|
# "uuid":"018712f2-5ff9-7de3-9010-xxxxxxxxx"
|
|
# },
|
|
# "instrument_id":"149264164756389888",
|
|
# "instrument_symbol":"ETH-PERP",
|
|
# "position_id":"1xy4v51m-1-2",
|
|
# "status":"PROCESSED",
|
|
# "to_portfolio":{
|
|
# "name":"CB_FUND"
|
|
# },
|
|
# "transfer_type":"FUNDING",
|
|
# "transfer_uuid":"a6b708df-2c44-32c5-bb98-xxxxxxxxxx",
|
|
# "updated_at":"2024-02-22T16:00:00Z"
|
|
# }
|
|
#
|
|
datetime = self.safe_integer(transfer, 'created_at')
|
|
timestamp = self.parse8601(datetime)
|
|
currencyId = self.safe_string(transfer, 'asset')
|
|
code = self.safe_currency_code(currencyId)
|
|
fromPorfolio = self.safe_dict(transfer, 'from_portfolio', {})
|
|
fromId = self.safe_string(fromPorfolio, 'id')
|
|
toPorfolio = self.safe_dict(transfer, 'to_portfolio', {})
|
|
toId = self.safe_string(toPorfolio, 'id')
|
|
return {
|
|
'info': transfer,
|
|
'id': self.safe_string(transfer, 'transfer_uuid'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'currency': code,
|
|
'amount': self.safe_number(transfer, 'amount'),
|
|
'fromAccount': fromId,
|
|
'toAccount': toId,
|
|
'status': self.parse_transfer_status(self.safe_string(transfer, 'status')),
|
|
}
|
|
|
|
def parse_transfer_status(self, status: Str) -> Str:
|
|
statuses: dict = {
|
|
'FAILED': 'failed',
|
|
'PROCESSED': 'ok',
|
|
'NEW': 'pending',
|
|
'STARTED': 'pending',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
async def create_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
create a currency deposit address
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/createaddress
|
|
https://docs.cloud.coinbase.com/intx/reference/createcounterpartyid
|
|
|
|
:param str code: unified currency code of the currency for the deposit address
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.network_arn_id]: Identifies the blockchain network(e.g., networks/ethereum-mainnet/assets/313ef8a9-ae5a-5f2f-8a56-572c0e2a4d5a) if not provided will pick default
|
|
:param str [params.network]: unified network code to identify the blockchain network
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
method = None
|
|
method, params = self.handle_option_and_params(params, 'createDepositAddress', 'method', 'v1PrivatePostTransfersAddress')
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('createDepositAddress', params)
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
}
|
|
if method == 'v1PrivatePostTransfersAddress':
|
|
currency = self.currency(code)
|
|
request['asset'] = currency['id']
|
|
networkId = None
|
|
networkId, params = await self.handle_network_id_and_params(code, 'createDepositAddress', params)
|
|
request['network_arn_id'] = networkId
|
|
response = await getattr(self, method)(self.extend(request, params))
|
|
#
|
|
# v1PrivatePostTransfersAddress
|
|
# {
|
|
# address: "3LkwYscRyh6tUR1XTqXSJQoJnK7ucC1F4n",
|
|
# network_arn_id: "networks/bitcoin-mainnet/assets/6ecc0dcc-10a2-500e-b315-a3b9abae19ce",
|
|
# destination_tag: "",
|
|
# }
|
|
# v1PrivatePostTransfersCreateCounterpartyId
|
|
# {
|
|
# "portfolio_uuid":"018e0a8b-6b6b-70e0-9689-1e7926c2c8bc",
|
|
# "counterparty_id":"CB2ZPUCZBE"
|
|
# }
|
|
#
|
|
tag = self.safe_string(response, 'destination_tag')
|
|
address = self.safe_string_2(response, 'address', 'counterparty_id')
|
|
return {
|
|
'currency': code,
|
|
'tag': tag,
|
|
'address': address,
|
|
'network': None,
|
|
'info': response,
|
|
}
|
|
|
|
def find_default_network(self, networks):
|
|
networksArray = self.to_array(networks)
|
|
for i in range(0, len(networksArray)):
|
|
info = networksArray[i]['info']
|
|
is_default = self.safe_bool(info, 'is_default', False)
|
|
if is_default is True:
|
|
return networksArray[i]
|
|
return networksArray[0]
|
|
|
|
async def load_currency_networks(self, code, params={}):
|
|
currency = self.currency(code)
|
|
networks = self.safe_dict(currency, 'networks')
|
|
if networks is not None:
|
|
return False
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
}
|
|
rawNetworks = await self.v1PublicGetAssetsAssetNetworks(request)
|
|
#
|
|
# [
|
|
# {
|
|
# "asset_id":"1",
|
|
# "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
|
|
# "asset_name":"USDC",
|
|
# "network_arn_id":"networks/ethereum-mainnet/assets/9bc140b4-69c3-5fc9-bd0d-b041bcf40039",
|
|
# "min_withdrawal_amt":"1",
|
|
# "max_withdrawal_amt":"100000000",
|
|
# "network_confirms":35,
|
|
# "processing_time":485,
|
|
# "is_default":true,
|
|
# "network_name":"ethereum",
|
|
# "display_name":"Ethereum"
|
|
# },
|
|
# ....
|
|
# ]
|
|
#
|
|
currency['networks'] = self.parse_networks(rawNetworks)
|
|
return True
|
|
|
|
def parse_networks(self, networks, params={}):
|
|
result: dict = {}
|
|
for i in range(0, len(networks)):
|
|
network = self.extend(self.parse_network(networks[i]), params)
|
|
result[network['network']] = network
|
|
return result
|
|
|
|
def parse_network(self, network, params={}):
|
|
#
|
|
# {
|
|
# "asset_id":"1",
|
|
# "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
|
|
# "asset_name":"USDC",
|
|
# "network_arn_id":"networks/ethereum-mainnet/assets/9bc140b4-69c3-5fc9-bd0d-b041bcf40039",
|
|
# "min_withdrawal_amt":"1",
|
|
# "max_withdrawal_amt":"100000000",
|
|
# "network_confirms":35,
|
|
# "processing_time":485,
|
|
# "is_default":true,
|
|
# "network_name":"ethereum",
|
|
# "display_name":"Ethereum"
|
|
# }
|
|
#
|
|
currencyId = self.safe_string(network, 'asset_name')
|
|
currencyCode = self.safe_currency_code(currencyId)
|
|
networkId = self.safe_string(network, 'network_arn_id')
|
|
networkIdForCode = self.safe_string_n(network, ['network_name', 'display_name', 'network_arn_id'], '')
|
|
return self.safe_network({
|
|
'info': network,
|
|
'id': networkId,
|
|
'name': self.safe_string(network, 'display_name'),
|
|
'network': self.network_id_to_code(networkIdForCode, currencyCode),
|
|
'active': None,
|
|
'deposit': None,
|
|
'withdraw': None,
|
|
'precision': None,
|
|
'fee': None,
|
|
'limits': {
|
|
'withdraw': {
|
|
'min': self.safe_number(network, 'min_withdrawal_amt'),
|
|
'max': self.safe_number(network, 'max_withdrawal_amt'),
|
|
},
|
|
'deposit': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
})
|
|
|
|
async def set_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
|
"""
|
|
Either adds or reduces margin in order to set the margin to a specific value
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/setportfoliomarginoverride
|
|
|
|
:param str symbol: unified market symbol of the market to set margin in
|
|
:param float amount: the amount to set the margin to
|
|
:param dict [params]: parameters specific to the exchange API endpoint
|
|
:returns dict: A `margin structure <https://github.com/ccxt/ccxt/wiki/Manual#add-margin-structure>`
|
|
"""
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('setMargin', params)
|
|
if symbol is not None:
|
|
raise BadRequest(self.id + ' setMargin() only allows setting margin to full portfolio')
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
'margin_override': amount,
|
|
}
|
|
return await self.v1PrivatePostPortfoliosMargin(self.extend(request, params))
|
|
|
|
async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch history of deposits and withdrawals
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/gettransfers
|
|
|
|
:param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
|
|
:param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
|
|
:param int [limit]: max number of deposit/withdrawals to return, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.portfolios]: Identifies the portfolios by UUID(e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID(e.g., 5189861793641175). Can provide single or multiple portfolios to filter by or fetches transfers for all portfolios if none are provided.
|
|
:param int [params.until]: Only find transfers updated before self time. Use timestamp format
|
|
:param str [params.status]: The current status of transfer. Possible values: [PROCESSED, NEW, FAILED, STARTED]
|
|
:param str [params.type]: The type of transfer Possible values: [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns dict: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
paginate = None
|
|
paginate, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'paginate')
|
|
maxEntriesPerRequest = None
|
|
maxEntriesPerRequest, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'maxEntriesPerRequest', 100)
|
|
pageKey = 'ccxtPageKey'
|
|
if paginate:
|
|
return await self.fetch_paginated_call_incremental('fetchDepositsWithdrawals', code, since, limit, params, pageKey, maxEntriesPerRequest)
|
|
page = self.safe_integer(params, pageKey, 1) - 1
|
|
request: dict = {
|
|
'result_offset': self.safe_integer_2(params, 'offset', 'result_offset', page * maxEntriesPerRequest),
|
|
}
|
|
if since is not None:
|
|
request['time_from'] = self.iso8601(since)
|
|
if limit is not None:
|
|
newLimit = min(limit, 100)
|
|
request['result_limit'] = newLimit
|
|
portfolios = None
|
|
portfolios, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'portfolios')
|
|
if portfolios is not None:
|
|
request['portfolios'] = portfolios
|
|
until = None
|
|
until, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'until')
|
|
if until is not None:
|
|
request['time_to'] = self.iso8601(until)
|
|
response = await self.v1PrivateGetTransfers(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "pagination":{
|
|
# "result_limit":25,
|
|
# "result_offset":0
|
|
# },
|
|
# "results":[
|
|
# {
|
|
# "transfer_uuid":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3",
|
|
# "transfer_type":"WITHDRAW",
|
|
# "amount":"1.000000",
|
|
# "asset":"USDC",
|
|
# "status":"PROCESSED",
|
|
# "network_name":"ethereum",
|
|
# "created_at":"2024-03-14T02:32:18.497795Z",
|
|
# "updated_at":"2024-03-14T02:35:38.514588Z",
|
|
# "from_portfolio":{
|
|
# "id":"1yun54bb-1-6",
|
|
# "uuid":"018e0a8b-6b6b-70e0-9689-1e7926c2c8bc",
|
|
# "name":"fungus technology o?Portfolio"
|
|
# },
|
|
# "to_address":"0xcdcE79F820BE9d6C5033db5c31d1AE3A8c2399bB"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
rawTransactions = self.safe_list(response, 'results', [])
|
|
return self.parse_transactions(rawTransactions)
|
|
|
|
async def fetch_position(self, symbol: str, params={}):
|
|
"""
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getportfolioposition
|
|
|
|
fetch data on an open position
|
|
:param str symbol: unified market symbol of the market the position is held in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbol = self.symbol(symbol)
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('fetchPosition', params)
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
'instrument': self.market_id(symbol),
|
|
}
|
|
position = await self.v1PrivateGetPortfoliosPortfolioPositionsInstrument(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "symbol":"BTC-PERP",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "vwap":"52482.3",
|
|
# "net_size":"0",
|
|
# "buy_order_size":"0.001",
|
|
# "sell_order_size":"0",
|
|
# "im_contribution":"0.2",
|
|
# "unrealized_pnl":"0",
|
|
# "mark_price":"52406.8",
|
|
# "entry_vwap":"52472.9"
|
|
# }
|
|
#
|
|
return self.parse_position(position)
|
|
|
|
def parse_position(self, position: dict, market: Market = None):
|
|
#
|
|
# {
|
|
# "symbol":"BTC-PERP",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "vwap":"52482.3",
|
|
# "net_size":"0",
|
|
# "buy_order_size":"0.001",
|
|
# "sell_order_size":"0",
|
|
# "im_contribution":"0.2",
|
|
# "unrealized_pnl":"0",
|
|
# "mark_price":"52406.8",
|
|
# "entry_vwap":"52472.9"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(position, 'symbol')
|
|
quantity = self.safe_string(position, 'net_size')
|
|
market = self.safe_market(marketId, market, '-')
|
|
side = 'long'
|
|
if Precise.string_le(quantity, '0'):
|
|
side = 'short'
|
|
quantity = Precise.string_mul('-1', quantity)
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': self.safe_string(position, 'id'),
|
|
'symbol': market['symbol'],
|
|
'entryPrice': None,
|
|
'markPrice': self.safe_number(position, 'mark_price'),
|
|
'notional': None,
|
|
'collateral': None,
|
|
'unrealizedPnl': self.safe_number(position, 'unrealized_pnl'),
|
|
'side': side,
|
|
'contracts': self.parse_number(quantity),
|
|
'contractSize': self.safe_number(market, 'contractSize'),
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'hedged': None,
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'initialMargin': self.safe_number(position, 'im_contribution'),
|
|
'initialMarginPercentage': None,
|
|
'leverage': None,
|
|
'liquidationPrice': None,
|
|
'marginRatio': None,
|
|
'marginMode': None,
|
|
'percentage': None,
|
|
})
|
|
|
|
async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getportfoliopositions
|
|
|
|
fetch all open positions
|
|
:param str[] [symbols]: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('fetchPositions', params)
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
}
|
|
response = await self.v1PrivateGetPortfoliosPortfolioPositions(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "symbol":"BTC-PERP",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "vwap":"52482.3",
|
|
# "net_size":"0",
|
|
# "buy_order_size":"0.001",
|
|
# "sell_order_size":"0",
|
|
# "im_contribution":"0.2",
|
|
# "unrealized_pnl":"0",
|
|
# "mark_price":"52406.8",
|
|
# "entry_vwap":"52472.9"
|
|
# }
|
|
# ]
|
|
#
|
|
positions = self.parse_positions(response)
|
|
if self.is_empty(symbols):
|
|
return positions
|
|
symbols = self.market_symbols(symbols)
|
|
return self.filter_by_array_positions(positions, 'symbol', symbols, False)
|
|
|
|
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all withdrawals made from an account
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/gettransfers
|
|
|
|
:param str code: unified currency code
|
|
:param int [since]: the earliest time in ms to fetch withdrawals for
|
|
:param int [limit]: the maximum number of withdrawals structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.portfolios]: Identifies the portfolios by UUID(e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID(e.g., 5189861793641175). Can provide single or multiple portfolios to filter by or fetches transfers for all portfolios if none are provided.
|
|
:param int [params.until]: Only find transfers updated before self time. Use timestamp format
|
|
:param str [params.status]: The current status of transfer. Possible values: [PROCESSED, NEW, FAILED, STARTED]
|
|
:param str [params.type]: The type of transfer Possible values: [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
params['type'] = 'WITHDRAW'
|
|
return await self.fetch_deposits_withdrawals(code, since, limit, params)
|
|
|
|
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all deposits made to an account
|
|
:param str code: unified currency code
|
|
:param int [since]: the earliest time in ms to fetch deposits for
|
|
:param int [limit]: the maximum number of deposits structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.portfolios]: Identifies the portfolios by UUID(e.g., 892e8c7c-e979-4cad-b61b-55a197932cf1) or portfolio ID(e.g., 5189861793641175). Can provide single or multiple portfolios to filter by or fetches transfers for all portfolios if none are provided.
|
|
:param int [params.until]: Only find transfers updated before self time. Use timestamp format
|
|
:param str [params.status]: The current status of transfer. Possible values: [PROCESSED, NEW, FAILED, STARTED]
|
|
:param str [params.type]: The type of transfer Possible values: [DEPOSIT, WITHDRAW, REBATE, STIPEND, INTERNAL, FUNDING]
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
params['type'] = 'DEPOSIT'
|
|
return await self.fetch_deposits_withdrawals(code, since, limit, params)
|
|
|
|
def parse_transaction_status(self, status: Str):
|
|
statuses: dict = {
|
|
'PROCESSED': 'ok',
|
|
'NEW': 'pending',
|
|
'STARTED': 'pending',
|
|
'FAILED': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# {
|
|
# "idem":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3"
|
|
# }
|
|
# transactionType = self.safe_string(transaction, 'type')
|
|
datetime = self.safe_string(transaction, 'updated_at')
|
|
fromPorfolio = self.safe_dict(transaction, 'from_portfolio', {})
|
|
addressFrom = self.safe_string_n(transaction, ['from_address', 'from_cb_account', self.safe_string_n(fromPorfolio, ['id', 'uuid', 'name']), 'from_counterparty_id'])
|
|
toPorfolio = self.safe_dict(transaction, 'from_portfolio', {})
|
|
addressTo = self.safe_string_n(transaction, ['to_address', 'to_cb_account', self.safe_string_n(toPorfolio, ['id', 'uuid', 'name']), 'to_counterparty_id'])
|
|
return {
|
|
'info': transaction,
|
|
'id': self.safe_string(transaction, 'transfer_uuid'),
|
|
'txid': self.safe_string(transaction, 'transaction_uuid'),
|
|
'timestamp': self.parse8601(datetime),
|
|
'datetime': datetime,
|
|
'network': self.network_id_to_code(self.safe_string(transaction, 'network_name')),
|
|
'address': None, # TODO check if withdraw or deposit and populate
|
|
'addressTo': addressTo,
|
|
'addressFrom': addressFrom,
|
|
'tag': None,
|
|
'tagTo': None,
|
|
'tagFrom': None,
|
|
'type': self.safe_string(transaction, 'resource'),
|
|
'amount': self.safe_number(transaction, 'amount'),
|
|
'currency': self.safe_currency_code(self.safe_string(transaction, 'asset'), currency),
|
|
'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
|
|
'updated': self.parse8601(datetime),
|
|
'fee': {
|
|
'cost': None,
|
|
'currency': None,
|
|
},
|
|
}
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# {
|
|
# "portfolio_id":"1wp37qsc-1-0",
|
|
# "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "portfolio_name":"CCXT Portfolio 020624-17:16",
|
|
# "fill_id":"1xbfy19y-1-184",
|
|
# "exec_id":"280841526207070392",
|
|
# "order_id":"1xbfv8yw-1-0",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "symbol":"BTC-PERP",
|
|
# "match_id":"280841526207053840",
|
|
# "fill_price":"52500",
|
|
# "fill_qty":"0.01",
|
|
# "client_id":"1x59ctku-1-1",
|
|
# "client_order_id":"ccxt3e4e2a5f-4a89-",
|
|
# "order_qty":"0.01",
|
|
# "limit_price":"52500",
|
|
# "total_filled":"0.01",
|
|
# "filled_vwap":"52500",
|
|
# "expire_time":"",
|
|
# "stop_price":"",
|
|
# "side":"BUY",
|
|
# "tif":"GTC",
|
|
# "stp_mode":"BOTH",
|
|
# "flags":"",
|
|
# "fee":"0.105",
|
|
# "fee_asset":"USDC",
|
|
# "order_status":"DONE",
|
|
# "event_time":"2024-02-15T00:43:57.631Z"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(trade, 'symbol')
|
|
datetime = self.safe_string(trade, 'event_time')
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'id': self.safe_string_2(trade, 'fill_id', 'exec_id'),
|
|
'order': self.safe_string(trade, 'order_id'),
|
|
'timestamp': self.parse8601(datetime),
|
|
'datetime': datetime,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'type': None,
|
|
'side': self.safe_string_lower(trade, 'side'),
|
|
'takerOrMaker': None,
|
|
'price': self.safe_number(trade, 'fill_price'),
|
|
'amount': self.safe_number(trade, 'fill_qty'),
|
|
'cost': None,
|
|
'fee': {
|
|
'cost': self.safe_number(trade, 'fee'),
|
|
'currency': self.safe_currency_code(self.safe_string(trade, 'fee_asset')),
|
|
},
|
|
})
|
|
|
|
async def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getinstruments
|
|
|
|
retrieves data on all markets for coinbaseinternational
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = await self.v1PublicGetInstruments(params)
|
|
#
|
|
# [
|
|
# {
|
|
# "instrument_id":"149264164756389888",
|
|
# "instrument_uuid":"e9360798-6a10-45d6-af05-67c30eb91e2d",
|
|
# "symbol":"ETH-PERP",
|
|
# "type":"PERP",
|
|
# "base_asset_id":"118059611793145856",
|
|
# "base_asset_uuid":"d85dce9b-5b73-5c3c-8978-522ce1d1c1b4",
|
|
# "base_asset_name":"ETH",
|
|
# "quote_asset_id":"1",
|
|
# "quote_asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
|
|
# "quote_asset_name":"USDC",
|
|
# "base_increment":"0.0001",
|
|
# "quote_increment":"0.01",
|
|
# "price_band_percent":"0.02",
|
|
# "market_order_percent":"0.0075",
|
|
# "qty_24hr":"44434.8131",
|
|
# "notional_24hr":"110943454.279785",
|
|
# "avg_daily_qty":"1099171.6025",
|
|
# "avg_daily_notional":"2637240145.456987",
|
|
# "previous_day_qty":"78909.3939",
|
|
# "open_interest":"1270.749",
|
|
# "position_limit_qty":"1831.9527",
|
|
# "position_limit_adq_pct":"0.05",
|
|
# "replacement_cost":"0.23",
|
|
# "base_imf":"0.1",
|
|
# "min_notional_value":"10",
|
|
# "funding_interval":"3600000000000",
|
|
# "trading_state":"TRADING",
|
|
# "quote":{
|
|
# "best_bid_price":"2490.8",
|
|
# "best_bid_size":"9.0515",
|
|
# "best_ask_price":"2490.81",
|
|
# "best_ask_size":"4.8486",
|
|
# "trade_price":"2490.39",
|
|
# "trade_qty":"0.9508",
|
|
# "index_price":"2490.5",
|
|
# "mark_price":"2490.8",
|
|
# "settlement_price":"2490.81",
|
|
# "limit_up":"2615.42",
|
|
# "limit_down":"2366.34",
|
|
# "predicted_funding":"0.000009",
|
|
# "timestamp":"2024-02-10T16:07:39.454Z"
|
|
# }
|
|
# },
|
|
# ...
|
|
# ]
|
|
#
|
|
return self.parse_markets(response)
|
|
|
|
def parse_market(self, market: dict) -> Market:
|
|
#
|
|
# {
|
|
# "instrument_id":"149264164756389888",
|
|
# "instrument_uuid":"e9360798-6a10-45d6-af05-67c30eb91e2d",
|
|
# "symbol":"ETH-PERP",
|
|
# "type":"PERP",
|
|
# "base_asset_id":"118059611793145856",
|
|
# "base_asset_uuid":"d85dce9b-5b73-5c3c-8978-522ce1d1c1b4",
|
|
# "base_asset_name":"ETH",
|
|
# "quote_asset_id":"1",
|
|
# "quote_asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
|
|
# "quote_asset_name":"USDC",
|
|
# "base_increment":"0.0001",
|
|
# "quote_increment":"0.01",
|
|
# "price_band_percent":"0.02",
|
|
# "market_order_percent":"0.0075",
|
|
# "qty_24hr":"44434.8131",
|
|
# "notional_24hr":"110943454.279785",
|
|
# "avg_daily_qty":"1099171.6025",
|
|
# "avg_daily_notional":"2637240145.456987",
|
|
# "previous_day_qty":"78909.3939",
|
|
# "open_interest":"1270.749",
|
|
# "position_limit_qty":"1831.9527",
|
|
# "position_limit_adq_pct":"0.05",
|
|
# "replacement_cost":"0.23",
|
|
# "base_imf":"0.1",
|
|
# "min_notional_value":"10",
|
|
# "funding_interval":"3600000000000",
|
|
# "trading_state":"TRADING",
|
|
# "quote":{
|
|
# "best_bid_price":"2490.8",
|
|
# "best_bid_size":"9.0515",
|
|
# "best_ask_price":"2490.81",
|
|
# "best_ask_size":"4.8486",
|
|
# "trade_price":"2490.39",
|
|
# "trade_qty":"0.9508",
|
|
# "index_price":"2490.5",
|
|
# "mark_price":"2490.8",
|
|
# "settlement_price":"2490.81",
|
|
# "limit_up":"2615.42",
|
|
# "limit_down":"2366.34",
|
|
# "predicted_funding":"0.000009",
|
|
# "timestamp":"2024-02-10T16:07:39.454Z"
|
|
# }
|
|
# }
|
|
#
|
|
marketId = self.safe_string(market, 'symbol')
|
|
baseId = self.safe_string(market, 'base_asset_name')
|
|
quoteId = self.safe_string(market, 'quote_asset_name')
|
|
typeId = self.safe_string(market, 'type') # 'SPOT', 'PERP'
|
|
isSpot = (typeId == 'SPOT')
|
|
fees = self.fees
|
|
symbol = baseId + '/' + quoteId
|
|
settleId = None
|
|
if not isSpot:
|
|
settleId = quoteId
|
|
symbol += ':' + quoteId
|
|
return {
|
|
'id': marketId,
|
|
'lowercaseId': marketId.lower(),
|
|
'symbol': symbol,
|
|
'base': baseId,
|
|
'quote': quoteId,
|
|
'settle': settleId if settleId else None,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId if settleId else None,
|
|
'type': 'spot' if isSpot else 'swap',
|
|
'spot': isSpot,
|
|
'margin': False,
|
|
'swap': not isSpot,
|
|
'future': False,
|
|
'option': False,
|
|
'active': self.safe_string(market, 'trading_state') == 'TRADING',
|
|
'contract': not isSpot,
|
|
'linear': None if isSpot else (settleId == quoteId),
|
|
'inverse': None if isSpot else (settleId != quoteId),
|
|
'taker': fees['trading']['taker'],
|
|
'maker': fees['trading']['maker'],
|
|
'contractSize': None if isSpot else 1,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': self.safe_number(market, 'base_increment'),
|
|
'price': self.safe_number(market, 'quote_increment'),
|
|
'cost': self.safe_number(market, 'quote_increment'),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': self.safe_number(market, 'base_imf'),
|
|
},
|
|
'amount': {
|
|
'min': None,
|
|
'max': None if isSpot else self.safe_number(market, 'position_limit_qty'),
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': self.safe_number(market, 'min_notional_value'),
|
|
'max': None,
|
|
},
|
|
},
|
|
'info': market,
|
|
'created': None,
|
|
}
|
|
|
|
async def fetch_currencies(self, params={}) -> Currencies:
|
|
"""
|
|
fetches all available currencies on an exchange
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getassets
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an associative dictionary of currencies
|
|
"""
|
|
currencies = await self.v1PublicGetAssets(params)
|
|
#
|
|
# [
|
|
# {
|
|
# "asset_id":"1",
|
|
# "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
|
|
# "asset_name":"USDC",
|
|
# "status":"ACTIVE",
|
|
# "collateral_weight":1.0,
|
|
# "supported_networks_enabled":true
|
|
# },
|
|
# ...
|
|
# ]
|
|
#
|
|
return self.parse_currencies(currencies)
|
|
|
|
def parse_currency(self, currency: dict) -> Currency:
|
|
#
|
|
# {
|
|
# "asset_id":"1",
|
|
# "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
|
|
# "asset_name":"USDC",
|
|
# "status":"ACTIVE",
|
|
# "collateral_weight":1.0,
|
|
# "supported_networks_enabled":true
|
|
# }
|
|
#
|
|
id = self.safe_string(currency, 'asset_name')
|
|
code = self.safe_currency_code(id)
|
|
statusId = self.safe_string(currency, 'status')
|
|
return self.safe_currency_structure({
|
|
'id': id,
|
|
'name': code,
|
|
'code': code,
|
|
'precision': None,
|
|
'info': currency,
|
|
'active': (statusId == 'ACTIVE'),
|
|
'deposit': None,
|
|
'withdraw': None,
|
|
'networks': None,
|
|
'fee': None,
|
|
'fees': None,
|
|
'limits': self.limits,
|
|
})
|
|
|
|
async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getinstruments
|
|
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
instruments = await self.v1PublicGetInstruments(params)
|
|
tickers: dict = {}
|
|
for i in range(0, len(instruments)):
|
|
instrument = instruments[i]
|
|
marketId = self.safe_string(instrument, 'symbol')
|
|
symbol = self.safe_symbol(marketId)
|
|
quote = self.safe_dict(instrument, 'quote', {})
|
|
tickers[symbol] = self.parse_ticker(quote, self.safe_market(marketId))
|
|
return self.filter_by_array(tickers, 'symbol', symbols, True)
|
|
|
|
async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getinstrumentquote
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'instrument': self.market_id(symbol),
|
|
}
|
|
ticker = await self.v1PublicGetInstrumentsInstrumentQuote(self.extend(request, params))
|
|
return self.parse_ticker(ticker, market)
|
|
|
|
def parse_ticker(self, ticker: object, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# "best_bid_price":"2490.8",
|
|
# "best_bid_size":"9.0515",
|
|
# "best_ask_price":"2490.81",
|
|
# "best_ask_size":"4.8486",
|
|
# "trade_price":"2490.39",
|
|
# "trade_qty":"0.9508",
|
|
# "index_price":"2490.5",
|
|
# "mark_price":"2490.8",
|
|
# "settlement_price":"2490.81",
|
|
# "limit_up":"2615.42",
|
|
# "limit_down":"2366.34",
|
|
# "predicted_funding":"0.000009",
|
|
# "timestamp":"2024-02-10T16:07:39.454Z"
|
|
# }
|
|
#
|
|
datetime = self.safe_string(ticker, 'timestamp')
|
|
return self.safe_ticker({
|
|
'info': ticker,
|
|
'symbol': self.safe_symbol(None, market),
|
|
'timestamp': self.parse8601(datetime),
|
|
'datetime': datetime,
|
|
'bid': self.safe_number(ticker, 'best_bid_price'),
|
|
'bidVolume': self.safe_number(ticker, 'best_bid_size'),
|
|
'ask': self.safe_number(ticker, 'best_ask_price'),
|
|
'askVolume': self.safe_number(ticker, 'best_ask_size'),
|
|
'high': None,
|
|
'low': None,
|
|
'open': None,
|
|
'close': None,
|
|
'last': None,
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': None,
|
|
'vwap': None,
|
|
'baseVolume': None,
|
|
'quoteVolume': None,
|
|
'previousClose': None,
|
|
'markPrice': self.safe_number(ticker, 'mark_price'),
|
|
'indexPrice': self.safe_number(ticker, 'index_price'),
|
|
})
|
|
|
|
async def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getportfoliobalances
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.v3]: default False, set True to use v3 api endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('fetchBalance', params)
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
}
|
|
balances = await self.v1PrivateGetPortfoliosPortfolioBalances(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "asset_id":"0-0-1",
|
|
# "asset_name":"USDC",
|
|
# "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
|
|
# "quantity":"500000.0000000000",
|
|
# "hold":"0",
|
|
# "hold_available_for_collateral":"0",
|
|
# "transfer_hold":"0",
|
|
# "collateral_value":"500000.0",
|
|
# "max_withdraw_amount":"500000.0000000000",
|
|
# "loan":"0",
|
|
# "loan_collateral_requirement":"0.0"
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_balance(balances)
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
#
|
|
# {
|
|
# "asset_id":"0-0-1",
|
|
# "asset_name":"USDC",
|
|
# "asset_uuid":"2b92315d-eab7-5bef-84fa-089a131333f5",
|
|
# "quantity":"500000.0000000000",
|
|
# "hold":"0",
|
|
# "hold_available_for_collateral":"0",
|
|
# "transfer_hold":"0",
|
|
# "collateral_value":"500000.0",
|
|
# "max_withdraw_amount":"500000.0000000000",
|
|
# "loan":"0",
|
|
# "loan_collateral_requirement":"0.0"
|
|
# }
|
|
#
|
|
result: dict = {
|
|
'info': response,
|
|
}
|
|
for i in range(0, len(response)):
|
|
rawBalance = response[i]
|
|
currencyId = self.safe_string(rawBalance, 'asset_name')
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['total'] = self.safe_string(rawBalance, 'quantity')
|
|
account['used'] = self.safe_string(rawBalance, 'hold')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
|
"""
|
|
Transfer an amount of asset from one portfolio to another.
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/createportfolioassettransfer
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: amount to transfer
|
|
:param str fromAccount: account to transfer from
|
|
:param str toAccount: account to transfer to
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transfer structure <https://github.com/ccxt/ccxt/wiki/Manual#transfer-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
'ammount': amount,
|
|
'from': fromAccount,
|
|
'to': toAccount,
|
|
}
|
|
response = await self.v1PrivatePostPortfoliosTransfer(self.extend(request, params))
|
|
success = self.safe_bool(response, 'success')
|
|
return {
|
|
'info': response,
|
|
'id': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'currency': code,
|
|
'amount': amount,
|
|
'fromAccount': fromAccount,
|
|
'toAccount': toAccount,
|
|
'status': 'ok' if success else 'failed',
|
|
}
|
|
|
|
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: float = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/createorder
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market' or 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much you want to trade in units of the base currency, quote currency for 'market' 'buy' orders
|
|
:param float [price]: the price to fulfill the order, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.stopPrice]: alias for triggerPrice
|
|
:param float [params.triggerPrice]: price to trigger stop orders
|
|
:param float [params.stopLossPrice]: price to trigger stop-loss orders
|
|
:param bool [params.postOnly]: True or False
|
|
:param str [params.tif]: 'GTC', 'IOC', 'GTD' default is 'GTC' for limit orders and 'IOC' for market orders
|
|
:param str [params.expire_time]: The expiration time required for orders with the time in force set to GTT. Must not go beyond 30 days of the current time. Uses ISO-8601 format(e.g., 2023-03-16T23:59:53Z)
|
|
:param str [params.stp_mode]: Possible values: [NONE, AGGRESSING, BOTH] Specifies the behavior for self match handling. None disables the functionality, new cancels the newest order, and both cancels both orders.
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
typeId = type.upper()
|
|
triggerPrice = self.safe_number_n(params, ['triggerPrice', 'stopPrice', 'stop_price'])
|
|
clientOrderIdprefix = self.safe_string(self.options, 'brokerId', 'nfqkvdjp')
|
|
clientOrderId = clientOrderIdprefix + '-' + self.uuid()
|
|
clientOrderId = clientOrderId[0:17]
|
|
request: dict = {
|
|
'client_order_id': clientOrderId,
|
|
'side': side.upper(),
|
|
'instrument': market['id'],
|
|
'size': self.amount_to_precision(market['symbol'], amount),
|
|
}
|
|
if triggerPrice is not None:
|
|
if type == 'limit':
|
|
typeId = 'STOP_LIMIT'
|
|
else:
|
|
typeId = 'STOP'
|
|
request['stop_price'] = triggerPrice
|
|
request['type'] = typeId
|
|
if type == 'limit':
|
|
if price is None:
|
|
raise InvalidOrder(self.id + ' createOrder() requires a price parameter for a limit order types')
|
|
request['price'] = price
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('createOrder', params)
|
|
if portfolio is not None:
|
|
request['portfolio'] = portfolio
|
|
postOnly = self.safe_bool_2(params, 'postOnly', 'post_only')
|
|
tif = self.safe_string_2(params, 'tif', 'timeInForce')
|
|
# market orders must be IOC
|
|
if typeId == 'MARKET':
|
|
if tif is not None and tif != 'IOC':
|
|
raise InvalidOrder(self.id + ' createOrder() market orders must have tif set to "IOC"')
|
|
tif = 'IOC'
|
|
else:
|
|
tif = 'GTC' if (tif is None) else tif
|
|
if postOnly is not None:
|
|
request['post_only'] = postOnly
|
|
request['tif'] = tif
|
|
params = self.omit(params, ['client_order_id', 'user', 'postOnly', 'timeInForce'])
|
|
response = await self.v1PrivatePostOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "order_id":"1x96skvg-1-0",
|
|
# "client_order_id":"ccxt",
|
|
# "side":"BUY",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "symbol":"BTC-PERP",
|
|
# "portfolio_id":"1wp37qsc-1-0",
|
|
# "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "type":"LIMIT",
|
|
# "price":"10000",
|
|
# "size":"0.001",
|
|
# "tif":"GTC",
|
|
# "stp_mode":"BOTH",
|
|
# "event_type":"NEW",
|
|
# "order_status":"WORKING",
|
|
# "leaves_qty":"0.001",
|
|
# "exec_qty":"0",
|
|
# "avg_price":"0",
|
|
# "fee":"0"
|
|
# }
|
|
#
|
|
return self.parse_order(response, market)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# {
|
|
# "order_id":"1x96skvg-1-0",
|
|
# "client_order_id":"ccxt",
|
|
# "side":"BUY",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "symbol":"BTC-PERP",
|
|
# "portfolio_id":"1wp37qsc-1-0",
|
|
# "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "type":"LIMIT",
|
|
# "price":"10000",
|
|
# "size":"0.001",
|
|
# "tif":"GTC",
|
|
# "stp_mode":"BOTH",
|
|
# "event_type":"NEW",
|
|
# "order_status":"WORKING",
|
|
# "leaves_qty":"0.001",
|
|
# "exec_qty":"0",
|
|
# "avg_price":"0",
|
|
# "fee":"0"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(order, 'symbol')
|
|
feeCost = self.safe_number(order, 'fee')
|
|
fee = None
|
|
if feeCost is not None:
|
|
fee = {
|
|
'cost': feeCost,
|
|
}
|
|
datetime = self.safe_string_2(order, 'submit_time', 'event_time')
|
|
return self.safe_order({
|
|
'info': order,
|
|
'id': self.safe_string(order, 'order_id'),
|
|
'clientOrderId': self.safe_string(order, 'client_order_id'),
|
|
'timestamp': self.parse8601(datetime),
|
|
'datetime': datetime,
|
|
'lastTradeTimestamp': None,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'type': self.parse_order_type(self.safe_string(order, 'type')),
|
|
'timeInForce': self.safe_string(order, 'tif'),
|
|
'postOnly': None,
|
|
'side': self.safe_string_lower(order, 'side'),
|
|
'price': self.safe_string(order, 'price'),
|
|
'triggerPrice': self.safe_string(order, 'stop_price'),
|
|
'amount': self.safe_string(order, 'size'),
|
|
'filled': self.safe_string(order, 'exec_qty'),
|
|
'remaining': self.safe_string(order, 'leaves_qty'),
|
|
'cost': None,
|
|
'average': self.safe_string(order, 'avg_price'),
|
|
'status': self.parse_order_status(self.safe_string(order, 'order_status')),
|
|
'fee': fee,
|
|
'trades': None,
|
|
}, market)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'NEW': 'open',
|
|
'PARTIAL_FILLED': 'open',
|
|
'FILLED': 'closed',
|
|
'CANCELED': 'canceled',
|
|
'REPLACED': 'canceled',
|
|
'PENDING_CANCEL': 'open',
|
|
'REJECTED': 'rejected',
|
|
'PENDING_NEW': 'open',
|
|
'EXPIRED': 'expired',
|
|
'PENDING_REPLACE': 'open',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order_type(self, type: Str):
|
|
if type == 'UNKNOWN_ORDER_TYPE':
|
|
return None
|
|
types: dict = {
|
|
'MARKET': 'market',
|
|
'LIMIT': 'limit',
|
|
'STOP': 'limit',
|
|
'STOP_LIMIT': 'limit',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/cancelorder
|
|
|
|
:param str id: order id
|
|
:param str symbol: not used by coinbaseinternational cancelOrder()
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('cancelOrder', params)
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
'id': id,
|
|
}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
orders = await self.v1PrivateDeleteOrdersId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "order_id":"1x96skvg-1-0",
|
|
# "client_order_id":"ccxt",
|
|
# "side":"BUY",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "symbol":"BTC-PERP",
|
|
# "portfolio_id":"1wp37qsc-1-0",
|
|
# "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "type":"LIMIT",
|
|
# "price":"10000",
|
|
# "size":"0.001",
|
|
# "tif":"GTC",
|
|
# "stp_mode":"BOTH",
|
|
# "event_type":"CANCELED",
|
|
# "order_status":"DONE",
|
|
# "leaves_qty":"0.001",
|
|
# "exec_qty":"0",
|
|
# "avg_price":"0",
|
|
# "fee":"0"
|
|
# }
|
|
#
|
|
return self.parse_order(orders, market)
|
|
|
|
async def cancel_all_orders(self, symbol: str = None, params={}):
|
|
"""
|
|
cancel all open orders
|
|
:param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('cancelAllOrders', params)
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
}
|
|
market = None
|
|
if symbol:
|
|
market = self.market(symbol)
|
|
request['instrument'] = market['id']
|
|
orders = await self.v1PrivateDeleteOrders(self.extend(request, params))
|
|
return self.parse_orders(orders, market)
|
|
|
|
async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: float = None, price: float = None, params={}):
|
|
"""
|
|
edit a trade order
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/modifyorder
|
|
|
|
:param str id: cancel order id
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market' or 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much of currency you want to trade in units of base currency
|
|
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str params['clientOrderId']: client order id
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'id': id,
|
|
}
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('editOrder', params)
|
|
if portfolio is not None:
|
|
request['portfolio'] = portfolio
|
|
if amount is not None:
|
|
request['size'] = self.amount_to_precision(symbol, amount)
|
|
if price is not None:
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
triggerPrice = self.safe_number_n(params, ['stopPrice', 'stop_price', 'triggerPrice'])
|
|
if triggerPrice is not None:
|
|
request['stop_price'] = triggerPrice
|
|
clientOrderId = self.safe_string_2(params, 'client_order_id', 'clientOrderId')
|
|
if clientOrderId is None:
|
|
raise BadRequest(self.id + ' editOrder() requires a clientOrderId parameter')
|
|
request['client_order_id'] = clientOrderId
|
|
order = await self.v1PrivatePutOrdersId(self.extend(request, params))
|
|
return self.parse_order(order, market)
|
|
|
|
async def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/modifyorder
|
|
|
|
:param str id: the order id
|
|
:param str symbol: unified market symbol that the order was made in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('fetchOrder', params)
|
|
request: dict = {
|
|
'id': id,
|
|
'portfolio': portfolio,
|
|
}
|
|
order = await self.v1PrivateGetOrdersId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "order_id":"1x96skvg-1-0",
|
|
# "client_order_id":"ccxt",
|
|
# "side":"BUY",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "symbol":"BTC-PERP",
|
|
# "portfolio_id":"1wp37qsc-1-0",
|
|
# "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "type":"LIMIT",
|
|
# "price":"10000",
|
|
# "size":"0.001",
|
|
# "tif":"GTC",
|
|
# "stp_mode":"BOTH",
|
|
# "event_type":"NEW",
|
|
# "event_time":"2024-02-14T03:25:14Z",
|
|
# "submit_time":"2024-02-14T03:25:13.999Z",
|
|
# "order_status":"WORKING",
|
|
# "leaves_qty":"0.001",
|
|
# "exec_qty":"0",
|
|
# "avg_price":"0",
|
|
# "fee":"0"
|
|
# }
|
|
#
|
|
return self.parse_order(order, market)
|
|
|
|
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on all currently open orders
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getorders
|
|
|
|
:param str symbol: unified market symbol of the orders
|
|
:param int [since]: timestamp in ms of the earliest order, default is None
|
|
:param int [limit]: the maximum number of open order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:param int [params.offset]: offset
|
|
:param str [params.event_type]: The most recent type of event that happened to the order. Allowed values: NEW, TRADE, REPLACED
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('fetchOpenOrders', params)
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'paginate')
|
|
maxEntriesPerRequest = None
|
|
maxEntriesPerRequest, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'maxEntriesPerRequest', 100)
|
|
pageKey = 'ccxtPageKey'
|
|
if paginate:
|
|
return await self.fetch_paginated_call_incremental('fetchOpenOrders', symbol, since, limit, params, pageKey, maxEntriesPerRequest)
|
|
page = self.safe_integer(params, pageKey, 1) - 1
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
'result_offset': self.safe_integer_2(params, 'offset', 'result_offset', page * maxEntriesPerRequest),
|
|
}
|
|
market = None
|
|
if symbol:
|
|
market = self.market(symbol)
|
|
request['instrument'] = symbol
|
|
if limit is not None:
|
|
if limit > 100:
|
|
raise BadRequest(self.id + ' fetchOpenOrders() maximum limit is 100')
|
|
request['result_limit'] = limit
|
|
if since is not None:
|
|
request['ref_datetime'] = self.iso8601(since)
|
|
response = await self.v1PrivateGetOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "pagination":{
|
|
# "result_limit":25,
|
|
# "result_offset":0
|
|
# },
|
|
# "results":[
|
|
# {
|
|
# "order_id":"1y4cm6b4-1-0",
|
|
# "client_order_id":"ccxtd0dd4b5d-8e5f-",
|
|
# "side":"SELL",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "symbol":"BTC-PERP",
|
|
# "portfolio_id":"1wp37qsc-1-0",
|
|
# "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "type":"LIMIT",
|
|
# "price":"54000",
|
|
# "size":"0.01",
|
|
# "tif":"GTC",
|
|
# "stp_mode":"BOTH",
|
|
# "event_type":"NEW",
|
|
# "event_time":"2024-02-24T16:46:37.413Z",
|
|
# "submit_time":"2024-02-24T16:46:37.412Z",
|
|
# "order_status":"WORKING",
|
|
# "leaves_qty":"0.01",
|
|
# "exec_qty":"0",
|
|
# "avg_price":"0",
|
|
# "fee":"0"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
rawOrders = self.safe_list(response, 'results', [])
|
|
return self.parse_orders(rawOrders, market, since, limit)
|
|
|
|
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/getmultiportfoliofills
|
|
|
|
:param str symbol: unified market symbol of the trades
|
|
:param int [since]: timestamp in ms of the earliest order, default is None
|
|
:param int [limit]: the maximum number of trade structures to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch trades for
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
|
|
pageKey = 'ccxtPageKey'
|
|
maxEntriesPerRequest = None
|
|
maxEntriesPerRequest, params = self.handle_option_and_params(params, 'fetchMyTrades', 'maxEntriesPerRequest', 100)
|
|
if paginate:
|
|
return await self.fetch_paginated_call_incremental('fetchMyTrades', symbol, since, limit, params, pageKey, maxEntriesPerRequest)
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
page = self.safe_integer(params, pageKey, 1) - 1
|
|
request: dict = {
|
|
'result_offset': self.safe_integer_2(params, 'offset', 'result_offset', page * maxEntriesPerRequest),
|
|
}
|
|
if limit is not None:
|
|
if limit > 100:
|
|
raise BadRequest(self.id + ' fetchMyTrades() maximum limit is 100. Consider setting paginate to True to fetch more trades.')
|
|
request['result_limit'] = limit
|
|
if since is not None:
|
|
request['time_from'] = self.iso8601(since)
|
|
until = self.safe_string_n(params, ['until'])
|
|
if until is not None:
|
|
params = self.omit(params, ['until'])
|
|
request['ref_datetime'] = self.iso8601(until)
|
|
response = await self.v1PrivateGetPortfoliosFills(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "pagination":{
|
|
# "result_limit":25,
|
|
# "result_offset":0
|
|
# },
|
|
# "results":[
|
|
# {
|
|
# "portfolio_id":"1wp37qsc-1-0",
|
|
# "portfolio_uuid":"018d7f6c-b92c-7361-8b7e-2932711e5a22",
|
|
# "portfolio_name":"CCXT Portfolio 020624-17:16",
|
|
# "fill_id":"1xbfy19y-1-184",
|
|
# "exec_id":"280841526207070392",
|
|
# "order_id":"1xbfv8yw-1-0",
|
|
# "instrument_id":"114jqr89-0-0",
|
|
# "instrument_uuid":"b3469e0b-222c-4f8a-9f68-1f9e44d7e5e0",
|
|
# "symbol":"BTC-PERP",
|
|
# "match_id":"280841526207053840",
|
|
# "fill_price":"52500",
|
|
# "fill_qty":"0.01",
|
|
# "client_id":"1x59ctku-1-1",
|
|
# "client_order_id":"ccxt3e4e2a5f-4a89-",
|
|
# "order_qty":"0.01",
|
|
# "limit_price":"52500",
|
|
# "total_filled":"0.01",
|
|
# "filled_vwap":"52500",
|
|
# "expire_time":"",
|
|
# "stop_price":"",
|
|
# "side":"BUY",
|
|
# "tif":"GTC",
|
|
# "stp_mode":"BOTH",
|
|
# "flags":"",
|
|
# "fee":"0.105",
|
|
# "fee_asset":"USDC",
|
|
# "order_status":"DONE",
|
|
# "event_time":"2024-02-15T00:43:57.631Z"
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
trades = self.safe_list(response, 'results', [])
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://docs.cloud.coinbase.com/intx/reference/withdraw
|
|
https://docs.cloud.coinbase.com/intx/reference/counterpartywithdraw
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: the amount to withdraw
|
|
:param str address: the address to withdraw to
|
|
:param str [tag]: an optional tag for the withdrawal
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.add_network_fee_to_total]: if True, deducts network fee from the portfolio, otherwise deduct fee from the withdrawal
|
|
:param str [params.network_arn_id]: Identifies the blockchain network(e.g., networks/ethereum-mainnet/assets/313ef8a9-ae5a-5f2f-8a56-572c0e2a4d5a)
|
|
:param str [params.nonce]: a unique integer representing the withdrawal request
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
|
self.check_address(address)
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
portfolio = None
|
|
portfolio, params = await self.handle_portfolio_and_params('withdraw', params)
|
|
method = None
|
|
method, params = self.handle_option_and_params(params, 'withdraw', 'method', 'v1PrivatePostTransfersWithdraw')
|
|
networkId = None
|
|
networkId, params = await self.handle_network_id_and_params(code, 'withdraw', params)
|
|
request: dict = {
|
|
'portfolio': portfolio,
|
|
'type': 'send',
|
|
'asset': currency['id'],
|
|
'address': address,
|
|
'amount': amount,
|
|
'currency': currency['id'],
|
|
'network_arn_id': networkId,
|
|
'nonce': self.nonce(),
|
|
}
|
|
response = await getattr(self, method)(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "idem":"8e471d77-4208-45a8-9e5b-f3bd8a2c1fc3"
|
|
# }
|
|
#
|
|
return self.parse_transaction(response, currency)
|
|
|
|
def safe_network(self, network):
|
|
withdrawEnabled = self.safe_bool(network, 'withdraw')
|
|
depositEnabled = self.safe_bool(network, 'deposit')
|
|
limits = self.safe_dict(network, 'limits')
|
|
withdraw = self.safe_dict(limits, 'withdraw')
|
|
withdrawMax = self.safe_number(withdraw, 'max')
|
|
deposit = self.safe_dict(limits, 'deposit')
|
|
depositMax = self.safe_number(deposit, 'max')
|
|
if withdrawEnabled is None and withdrawMax is not None:
|
|
withdrawEnabled = (withdrawMax > 0)
|
|
if depositEnabled is None and depositMax is not None:
|
|
depositEnabled = (depositMax > 0)
|
|
networkId = self.safe_string(network, 'id')
|
|
isEnabled = (withdrawEnabled and depositEnabled)
|
|
return {
|
|
'info': network['info'],
|
|
'id': networkId,
|
|
'name': self.safe_string(network, 'name'),
|
|
'network': self.safe_string(network, 'network'),
|
|
'active': self.safe_bool(network, 'active', isEnabled),
|
|
'deposit': depositEnabled,
|
|
'withdraw': withdrawEnabled,
|
|
'fee': self.safe_number(network, 'fee'),
|
|
'precision': self.safe_number(network, 'precision'),
|
|
'limits': {
|
|
'withdraw': {
|
|
'min': self.safe_number(withdraw, 'min'),
|
|
'max': withdrawMax,
|
|
},
|
|
'deposit': {
|
|
'min': self.safe_number(deposit, 'min'),
|
|
'max': depositMax,
|
|
},
|
|
},
|
|
}
|
|
|
|
def sign(self, path, api=[], method='GET', params={}, headers=None, body=None):
|
|
version = api[0]
|
|
signed = api[1] == 'private'
|
|
fullPath = '/' + version + '/' + self.implode_params(path, params)
|
|
query = self.omit(params, self.extract_params(path))
|
|
savedPath = '/api' + fullPath
|
|
if method == 'GET' or method == 'DELETE':
|
|
if query:
|
|
fullPath += '?' + self.urlencode_with_array_repeat(query)
|
|
url = self.urls['api']['rest'] + fullPath
|
|
if signed:
|
|
self.check_required_credentials()
|
|
nonce = str(self.nonce())
|
|
payload = ''
|
|
if method != 'GET':
|
|
if query:
|
|
body = self.json(query)
|
|
payload = body
|
|
auth = nonce + method + savedPath + payload
|
|
signature = self.hmac(self.encode(auth), self.base64_to_binary(self.secret), hashlib.sha256, 'base64')
|
|
headers = {
|
|
'CB-ACCESS-TIMESTAMP': nonce,
|
|
'CB-ACCESS-SIGN': signature,
|
|
'CB-ACCESS-PASSPHRASE': self.password,
|
|
'CB-ACCESS-KEY': self.apiKey,
|
|
}
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
#
|
|
# {
|
|
# "title":"io.javalin.http.BadRequestResponse: Order rejected(DUPLICATE_CLIENT_ORDER_ID - duplicate client order id detected)",
|
|
# "status":400
|
|
# }
|
|
#
|
|
if response is None:
|
|
return None # fallback to default error handler
|
|
feedback = self.id + ' ' + body
|
|
errMsg = self.safe_string(response, 'title')
|
|
if errMsg is not None:
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], errMsg, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], errMsg, feedback)
|
|
raise ExchangeError(feedback)
|
|
return None
|