3380 lines
149 KiB
Python
3380 lines
149 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.base.exchange import Exchange
|
|
from ccxt.abstract.cryptocom import ImplicitAPI
|
|
import hashlib
|
|
import math
|
|
from ccxt.base.types import Account, Any, Balances, Currencies, Currency, DepositAddress, Int, LedgerEntry, Market, Num, Order, OrderBook, OrderRequest, CancellationRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, Trade, TradingFeeInterface, TradingFees, Transaction
|
|
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 AccountNotEnabled
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import BadSymbol
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import OrderNotFound
|
|
from ccxt.base.errors import NotSupported
|
|
from ccxt.base.errors import DDoSProtection
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.errors import OnMaintenance
|
|
from ccxt.base.errors import InvalidNonce
|
|
from ccxt.base.errors import RequestTimeout
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class cryptocom(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(cryptocom, self).describe(), {
|
|
'id': 'cryptocom',
|
|
'name': 'Crypto.com',
|
|
'countries': ['MT'],
|
|
'version': 'v2',
|
|
'rateLimit': 10, # 100 requests per second
|
|
'certified': True,
|
|
'pro': True,
|
|
'has': {
|
|
'CORS': False,
|
|
'spot': True,
|
|
'margin': True,
|
|
'swap': True,
|
|
'future': True,
|
|
'option': True,
|
|
'addMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'cancelOrders': True,
|
|
'cancelOrdersForSymbols': True,
|
|
'closeAllPositions': False,
|
|
'closePosition': True,
|
|
'createMarketBuyOrderWithCost': False,
|
|
'createMarketOrderWithCost': False,
|
|
'createMarketSellOrderWithCost': False,
|
|
'createOrder': True,
|
|
'createOrders': True,
|
|
'createStopOrder': True,
|
|
'createTriggerOrder': True,
|
|
'editOrder': True,
|
|
'fetchAccounts': True,
|
|
'fetchBalance': True,
|
|
'fetchBidsAsks': False,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchClosedOrders': 'emulated',
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': True,
|
|
'fetchDepositAddress': True,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDepositAddressesByNetwork': True,
|
|
'fetchDeposits': True,
|
|
'fetchDepositsWithdrawals': False,
|
|
'fetchDepositWithdrawFee': 'emulated',
|
|
'fetchDepositWithdrawFees': True,
|
|
'fetchFundingHistory': False,
|
|
'fetchFundingRate': True,
|
|
'fetchFundingRateHistory': True,
|
|
'fetchFundingRates': False,
|
|
'fetchGreeks': False,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchLedger': True,
|
|
'fetchLeverage': False,
|
|
'fetchLeverageTiers': False,
|
|
'fetchMarginAdjustmentHistory': False,
|
|
'fetchMarginMode': False,
|
|
'fetchMarketLeverageTiers': False,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMySettlementHistory': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': True,
|
|
'fetchPosition': True,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': True,
|
|
'fetchPositionsHistory': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchSettlementHistory': True,
|
|
'fetchStatus': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': False,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': True,
|
|
'fetchTradingFees': True,
|
|
'fetchTransactionFees': False,
|
|
'fetchTransactions': False,
|
|
'fetchTransfers': False,
|
|
'fetchUnderlyingAssets': False,
|
|
'fetchVolatilityHistory': False,
|
|
'fetchWithdrawals': True,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'sandbox': True,
|
|
'setLeverage': False,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'transfer': False,
|
|
'withdraw': True,
|
|
},
|
|
'timeframes': {
|
|
'1m': '1m',
|
|
'5m': '5m',
|
|
'15m': '15m',
|
|
'30m': '30m',
|
|
'1h': '1h',
|
|
'4h': '4h',
|
|
'6h': '6h',
|
|
'12h': '12h',
|
|
'1d': '1D',
|
|
'1w': '7D',
|
|
'2w': '14D',
|
|
'1M': '1M',
|
|
},
|
|
'urls': {
|
|
'logo': 'https://user-images.githubusercontent.com/1294454/147792121-38ed5e36-c229-48d6-b49a-48d05fc19ed4.jpeg',
|
|
'test': {
|
|
'v1': 'https://uat-api.3ona.co/exchange/v1',
|
|
'v2': 'https://uat-api.3ona.co/v2',
|
|
'derivatives': 'https://uat-api.3ona.co/v2',
|
|
},
|
|
'api': {
|
|
'base': 'https://api.crypto.com',
|
|
'v1': 'https://api.crypto.com/exchange/v1',
|
|
'v2': 'https://api.crypto.com/v2',
|
|
'derivatives': 'https://deriv-api.crypto.com/v1',
|
|
},
|
|
'www': 'https://crypto.com/',
|
|
'referral': {
|
|
'url': 'https://crypto.com/exch/kdacthrnxt',
|
|
'discount': 0.75,
|
|
},
|
|
'doc': [
|
|
'https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html',
|
|
'https://exchange-docs.crypto.com/spot/index.html',
|
|
'https://exchange-docs.crypto.com/derivatives/index.html',
|
|
],
|
|
'fees': 'https://crypto.com/exchange/document/fees-limits',
|
|
},
|
|
'api': {
|
|
'base': {
|
|
'public': {
|
|
'get': {
|
|
'v1/public/get-announcements': 1, # no description of rate limit
|
|
},
|
|
},
|
|
},
|
|
'v1': {
|
|
'public': {
|
|
'get': {
|
|
'public/auth': 10 / 3,
|
|
'public/get-instruments': 10 / 3,
|
|
'public/get-book': 1,
|
|
'public/get-candlestick': 1,
|
|
'public/get-trades': 1,
|
|
'public/get-tickers': 1,
|
|
'public/get-valuations': 1,
|
|
'public/get-expired-settlement-price': 10 / 3,
|
|
'public/get-insurance': 1,
|
|
'public/get-risk-parameters': 1,
|
|
},
|
|
'post': {
|
|
'public/staking/get-conversion-rate': 2,
|
|
},
|
|
},
|
|
'private': {
|
|
'post': {
|
|
'private/set-cancel-on-disconnect': 10 / 3,
|
|
'private/get-cancel-on-disconnect': 10 / 3,
|
|
'private/user-balance': 10 / 3,
|
|
'private/user-balance-history': 10 / 3,
|
|
'private/get-positions': 10 / 3,
|
|
'private/create-order': 2 / 3,
|
|
'private/amend-order': 4 / 3, # no description of rate limit
|
|
'private/create-order-list': 10 / 3,
|
|
'private/cancel-order': 2 / 3,
|
|
'private/cancel-order-list': 10 / 3,
|
|
'private/cancel-all-orders': 2 / 3,
|
|
'private/close-position': 10 / 3,
|
|
'private/get-order-history': 100,
|
|
'private/get-open-orders': 10 / 3,
|
|
'private/get-order-detail': 1 / 3,
|
|
'private/get-trades': 100,
|
|
'private/change-account-leverage': 10 / 3,
|
|
'private/get-transactions': 10 / 3,
|
|
'private/create-subaccount-transfer': 10 / 3,
|
|
'private/get-subaccount-balances': 10 / 3,
|
|
'private/get-order-list': 10 / 3,
|
|
'private/create-withdrawal': 10 / 3,
|
|
'private/get-currency-networks': 10 / 3,
|
|
'private/get-deposit-address': 10 / 3,
|
|
'private/get-accounts': 10 / 3,
|
|
'private/get-withdrawal-history': 10 / 3,
|
|
'private/get-deposit-history': 10 / 3,
|
|
'private/get-fee-rate': 2,
|
|
'private/get-instrument-fee-rate': 2,
|
|
'private/staking/stake': 2,
|
|
'private/staking/unstake': 2,
|
|
'private/staking/get-staking-position': 2,
|
|
'private/staking/get-staking-instruments': 2,
|
|
'private/staking/get-open-stake': 2,
|
|
'private/staking/get-stake-history': 2,
|
|
'private/staking/get-reward-history': 2,
|
|
'private/staking/convert': 2,
|
|
'private/staking/get-open-convert': 2,
|
|
'private/staking/get-convert-history': 2,
|
|
},
|
|
},
|
|
},
|
|
'v2': {
|
|
'public': {
|
|
'get': {
|
|
'public/auth': 1,
|
|
'public/get-instruments': 1,
|
|
'public/get-book': 1,
|
|
'public/get-candlestick': 1,
|
|
'public/get-ticker': 1,
|
|
'public/get-trades': 1,
|
|
'public/margin/get-transfer-currencies': 1,
|
|
'public/margin/get-load-currenices': 1,
|
|
'public/respond-heartbeat': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'post': {
|
|
'private/set-cancel-on-disconnect': 10 / 3,
|
|
'private/get-cancel-on-disconnect': 10 / 3,
|
|
'private/create-withdrawal': 10 / 3,
|
|
'private/get-withdrawal-history': 10 / 3,
|
|
'private/get-currency-networks': 10 / 3,
|
|
'private/get-deposit-history': 10 / 3,
|
|
'private/get-deposit-address': 10 / 3,
|
|
'private/export/create-export-request': 10 / 3,
|
|
'private/export/get-export-requests': 10 / 3,
|
|
'private/export/download-export-output': 10 / 3,
|
|
'private/get-account-summary': 10 / 3,
|
|
'private/create-order': 2 / 3,
|
|
'private/cancel-order': 2 / 3,
|
|
'private/cancel-all-orders': 2 / 3,
|
|
'private/create-order-list': 10 / 3,
|
|
'private/get-order-history': 10 / 3,
|
|
'private/get-open-orders': 10 / 3,
|
|
'private/get-order-detail': 1 / 3,
|
|
'private/get-trades': 100,
|
|
'private/get-accounts': 10 / 3,
|
|
'private/get-subaccount-balances': 10 / 3,
|
|
'private/create-subaccount-transfer': 10 / 3,
|
|
'private/otc/get-otc-user': 10 / 3,
|
|
'private/otc/get-instruments': 10 / 3,
|
|
'private/otc/request-quote': 100,
|
|
'private/otc/accept-quote': 100,
|
|
'private/otc/get-quote-history': 10 / 3,
|
|
'private/otc/get-trade-history': 10 / 3,
|
|
'private/otc/create-order': 10 / 3,
|
|
},
|
|
},
|
|
},
|
|
'derivatives': {
|
|
'public': {
|
|
'get': {
|
|
'public/auth': 10 / 3,
|
|
'public/get-instruments': 10 / 3,
|
|
'public/get-book': 1,
|
|
'public/get-candlestick': 1,
|
|
'public/get-trades': 1,
|
|
'public/get-tickers': 1,
|
|
'public/get-valuations': 1,
|
|
'public/get-expired-settlement-price': 10 / 3,
|
|
'public/get-insurance': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'post': {
|
|
'private/set-cancel-on-disconnect': 10 / 3,
|
|
'private/get-cancel-on-disconnect': 10 / 3,
|
|
'private/user-balance': 10 / 3,
|
|
'private/user-balance-history': 10 / 3,
|
|
'private/get-positions': 10 / 3,
|
|
'private/create-order': 2 / 3,
|
|
'private/create-order-list': 10 / 3,
|
|
'private/cancel-order': 2 / 3,
|
|
'private/cancel-order-list': 10 / 3,
|
|
'private/cancel-all-orders': 2 / 3,
|
|
'private/close-position': 10 / 3,
|
|
'private/convert-collateral': 10 / 3,
|
|
'private/get-order-history': 100,
|
|
'private/get-open-orders': 10 / 3,
|
|
'private/get-order-detail': 1 / 3,
|
|
'private/get-trades': 100,
|
|
'private/change-account-leverage': 10 / 3,
|
|
'private/get-transactions': 10 / 3,
|
|
'private/create-subaccount-transfer': 10 / 3,
|
|
'private/get-subaccount-balances': 10 / 3,
|
|
'private/get-order-list': 10 / 3,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'maker': self.parse_number('0.0025'),
|
|
'taker': self.parse_number('0.005'),
|
|
'tiers': {
|
|
'maker': [
|
|
[self.parse_number('0'), self.parse_number('0.0025')],
|
|
[self.parse_number('10000'), self.parse_number('0.002')],
|
|
[self.parse_number('50000'), self.parse_number('0.0015')],
|
|
[self.parse_number('250000'), self.parse_number('0.001')],
|
|
[self.parse_number('500000'), self.parse_number('0.0008')],
|
|
[self.parse_number('2500000'), self.parse_number('0.00065')],
|
|
[self.parse_number('10000000'), self.parse_number('0')],
|
|
[self.parse_number('25000000'), self.parse_number('0')],
|
|
[self.parse_number('100000000'), self.parse_number('0')],
|
|
[self.parse_number('250000000'), self.parse_number('0')],
|
|
[self.parse_number('500000000'), self.parse_number('0')],
|
|
],
|
|
'taker': [
|
|
[self.parse_number('0'), self.parse_number('0.005')],
|
|
[self.parse_number('10000'), self.parse_number('0.004')],
|
|
[self.parse_number('50000'), self.parse_number('0.0025')],
|
|
[self.parse_number('250000'), self.parse_number('0.002')],
|
|
[self.parse_number('500000'), self.parse_number('0.0018')],
|
|
[self.parse_number('2500000'), self.parse_number('0.001')],
|
|
[self.parse_number('10000000'), self.parse_number('0.0005')],
|
|
[self.parse_number('25000000'), self.parse_number('0.0004')],
|
|
[self.parse_number('100000000'), self.parse_number('0.00035')],
|
|
[self.parse_number('250000000'), self.parse_number('0.00031')],
|
|
[self.parse_number('500000000'), self.parse_number('0.00025')],
|
|
],
|
|
},
|
|
},
|
|
},
|
|
'options': {
|
|
'defaultType': 'spot',
|
|
'accountsById': {
|
|
'funding': 'SPOT',
|
|
'spot': 'SPOT',
|
|
'margin': 'MARGIN',
|
|
'derivatives': 'DERIVATIVES',
|
|
'swap': 'DERIVATIVES',
|
|
'future': 'DERIVATIVES',
|
|
},
|
|
'networks': {
|
|
'BEP20': 'BSC',
|
|
'ERC20': 'ETH',
|
|
'TRC20': 'TRON',
|
|
},
|
|
'broker': 'CCXT',
|
|
},
|
|
'features': {
|
|
'default': {
|
|
'sandbox': True,
|
|
'createOrder': {
|
|
'marginMode': True,
|
|
'triggerPrice': True,
|
|
# todo: implementation fix
|
|
'triggerPriceType': {
|
|
'last': True,
|
|
'mark': True,
|
|
'index': True,
|
|
},
|
|
'triggerDirection': False,
|
|
'stopLossPrice': True,
|
|
'takeProfitPrice': True,
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': True,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'selfTradePrevention': True, # todo: implement
|
|
'trailing': False,
|
|
'iceberg': False,
|
|
'leverage': False,
|
|
'marketBuyByCost': True,
|
|
'marketBuyRequiresPrice': True,
|
|
},
|
|
'createOrders': {
|
|
'max': 10,
|
|
},
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 100,
|
|
'daysBack': None,
|
|
'untilDays': 1,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': True,
|
|
'limit': 100,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': {
|
|
'marginMode': False,
|
|
'limit': 100,
|
|
'daysBack': None,
|
|
'untilDays': 1,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchClosedOrders': {
|
|
'marginMode': False,
|
|
'limit': 100,
|
|
'daysBack': None,
|
|
'daysBackCanceled': None,
|
|
'untilDays': 1,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOHLCV': {
|
|
'limit': 300,
|
|
},
|
|
},
|
|
'spot': {
|
|
'extends': 'default',
|
|
'fetchCurrencies': {
|
|
'private': True,
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': {
|
|
'extends': 'default',
|
|
},
|
|
'inverse': {
|
|
'extends': 'default',
|
|
},
|
|
},
|
|
'future': {
|
|
'linear': {
|
|
'extends': 'default',
|
|
},
|
|
'inverse': {
|
|
'extends': 'default',
|
|
},
|
|
},
|
|
},
|
|
# https://exchange-docs.crypto.com/spot/index.html#response-and-reason-codes
|
|
'commonCurrencies': {
|
|
'USD_STABLE_COIN': 'USDC',
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'exceptions': {
|
|
'exact': {
|
|
'219': InvalidOrder,
|
|
'306': InsufficientFunds, # {"id" : 1753xxx, "method" : "private/amend-order", "code" : 306, "message" : "INSUFFICIENT_AVAILABLE_BALANCE", "result" : {"client_oid" : "1753xxx", "order_id" : "6530xxx"}}
|
|
'314': InvalidOrder, # {"id" : 1700xxx, "method" : "private/create-order", "code" : 314, "message" : "EXCEEDS_MAX_ORDER_SIZE", "result" : {"client_oid" : "1700xxx", "order_id" : "6530xxx"}}
|
|
'325': InvalidOrder, # {"id" : 1741xxx, "method" : "private/create-order", "code" : 325, "message" : "EXCEED_DAILY_VOL_LIMIT", "result" : {"client_oid" : "1741xxx", "order_id" : "6530xxx"}}
|
|
'415': InvalidOrder, # {"id" : 1741xxx, "method" : "private/create-order", "code" : 415, "message" : "BELOW_MIN_ORDER_SIZE", "result" : {"client_oid" : "1741xxx", "order_id" : "6530xxx"}}
|
|
'10001': ExchangeError,
|
|
'10002': PermissionDenied,
|
|
'10003': PermissionDenied,
|
|
'10004': BadRequest,
|
|
'10005': PermissionDenied,
|
|
'10006': DDoSProtection,
|
|
'10007': InvalidNonce,
|
|
'10008': BadRequest,
|
|
'10009': BadRequest,
|
|
'20001': BadRequest,
|
|
'20002': InsufficientFunds,
|
|
'20005': AccountNotEnabled, # {"id":"123xxx","method":"private/margin/xxx","code":"20005","message":"ACCOUNT_NOT_FOUND"}
|
|
'30003': BadSymbol,
|
|
'30004': BadRequest,
|
|
'30005': BadRequest,
|
|
'30006': InvalidOrder,
|
|
'30007': InvalidOrder,
|
|
'30008': InvalidOrder,
|
|
'30009': InvalidOrder,
|
|
'30010': BadRequest,
|
|
'30013': InvalidOrder,
|
|
'30014': InvalidOrder,
|
|
'30016': InvalidOrder,
|
|
'30017': InvalidOrder,
|
|
'30023': InvalidOrder,
|
|
'30024': InvalidOrder,
|
|
'30025': InvalidOrder,
|
|
'40001': BadRequest,
|
|
'40002': BadRequest,
|
|
'40003': BadRequest,
|
|
'40004': BadRequest,
|
|
'40005': BadRequest,
|
|
'40006': BadRequest,
|
|
'40007': BadRequest,
|
|
'40101': AuthenticationError,
|
|
'40102': InvalidNonce, # Nonce value differs by more than 60 seconds from server
|
|
'40103': AuthenticationError, # IP address not whitelisted
|
|
'40104': AuthenticationError, # Disallowed based on user tier
|
|
'40107': BadRequest, # Session subscription limit has been exceeded
|
|
'40401': OrderNotFound,
|
|
'40801': RequestTimeout,
|
|
'42901': RateLimitExceeded,
|
|
'43005': InvalidOrder, # Rejected POST_ONLY create-order request(normally happened when exec_inst contains POST_ONLY but time_in_force is NOT GOOD_TILL_CANCEL)
|
|
'43003': InvalidOrder, # FOK order has not been filled and cancelled
|
|
'43004': InvalidOrder, # IOC order has not been filled and cancelled
|
|
'43012': BadRequest, # Canceled due to Self Trade Prevention
|
|
'50001': ExchangeError,
|
|
'9010001': OnMaintenance, # {"code":9010001,"message":"SYSTEM_MAINTENANCE","details":"Crypto.com Exchange is currently under maintenance. Please refer to https://status.crypto.com for more details."}
|
|
},
|
|
'broad': {},
|
|
},
|
|
})
|
|
|
|
def fetch_currencies(self, params={}) -> Currencies:
|
|
"""
|
|
fetches all available currencies on an exchange
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-currency-networks
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an associative dictionary of currencies
|
|
"""
|
|
# self endpoint requires authentication
|
|
if not self.check_required_credentials(False):
|
|
return {}
|
|
skipFetchCurrencies = False
|
|
skipFetchCurrencies, params = self.handle_option_and_params(params, 'fetchCurrencies', 'skipFetchCurrencies', False)
|
|
if skipFetchCurrencies:
|
|
# sub-accounts can't access self endpoint
|
|
return {}
|
|
response = {}
|
|
try:
|
|
response = self.v1PrivatePostPrivateGetCurrencyNetworks(params)
|
|
except Exception as e:
|
|
if isinstance(e, ExchangeError):
|
|
# sub-accounts can't access self endpoint
|
|
# {"code":"10001","msg":"SYS_ERROR"}
|
|
return {}
|
|
raise e
|
|
# do nothing
|
|
# sub-accounts can't access self endpoint
|
|
#
|
|
# {
|
|
# "id": "1747502328559",
|
|
# "method": "private/get-currency-networks",
|
|
# "code": "0",
|
|
# "result": {
|
|
# "update_time": "1747502281000",
|
|
# "currency_map": {
|
|
# "USDT": {
|
|
# "full_name": "Tether USD",
|
|
# "default_network": "ETH",
|
|
# "network_list": [
|
|
# {
|
|
# "network_id": "ETH",
|
|
# "withdrawal_fee": "10.00000000",
|
|
# "withdraw_enabled": True,
|
|
# "min_withdrawal_amount": "20.0",
|
|
# "deposit_enabled": True,
|
|
# "confirmation_required": "32"
|
|
# },
|
|
# {
|
|
# "network_id": "CRONOS",
|
|
# "withdrawal_fee": "0.18000000",
|
|
# "withdraw_enabled": True,
|
|
# "min_withdrawal_amount": "0.35",
|
|
# "deposit_enabled": True,
|
|
# "confirmation_required": "15"
|
|
# },
|
|
# {
|
|
# "network_id": "SOL",
|
|
# "withdrawal_fee": "5.31000000",
|
|
# "withdraw_enabled": True,
|
|
# "min_withdrawal_amount": "10.62",
|
|
# "deposit_enabled": True,
|
|
# "confirmation_required": "1"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
resultData = self.safe_dict(response, 'result', {})
|
|
currencyMap = self.safe_dict(resultData, 'currency_map', {})
|
|
keys = list(currencyMap.keys())
|
|
result: dict = {}
|
|
for i in range(0, len(keys)):
|
|
key = keys[i]
|
|
currency = currencyMap[key]
|
|
id = key
|
|
code = self.safe_currency_code(id)
|
|
networks: dict = {}
|
|
chains = self.safe_list(currency, 'network_list', [])
|
|
for j in range(0, len(chains)):
|
|
chain = chains[j]
|
|
networkId = self.safe_string(chain, 'network_id')
|
|
network = self.network_id_to_code(networkId)
|
|
networks[network] = {
|
|
'info': chain,
|
|
'id': networkId,
|
|
'network': network,
|
|
'active': None,
|
|
'deposit': self.safe_bool(chain, 'deposit_enabled', False),
|
|
'withdraw': self.safe_bool(chain, 'withdraw_enabled', False),
|
|
'fee': self.safe_number(chain, 'withdrawal_fee'),
|
|
'precision': None,
|
|
'limits': {
|
|
'withdraw': {
|
|
'min': self.safe_number(chain, 'min_withdrawal_amount'),
|
|
'max': None,
|
|
},
|
|
},
|
|
}
|
|
result[code] = self.safe_currency_structure({
|
|
'info': currency,
|
|
'id': id,
|
|
'code': code,
|
|
'name': self.safe_string(currency, 'full_name'),
|
|
'active': None,
|
|
'deposit': None,
|
|
'withdraw': None,
|
|
'fee': None,
|
|
'precision': None,
|
|
'limits': {
|
|
'amount': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'type': 'crypto', # only crypto now
|
|
'networks': networks,
|
|
})
|
|
return result
|
|
|
|
def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-instruments
|
|
|
|
retrieves data on all markets for cryptocom
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = self.v1PublicGetPublicGetInstruments(params)
|
|
#
|
|
# {
|
|
# "id": 1,
|
|
# "method": "public/get-instruments",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTC_USDT",
|
|
# "inst_type": "CCY_PAIR",
|
|
# "display_name": "BTC/USDT",
|
|
# "base_ccy": "BTC",
|
|
# "quote_ccy": "USDT",
|
|
# "quote_decimals": 2,
|
|
# "quantity_decimals": 5,
|
|
# "price_tick_size": "0.01",
|
|
# "qty_tick_size": "0.00001",
|
|
# "max_leverage": "50",
|
|
# "tradable": True,
|
|
# "expiry_timestamp_ms": 0,
|
|
# "beta_product": False,
|
|
# "margin_buy_enabled": False,
|
|
# "margin_sell_enabled": True
|
|
# },
|
|
# {
|
|
# "symbol": "RUNEUSD-PERP",
|
|
# "inst_type": "PERPETUAL_SWAP",
|
|
# "display_name": "RUNEUSD Perpetual",
|
|
# "base_ccy": "RUNE",
|
|
# "quote_ccy": "USD",
|
|
# "quote_decimals": 3,
|
|
# "quantity_decimals": 1,
|
|
# "price_tick_size": "0.001",
|
|
# "qty_tick_size": "0.1",
|
|
# "max_leverage": "50",
|
|
# "tradable": True,
|
|
# "expiry_timestamp_ms": 0,
|
|
# "beta_product": False,
|
|
# "underlying_symbol": "RUNEUSD-INDEX",
|
|
# "contract_size": "1",
|
|
# "margin_buy_enabled": False,
|
|
# "margin_sell_enabled": False
|
|
# },
|
|
# {
|
|
# "symbol": "ETHUSD-230825",
|
|
# "inst_type": "FUTURE",
|
|
# "display_name": "ETHUSD Futures 20230825",
|
|
# "base_ccy": "ETH",
|
|
# "quote_ccy": "USD",
|
|
# "quote_decimals": 2,
|
|
# "quantity_decimals": 4,
|
|
# "price_tick_size": "0.01",
|
|
# "qty_tick_size": "0.0001",
|
|
# "max_leverage": "100",
|
|
# "tradable": True,
|
|
# "expiry_timestamp_ms": 1692950400000,
|
|
# "beta_product": False,
|
|
# "underlying_symbol": "ETHUSD-INDEX",
|
|
# "contract_size": "1",
|
|
# "margin_buy_enabled": False,
|
|
# "margin_sell_enabled": False
|
|
# },
|
|
# {
|
|
# "symbol": "BTCUSD-230630-CW30000",
|
|
# "inst_type": "WARRANT",
|
|
# "display_name": "BTCUSD-230630-CW30000",
|
|
# "base_ccy": "BTC",
|
|
# "quote_ccy": "USD",
|
|
# "quote_decimals": 3,
|
|
# "quantity_decimals": 0,
|
|
# "price_tick_size": "0.001",
|
|
# "qty_tick_size": "10",
|
|
# "max_leverage": "50",
|
|
# "tradable": True,
|
|
# "expiry_timestamp_ms": 1688112000000,
|
|
# "beta_product": False,
|
|
# "underlying_symbol": "BTCUSD-INDEX",
|
|
# "put_call": "CALL",
|
|
# "strike": "30000",
|
|
# "contract_size": "0.0001",
|
|
# "margin_buy_enabled": False,
|
|
# "margin_sell_enabled": False
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
resultResponse = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(resultResponse, 'data', [])
|
|
result = []
|
|
for i in range(0, len(data)):
|
|
market = data[i]
|
|
inst_type = self.safe_string(market, 'inst_type')
|
|
spot = inst_type == 'CCY_PAIR'
|
|
swap = inst_type == 'PERPETUAL_SWAP'
|
|
future = inst_type == 'FUTURE'
|
|
option = inst_type == 'WARRANT'
|
|
baseId = self.safe_string(market, 'base_ccy')
|
|
quoteId = self.safe_string(market, 'quote_ccy')
|
|
settleId = None if spot else quoteId
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
settle = None if spot else self.safe_currency_code(settleId)
|
|
optionType = self.safe_string_lower(market, 'put_call')
|
|
strike = self.safe_string(market, 'strike')
|
|
marginBuyEnabled = self.safe_bool(market, 'margin_buy_enabled')
|
|
marginSellEnabled = self.safe_bool(market, 'margin_sell_enabled')
|
|
expiryString = self.omit_zero(self.safe_string(market, 'expiry_timestamp_ms'))
|
|
expiry = int(expiryString) if (expiryString is not None) else None
|
|
symbol = base + '/' + quote
|
|
type = None
|
|
contract = None
|
|
if inst_type == 'CCY_PAIR':
|
|
type = 'spot'
|
|
contract = False
|
|
elif inst_type == 'PERPETUAL_SWAP':
|
|
type = 'swap'
|
|
symbol = symbol + ':' + quote
|
|
contract = True
|
|
elif inst_type == 'FUTURE':
|
|
type = 'future'
|
|
symbol = symbol + ':' + quote + '-' + self.yymmdd(expiry)
|
|
contract = True
|
|
elif inst_type == 'WARRANT':
|
|
type = 'option'
|
|
symbolOptionType = 'C' if (optionType == 'call') else 'P'
|
|
symbol = symbol + ':' + quote + '-' + self.yymmdd(expiry) + '-' + strike + '-' + symbolOptionType
|
|
contract = True
|
|
result.append({
|
|
'id': self.safe_string(market, 'symbol'),
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId,
|
|
'type': type,
|
|
'spot': spot,
|
|
'margin': ((marginBuyEnabled) or (marginSellEnabled)),
|
|
'swap': swap,
|
|
'future': future,
|
|
'option': option,
|
|
'active': self.safe_bool(market, 'tradable'),
|
|
'contract': contract,
|
|
'linear': True if (contract) else None,
|
|
'inverse': False if (contract) else None,
|
|
'contractSize': self.safe_number(market, 'contract_size'),
|
|
'expiry': expiry,
|
|
'expiryDatetime': self.iso8601(expiry),
|
|
'strike': self.parse_number(strike),
|
|
'optionType': optionType,
|
|
'precision': {
|
|
'price': self.parse_number(self.safe_string(market, 'price_tick_size')),
|
|
'amount': self.parse_number(self.safe_string(market, 'qty_tick_size')),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': self.parse_number('1'),
|
|
'max': self.safe_number(market, 'max_leverage'),
|
|
},
|
|
'amount': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': None,
|
|
'info': market,
|
|
})
|
|
return result
|
|
|
|
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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-tickers
|
|
https://exchange-docs.crypto.com/derivatives/index.html#public-get-tickers
|
|
|
|
: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>`
|
|
"""
|
|
self.load_markets()
|
|
market = None
|
|
request: dict = {}
|
|
if symbols is not None:
|
|
symbol = None
|
|
if isinstance(symbols, list):
|
|
symbolsLength = len(symbols)
|
|
if symbolsLength > 1:
|
|
raise BadRequest(self.id + ' fetchTickers() symbols argument cannot contain more than 1 symbol')
|
|
symbol = symbols[0]
|
|
else:
|
|
symbol = symbols
|
|
market = self.market(symbol)
|
|
request['instrument_name'] = market['id']
|
|
response = self.v1PublicGetPublicGetTickers(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": -1,
|
|
# "method": "public/get-tickers",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "i": "AVAXUSD-PERP",
|
|
# "h": "13.209",
|
|
# "l": "12.148",
|
|
# "a": "13.209",
|
|
# "v": "1109.8",
|
|
# "vv": "14017.33",
|
|
# "c": "0.0732",
|
|
# "b": "13.210",
|
|
# "k": "13.230",
|
|
# "oi": "10888.9",
|
|
# "t": 1687402657575
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(result, 'data', [])
|
|
return self.parse_tickers(data, symbols)
|
|
|
|
def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-tickers
|
|
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
: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>`
|
|
"""
|
|
self.load_markets()
|
|
symbol = self.symbol(symbol)
|
|
tickers = self.fetch_tickers([symbol], params)
|
|
return self.safe_value(tickers, symbol)
|
|
|
|
def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple orders made by the user
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-order-history
|
|
|
|
:param str symbol: unified market symbol of the market the orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for, max date range is one day
|
|
:param int [limit]: the maximum number of order structures to retrieve, default 100 max 100
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms for the ending date filter, default is the current time
|
|
: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 Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_dynamic('fetchOrders', symbol, since, limit, params)
|
|
market = None
|
|
request: dict = {}
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['instrument_name'] = market['id']
|
|
if since is not None:
|
|
request['start_time'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, ['until'])
|
|
if until is not None:
|
|
request['end_time'] = until
|
|
response = self.v1PrivatePostPrivateGetOrderHistory(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1686881486183,
|
|
# "method": "private/get-order-history",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "account_id": "ce075bef-1234-4321-bd6g-ff9007252e63",
|
|
# "order_id": "6142909895014042762",
|
|
# "client_oid": "4e918597-1234-4321-8201-a7577e1e1d91",
|
|
# "order_type": "MARKET",
|
|
# "time_in_force": "GOOD_TILL_CANCEL",
|
|
# "side": "SELL",
|
|
# "exec_inst": [],
|
|
# "quantity": "0.00024",
|
|
# "order_value": "5.7054672",
|
|
# "maker_fee_rate": "0",
|
|
# "taker_fee_rate": "0",
|
|
# "avg_price": "25023.97",
|
|
# "trigger_price": "0",
|
|
# "ref_price": "0",
|
|
# "ref_price_type": "NULL_VAL",
|
|
# "cumulative_quantity": "0.00024",
|
|
# "cumulative_value": "6.0057528",
|
|
# "cumulative_fee": "0.001501438200",
|
|
# "status": "FILLED",
|
|
# "update_user_id": "ce075bef-1234-4321-bd6g-ff9007252e63",
|
|
# "order_date": "2023-06-15",
|
|
# "instrument_name": "BTC_USD",
|
|
# "fee_instrument_name": "USD",
|
|
# "create_time": 1686805465891,
|
|
# "create_time_ns": "1686805465891812578",
|
|
# "update_time": 1686805465891
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'result', {})
|
|
orders = self.safe_list(data, 'data', [])
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get a list of the most recent trades for a particular symbol
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-trades
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch, maximum date range is one day
|
|
:param int [limit]: the maximum number of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms for the ending date filter, default is the current time
|
|
: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=public-trades>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchTrades', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_dynamic('fetchTrades', symbol, since, limit, params)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
}
|
|
if since is not None:
|
|
request['start_ts'] = since
|
|
if limit is not None:
|
|
request['count'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, ['until'])
|
|
if until is not None:
|
|
request['end_ts'] = until
|
|
response = self.v1PublicGetPublicGetTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": -1,
|
|
# "method": "public/get-trades",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "s": "sell",
|
|
# "p": "26386.00",
|
|
# "q": "0.00453",
|
|
# "t": 1686944282062,
|
|
# "tn" : 1704476468851524373,
|
|
# "d": "4611686018455979970",
|
|
# "i": "BTC_USD"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
trades = self.safe_list(result, 'data', [])
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-candlestick
|
|
|
|
: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
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms for the ending date filter, default is the current time
|
|
: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 int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate', False)
|
|
if paginate:
|
|
return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 300)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
'timeframe': self.safe_string(self.timeframes, timeframe, timeframe),
|
|
}
|
|
if limit is not None:
|
|
if limit > 300:
|
|
limit = 300
|
|
request['count'] = limit
|
|
now = self.microseconds()
|
|
duration = self.parse_timeframe(timeframe)
|
|
until = self.safe_integer(params, 'until', now)
|
|
params = self.omit(params, ['until'])
|
|
if since is not None:
|
|
request['start_ts'] = since - duration * 1000
|
|
if limit is not None:
|
|
request['end_ts'] = self.sum(since, duration * limit * 1000)
|
|
else:
|
|
request['end_ts'] = until
|
|
else:
|
|
request['end_ts'] = until
|
|
response = self.v1PublicGetPublicGetCandlestick(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": -1,
|
|
# "method": "public/get-candlestick",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "interval": "1m",
|
|
# "data": [
|
|
# {
|
|
# "o": "26949.89",
|
|
# "h": "26957.64",
|
|
# "l": "26948.24",
|
|
# "c": "26950.00",
|
|
# "v": "0.0670",
|
|
# "t": 1687237080000
|
|
# },
|
|
# ],
|
|
# "instrument_name": "BTC_USD"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(result, 'data', [])
|
|
return self.parse_ohlcvs(data, market, timeframe, since, limit)
|
|
|
|
def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-book
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the number of order book entries to return, max 50
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
}
|
|
if limit:
|
|
request['depth'] = min(limit, 50) # max 50
|
|
response = self.v1PublicGetPublicGetBook(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": -1,
|
|
# "method": "public/get-book",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "depth": 3,
|
|
# "data": [
|
|
# {
|
|
# "bids": [["30025.00", "0.00004", "1"], ["30020.15", "0.02498", "1"], ["30020.00", "0.00004", "1"]],
|
|
# "asks": [["30025.01", "0.04090", "1"], ["30025.70", "0.01000", "1"], ["30026.94", "0.02681", "1"]],
|
|
# "t": 1687491287380
|
|
# }
|
|
# ],
|
|
# "instrument_name": "BTC_USD"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(result, 'data', [])
|
|
orderBook = self.safe_value(data, 0)
|
|
timestamp = self.safe_integer(orderBook, 't')
|
|
return self.parse_order_book(orderBook, symbol, timestamp)
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
responseResult = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(responseResult, 'data', [])
|
|
positionBalances = self.safe_value(data[0], 'position_balances', [])
|
|
result: dict = {'info': response}
|
|
for i in range(0, len(positionBalances)):
|
|
balance = positionBalances[i]
|
|
currencyId = self.safe_string(balance, 'instrument_name')
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['total'] = self.safe_string(balance, 'quantity')
|
|
account['used'] = self.safe_string(balance, 'reserved_qty')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-user-balance
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.v1PrivatePostPrivateUserBalance(params)
|
|
#
|
|
# {
|
|
# "id": 1687300499018,
|
|
# "method": "private/user-balance",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "total_available_balance": "5.84684368",
|
|
# "total_margin_balance": "5.84684368",
|
|
# "total_initial_margin": "0",
|
|
# "total_maintenance_margin": "0",
|
|
# "total_position_cost": "0",
|
|
# "total_cash_balance": "6.44412101",
|
|
# "total_collateral_value": "5.846843685",
|
|
# "total_session_unrealized_pnl": "0",
|
|
# "instrument_name": "USD",
|
|
# "total_session_realized_pnl": "0",
|
|
# "position_balances": [
|
|
# {
|
|
# "quantity": "0.0002119875",
|
|
# "reserved_qty": "0",
|
|
# "collateral_weight": "0.9",
|
|
# "collateral_amount": "5.37549592",
|
|
# "market_value": "5.97277325",
|
|
# "max_withdrawal_balance": "0.00021198",
|
|
# "instrument_name": "BTC",
|
|
# "hourly_interest_rate": "0"
|
|
# },
|
|
# ],
|
|
# "total_effective_leverage": "0",
|
|
# "position_limit": "3000000",
|
|
# "used_position_limit": "0",
|
|
# "total_borrow": "0",
|
|
# "margin_score": "0",
|
|
# "is_liquidating": False,
|
|
# "has_risk": False,
|
|
# "terminatable": True
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
return self.parse_balance(response)
|
|
|
|
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-order-detail
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified symbol of the market 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>`
|
|
"""
|
|
self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'order_id': id,
|
|
}
|
|
response = self.v1PrivatePostPrivateGetOrderDetail(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1686872583882,
|
|
# "method": "private/get-order-detail",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "account_id": "ae075bef-1234-4321-bd6g-bb9007252a63",
|
|
# "order_id": "6142909895025252686",
|
|
# "client_oid": "CCXT_c2d2152cc32d40a3ae7fbf",
|
|
# "order_type": "LIMIT",
|
|
# "time_in_force": "GOOD_TILL_CANCEL",
|
|
# "side": "BUY",
|
|
# "exec_inst": [],
|
|
# "quantity": "0.00020",
|
|
# "limit_price": "20000.00",
|
|
# "order_value": "4",
|
|
# "avg_price": "0",
|
|
# "trigger_price": "0",
|
|
# "ref_price": "0",
|
|
# "cumulative_quantity": "0",
|
|
# "cumulative_value": "0",
|
|
# "cumulative_fee": "0",
|
|
# "status": "ACTIVE",
|
|
# "update_user_id": "ae075bef-1234-4321-bd6g-bb9007252a63",
|
|
# "order_date": "2023-06-15",
|
|
# "instrument_name": "BTC_USD",
|
|
# "fee_instrument_name": "BTC",
|
|
# "create_time": 1686870220684,
|
|
# "create_time_ns": "1686870220684239675",
|
|
# "update_time": 1686870220684
|
|
# }
|
|
# }
|
|
#
|
|
order = self.safe_dict(response, 'result', {})
|
|
return self.parse_order(order, market)
|
|
|
|
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
market = self.market(symbol)
|
|
uppercaseType = type.upper()
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
'side': side.upper(),
|
|
'quantity': self.amount_to_precision(symbol, amount),
|
|
}
|
|
if (uppercaseType == 'LIMIT') or (uppercaseType == 'STOP_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
broker = self.safe_string(self.options, 'broker', 'CCXT')
|
|
request['broker_id'] = broker
|
|
marketType = None
|
|
marginMode = None
|
|
marketType, params = self.handle_market_type_and_params('createOrder', market, params)
|
|
marginMode, params = self.custom_handle_margin_mode_and_params('createOrder', params)
|
|
if (marketType == 'margin') or (marginMode is not None):
|
|
request['spot_margin'] = 'MARGIN'
|
|
elif marketType == 'spot':
|
|
request['spot_margin'] = 'SPOT'
|
|
timeInForce = self.safe_string_upper_2(params, 'timeInForce', 'time_in_force')
|
|
if timeInForce is not None:
|
|
if timeInForce == 'GTC':
|
|
request['time_in_force'] = 'GOOD_TILL_CANCEL'
|
|
elif timeInForce == 'IOC':
|
|
request['time_in_force'] = 'IMMEDIATE_OR_CANCEL'
|
|
elif timeInForce == 'FOK':
|
|
request['time_in_force'] = 'FILL_OR_KILL'
|
|
else:
|
|
request['time_in_force'] = timeInForce
|
|
postOnly = self.safe_bool(params, 'postOnly', False)
|
|
if (postOnly) or (timeInForce == 'PO'):
|
|
request['exec_inst'] = ['POST_ONLY']
|
|
request['time_in_force'] = 'GOOD_TILL_CANCEL'
|
|
triggerPrice = self.safe_string_n(params, ['stopPrice', 'triggerPrice', 'ref_price'])
|
|
stopLossPrice = self.safe_number(params, 'stopLossPrice')
|
|
takeProfitPrice = self.safe_number(params, 'takeProfitPrice')
|
|
isTrigger = (triggerPrice is not None)
|
|
isStopLossTrigger = (stopLossPrice is not None)
|
|
isTakeProfitTrigger = (takeProfitPrice is not None)
|
|
if isTrigger:
|
|
request['ref_price'] = self.price_to_precision(symbol, triggerPrice)
|
|
priceString = self.number_to_string(price)
|
|
if (uppercaseType == 'LIMIT') or (uppercaseType == 'STOP_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
|
if side == 'buy':
|
|
if Precise.string_lt(priceString, triggerPrice):
|
|
request['type'] = 'TAKE_PROFIT_LIMIT'
|
|
else:
|
|
request['type'] = 'STOP_LIMIT'
|
|
else:
|
|
if Precise.string_lt(priceString, triggerPrice):
|
|
request['type'] = 'STOP_LIMIT'
|
|
else:
|
|
request['type'] = 'TAKE_PROFIT_LIMIT'
|
|
else:
|
|
if side == 'buy':
|
|
if Precise.string_lt(priceString, triggerPrice):
|
|
request['type'] = 'TAKE_PROFIT'
|
|
else:
|
|
request['type'] = 'STOP_LOSS'
|
|
else:
|
|
if Precise.string_lt(priceString, triggerPrice):
|
|
request['type'] = 'STOP_LOSS'
|
|
else:
|
|
request['type'] = 'TAKE_PROFIT'
|
|
elif isStopLossTrigger:
|
|
if (uppercaseType == 'LIMIT') or (uppercaseType == 'STOP_LIMIT'):
|
|
request['type'] = 'STOP_LIMIT'
|
|
else:
|
|
request['type'] = 'STOP_LOSS'
|
|
request['ref_price'] = self.price_to_precision(symbol, stopLossPrice)
|
|
elif isTakeProfitTrigger:
|
|
if (uppercaseType == 'LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
|
request['type'] = 'TAKE_PROFIT_LIMIT'
|
|
else:
|
|
request['type'] = 'TAKE_PROFIT'
|
|
request['ref_price'] = self.price_to_precision(symbol, takeProfitPrice)
|
|
else:
|
|
request['type'] = uppercaseType
|
|
params = self.omit(params, ['postOnly', 'clientOrderId', 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice'])
|
|
return self.extend(request, params)
|
|
|
|
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-create-order
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market', 'limit', 'stop_loss', 'stop_limit', 'take_profit', 'take_profit_limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much 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.timeInForce]: 'GTC', 'IOC', 'FOK' or 'PO'
|
|
:param str [params.ref_price_type]: 'MARK_PRICE', 'INDEX_PRICE', 'LAST_PRICE' which trigger price type to use, default is MARK_PRICE
|
|
:param float [params.triggerPrice]: price to trigger a trigger order
|
|
:param float [params.stopLossPrice]: price to trigger a stop-loss trigger order
|
|
:param float [params.takeProfitPrice]: price to trigger a take-profit trigger order
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request = self.create_order_request(symbol, type, side, amount, price, params)
|
|
response = self.v1PrivatePostPrivateCreateOrder(request)
|
|
#
|
|
# {
|
|
# "id": 1686804664362,
|
|
# "method": "private/create-order",
|
|
# "code" : 0,
|
|
# "result": {
|
|
# "order_id": "6540219377766741832",
|
|
# "client_oid": "CCXT_d6ef7c3db6c1495aa8b757"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
return self.parse_order(result, market)
|
|
|
|
def create_orders(self, orders: List[OrderRequest], params={}):
|
|
"""
|
|
create a list of trade orders
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-create-order-list-list
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-create-order-list-oco
|
|
|
|
:param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
ordersRequests = []
|
|
for i in range(0, len(orders)):
|
|
rawOrder = orders[i]
|
|
marketId = self.safe_string(rawOrder, 'symbol')
|
|
type = self.safe_string(rawOrder, 'type')
|
|
side = self.safe_string(rawOrder, 'side')
|
|
amount = self.safe_value(rawOrder, 'amount')
|
|
price = self.safe_value(rawOrder, 'price')
|
|
orderParams = self.safe_dict(rawOrder, 'params', {})
|
|
orderRequest = self.create_advanced_order_request(marketId, type, side, amount, price, orderParams)
|
|
ordersRequests.append(orderRequest)
|
|
contigency = self.safe_string(params, 'contingency_type', 'LIST')
|
|
request: dict = {
|
|
'contingency_type': contigency, # or OCO
|
|
'order_list': ordersRequests,
|
|
}
|
|
response = self.v1PrivatePostPrivateCreateOrderList(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 12,
|
|
# "method": "private/create-order-list",
|
|
# "code": 10001,
|
|
# "result": {
|
|
# "result_list": [
|
|
# {
|
|
# "index": 0,
|
|
# "code": 0,
|
|
# "order_id": "2015106383706015873",
|
|
# "client_oid": "my_order_0001"
|
|
# },
|
|
# {
|
|
# "index": 1,
|
|
# "code": 20007,
|
|
# "message": "INVALID_REQUEST",
|
|
# "client_oid": "my_order_0002"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
# {
|
|
# "id" : 1698068111133,
|
|
# "method" : "private/create-order-list",
|
|
# "code" : 0,
|
|
# "result" : [{
|
|
# "code" : 0,
|
|
# "index" : 0,
|
|
# "client_oid" : "1698068111133_0",
|
|
# "order_id" : "6142909896519488206"
|
|
# }, {
|
|
# "code" : 306,
|
|
# "index" : 1,
|
|
# "client_oid" : "1698068111133_1",
|
|
# "message" : "INSUFFICIENT_AVAILABLE_BALANCE",
|
|
# "order_id" : "6142909896519488207"
|
|
# }]
|
|
# }
|
|
#
|
|
result = self.safe_value(response, 'result', [])
|
|
listId = self.safe_string(result, 'list_id')
|
|
if listId is not None:
|
|
ocoOrders = [{'order_id': listId}]
|
|
return self.parse_orders(ocoOrders)
|
|
return self.parse_orders(result)
|
|
|
|
def create_advanced_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
# differs slightly from createOrderRequest
|
|
# since the advanced order endpoint requires a different set of parameters
|
|
# namely here we don't support ref_price or spot_margin
|
|
# and market-buy orders need to send notional instead of quantity
|
|
market = self.market(symbol)
|
|
uppercaseType = type.upper()
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
'side': side.upper(),
|
|
}
|
|
if (uppercaseType == 'LIMIT') or (uppercaseType == 'STOP_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
broker = self.safe_string(self.options, 'broker', 'CCXT')
|
|
request['broker_id'] = broker
|
|
timeInForce = self.safe_string_upper_2(params, 'timeInForce', 'time_in_force')
|
|
if timeInForce is not None:
|
|
if timeInForce == 'GTC':
|
|
request['time_in_force'] = 'GOOD_TILL_CANCEL'
|
|
elif timeInForce == 'IOC':
|
|
request['time_in_force'] = 'IMMEDIATE_OR_CANCEL'
|
|
elif timeInForce == 'FOK':
|
|
request['time_in_force'] = 'FILL_OR_KILL'
|
|
else:
|
|
request['time_in_force'] = timeInForce
|
|
postOnly = self.safe_bool(params, 'postOnly', False)
|
|
if (postOnly) or (timeInForce == 'PO'):
|
|
request['exec_inst'] = ['POST_ONLY']
|
|
request['time_in_force'] = 'GOOD_TILL_CANCEL'
|
|
triggerPrice = self.safe_string_n(params, ['stopPrice', 'triggerPrice', 'ref_price'])
|
|
stopLossPrice = self.safe_number(params, 'stopLossPrice')
|
|
takeProfitPrice = self.safe_number(params, 'takeProfitPrice')
|
|
isTrigger = (triggerPrice is not None)
|
|
isStopLossTrigger = (stopLossPrice is not None)
|
|
isTakeProfitTrigger = (takeProfitPrice is not None)
|
|
if isTrigger:
|
|
priceString = self.number_to_string(price)
|
|
if (uppercaseType == 'LIMIT') or (uppercaseType == 'STOP_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
|
if side == 'buy':
|
|
if Precise.string_lt(priceString, triggerPrice):
|
|
request['type'] = 'TAKE_PROFIT_LIMIT'
|
|
else:
|
|
request['type'] = 'STOP_LIMIT'
|
|
else:
|
|
if Precise.string_lt(priceString, triggerPrice):
|
|
request['type'] = 'STOP_LIMIT'
|
|
else:
|
|
request['type'] = 'TAKE_PROFIT_LIMIT'
|
|
else:
|
|
if side == 'buy':
|
|
if Precise.string_lt(priceString, triggerPrice):
|
|
request['type'] = 'TAKE_PROFIT'
|
|
else:
|
|
request['type'] = 'STOP_LOSS'
|
|
else:
|
|
if Precise.string_lt(priceString, triggerPrice):
|
|
request['type'] = 'STOP_LOSS'
|
|
else:
|
|
request['type'] = 'TAKE_PROFIT'
|
|
elif isStopLossTrigger:
|
|
if (uppercaseType == 'LIMIT') or (uppercaseType == 'STOP_LIMIT'):
|
|
request['type'] = 'STOP_LIMIT'
|
|
else:
|
|
request['type'] = 'STOP_LOSS'
|
|
elif isTakeProfitTrigger:
|
|
if (uppercaseType == 'LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
|
request['type'] = 'TAKE_PROFIT_LIMIT'
|
|
else:
|
|
request['type'] = 'TAKE_PROFIT'
|
|
else:
|
|
request['type'] = uppercaseType
|
|
if (side == 'buy') and ((uppercaseType == 'MARKET') or (uppercaseType == 'STOP_LOSS') or (uppercaseType == 'TAKE_PROFIT')):
|
|
# use createmarketBuy logic here
|
|
quoteAmount = None
|
|
createMarketBuyOrderRequiresPrice = True
|
|
createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
|
|
cost = self.safe_number_2(params, 'cost', 'notional')
|
|
params = self.omit(params, 'cost')
|
|
if cost is not None:
|
|
quoteAmount = self.cost_to_precision(symbol, cost)
|
|
elif createMarketBuyOrderRequiresPrice:
|
|
if price is None:
|
|
raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument')
|
|
else:
|
|
amountString = self.number_to_string(amount)
|
|
priceString = self.number_to_string(price)
|
|
costRequest = Precise.string_mul(amountString, priceString)
|
|
quoteAmount = self.cost_to_precision(symbol, costRequest)
|
|
else:
|
|
quoteAmount = self.cost_to_precision(symbol, amount)
|
|
request['notional'] = quoteAmount
|
|
else:
|
|
request['quantity'] = self.amount_to_precision(symbol, amount)
|
|
params = self.omit(params, ['postOnly', 'clientOrderId', 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice'])
|
|
return self.extend(request, params)
|
|
|
|
def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
|
"""
|
|
edit a trade order
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-amend-order
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified market symbol of the order to edit
|
|
:param str [type]: not used by cryptocom editOrder
|
|
:param str [side]: not used by cryptocom editOrder
|
|
:param float amount:(mandatory) how much of the currency you want to trade in units of the base currency
|
|
:param float price:(mandatory) the price for the order, 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]: the original client order id of the order to edit, required if id is not provided
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request = self.edit_order_request(id, symbol, amount, price, params)
|
|
response = self.v1PrivatePostPrivateAmendOrder(request)
|
|
result = self.safe_dict(response, 'result', {})
|
|
return self.parse_order(result)
|
|
|
|
def edit_order_request(self, id: str, symbol: str, amount: float, price: Num = None, params={}):
|
|
request: dict = {}
|
|
if id is not None:
|
|
request['order_id'] = id
|
|
else:
|
|
originalClientOrderId = self.safe_string_2(params, 'orig_client_oid', 'clientOrderId')
|
|
if originalClientOrderId is None:
|
|
raise ArgumentsRequired(self.id + ' editOrder() requires an id argument or orig_client_oid parameter')
|
|
else:
|
|
request['orig_client_oid'] = originalClientOrderId
|
|
params = self.omit(params, ['orig_client_oid', 'clientOrderId'])
|
|
if (amount is None) or (price is None):
|
|
raise ArgumentsRequired(self.id + ' editOrder() requires both amount and price arguments. If you do not want to change the amount or price, you should pass the original values')
|
|
request['new_quantity'] = self.amount_to_precision(symbol, amount)
|
|
request['new_price'] = self.price_to_precision(symbol, price)
|
|
return self.extend(request, params)
|
|
|
|
def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
cancel all open orders
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-cancel-all-orders
|
|
|
|
:param str symbol: unified market symbol of the orders to cancel
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict} Returns exchange raw message{@link https://docs.ccxt.com/#/?id=order-structure:
|
|
"""
|
|
self.load_markets()
|
|
market = None
|
|
request: dict = {}
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['instrument_name'] = market['id']
|
|
response = self.v1PrivatePostPrivateCancelAllOrders(self.extend(request, params))
|
|
return [self.safe_order({'info': response})]
|
|
|
|
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-cancel-order
|
|
|
|
:param str id: the order id of the order to cancel
|
|
:param str [symbol]: unified symbol of the market 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>`
|
|
"""
|
|
self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'order_id': id,
|
|
}
|
|
response = self.v1PrivatePostPrivateCancelOrder(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1686882846638,
|
|
# "method": "private/cancel-order",
|
|
# "code": 0,
|
|
# "message": "NO_ERROR",
|
|
# "result": {
|
|
# "client_oid": "CCXT_c2d2152cc32d40a3ae7fbf",
|
|
# "order_id": "6142909895025252686"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
return self.parse_order(result, market)
|
|
|
|
def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
|
|
"""
|
|
cancel multiple orders
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-cancel-order-list-list
|
|
|
|
:param str[] ids: order ids
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
orderRequests = []
|
|
for i in range(0, len(ids)):
|
|
id = ids[i]
|
|
order: dict = {
|
|
'instrument_name': market['id'],
|
|
'order_id': str(id),
|
|
}
|
|
orderRequests.append(order)
|
|
request: dict = {
|
|
'contingency_type': 'LIST',
|
|
'order_list': orderRequests,
|
|
}
|
|
response = self.v1PrivatePostPrivateCancelOrderList(self.extend(request, params))
|
|
result = self.safe_list(response, 'result', [])
|
|
return self.parse_orders(result, market, None, None, params)
|
|
|
|
def cancel_orders_for_symbols(self, orders: List[CancellationRequest], params={}):
|
|
"""
|
|
cancel multiple orders for multiple symbols
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-cancel-order-list-list
|
|
|
|
:param CancellationRequest[] orders: each order should contain the parameters required by cancelOrder namely id and symbol, example [{"id": "a", "symbol": "BTC/USDT"}, {"id": "b", "symbol": "ETH/USDT"}]
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
orderRequests = []
|
|
for i in range(0, len(orders)):
|
|
order = orders[i]
|
|
id = self.safe_string(order, 'id')
|
|
symbol = self.safe_string(order, 'symbol')
|
|
market = self.market(symbol)
|
|
orderItem: dict = {
|
|
'instrument_name': market['id'],
|
|
'order_id': str(id),
|
|
}
|
|
orderRequests.append(orderItem)
|
|
request: dict = {
|
|
'contingency_type': 'LIST',
|
|
'order_list': orderRequests,
|
|
}
|
|
response = self.v1PrivatePostPrivateCancelOrderList(self.extend(request, params))
|
|
result = self.safe_list(response, 'result', [])
|
|
return self.parse_orders(result, None, None, None, params)
|
|
|
|
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetch all unfilled currently open orders
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-open-orders
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch open orders for
|
|
:param int [limit]: the maximum number of open order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = None
|
|
request: dict = {}
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['instrument_name'] = market['id']
|
|
response = self.v1PrivatePostPrivateGetOpenOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1686806134961,
|
|
# "method": "private/get-open-orders",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "account_id": "ce075bef-1234-4321-bd6g-ff9007252e63",
|
|
# "order_id": "6530219477767564494",
|
|
# "client_oid": "CCXT_7ce730f0388441df9bc218",
|
|
# "order_type": "LIMIT",
|
|
# "time_in_force": "GOOD_TILL_CANCEL",
|
|
# "side": "BUY",
|
|
# "exec_inst": [],
|
|
# "quantity": "0.00020",
|
|
# "limit_price": "20000.00",
|
|
# "order_value": "4",
|
|
# "avg_price": "0",
|
|
# "trigger_price": "0",
|
|
# "ref_price": "0",
|
|
# "cumulative_quantity": "0",
|
|
# "cumulative_value": "0",
|
|
# "cumulative_fee": "0",
|
|
# "status": "ACTIVE",
|
|
# "update_user_id": "ce075bef-1234-4321-bd6g-gg9007252e63",
|
|
# "order_date": "2023-06-15",
|
|
# "instrument_name": "BTC_USD",
|
|
# "fee_instrument_name": "BTC",
|
|
# "create_time": 1686806053992,
|
|
# "create_time_ns": "1686806053992921880",
|
|
# "update_time": 1686806053993
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'result', {})
|
|
orders = self.safe_list(data, 'data', [])
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-trades
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch trades for, maximum date range is one day
|
|
:param int [limit]: the maximum number of trade structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms for the ending date filter, default is the current time
|
|
: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>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params, 100)
|
|
request: dict = {}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['instrument_name'] = market['id']
|
|
if since is not None:
|
|
request['start_time'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, ['until'])
|
|
if until is not None:
|
|
request['end_time'] = until
|
|
response = self.v1PrivatePostPrivateGetTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1686942003520,
|
|
# "method": "private/get-trades",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "account_id": "ds075abc-1234-4321-bd6g-ff9007252r63",
|
|
# "event_date": "2023-06-16",
|
|
# "journal_type": "TRADING",
|
|
# "side": "BUY",
|
|
# "instrument_name": "BTC_USD",
|
|
# "fees": "-0.0000000525",
|
|
# "trade_id": "6142909898247428343",
|
|
# "trade_match_id": "4611686018455978480",
|
|
# "create_time": 1686941992887,
|
|
# "traded_price": "26347.16",
|
|
# "traded_quantity": "0.00021",
|
|
# "fee_instrument_name": "BTC",
|
|
# "client_oid": "d1c70a60-810e-4c92-b2a0-72b931cb31e0",
|
|
# "taker_side": "TAKER",
|
|
# "order_id": "6142909895036331486",
|
|
# "create_time_ns": "1686941992887207066"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
trades = self.safe_list(result, 'data', [])
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
def parse_address(self, addressString):
|
|
address = None
|
|
tag = None
|
|
rawTag = None
|
|
if addressString.find('?') > 0:
|
|
address, rawTag = addressString.split('?')
|
|
splitted = rawTag.split('=')
|
|
tag = splitted[1]
|
|
else:
|
|
address = addressString
|
|
return [address, tag]
|
|
|
|
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-create-withdrawal
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: the amount to withdraw
|
|
:param str address: the address to withdraw to
|
|
:param str tag:
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
|
self.load_markets()
|
|
currency = self.safe_currency(code) # for instance, USDC is not inferred from markets but it's still available
|
|
request: dict = {
|
|
'currency': currency['id'],
|
|
'amount': amount,
|
|
'address': address,
|
|
}
|
|
if tag is not None:
|
|
request['address_tag'] = tag
|
|
networkCode = None
|
|
networkCode, params = self.handle_network_code_and_params(params)
|
|
networkId = self.network_code_to_id(networkCode)
|
|
if networkId is not None:
|
|
request['network_id'] = networkId
|
|
response = self.v1PrivatePostPrivateCreateWithdrawal(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id":-1,
|
|
# "method":"private/create-withdrawal",
|
|
# "code":0,
|
|
# "result": {
|
|
# "id": 2220,
|
|
# "amount": 1,
|
|
# "fee": 0.0004,
|
|
# "symbol": "BTC",
|
|
# "address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBf",
|
|
# "client_wid": "my_withdrawal_002",
|
|
# "create_time":1607063412000
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result')
|
|
return self.parse_transaction(result, currency)
|
|
|
|
def fetch_deposit_addresses_by_network(self, code: str, params={}) -> List[DepositAddress]:
|
|
"""
|
|
fetch a dictionary of addresses for a currency, indexed by network
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-deposit-address
|
|
|
|
:param str code: unified currency code of the currency for the deposit address
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `address structures <https://docs.ccxt.com/#/?id=address-structure>` indexed by the network
|
|
"""
|
|
self.load_markets()
|
|
currency = self.safe_currency(code)
|
|
request: dict = {
|
|
'currency': currency['id'],
|
|
}
|
|
response = self.v1PrivatePostPrivateGetDepositAddress(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1234555011221,
|
|
# "method": "private/get-deposit-address",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "deposit_address_list": [
|
|
# {
|
|
# "currency": "BTC",
|
|
# "create_time": 1686730755000,
|
|
# "id": "3737377",
|
|
# "address": "3N9afggxTSmJ3H4jaMQuWyEiLBzZdAbK6d",
|
|
# "status":"1",
|
|
# "network": "BTC"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'result', {})
|
|
addresses = self.safe_list(data, 'deposit_address_list', [])
|
|
addressesLength = len(addresses)
|
|
if addressesLength == 0:
|
|
raise ExchangeError(self.id + ' fetchDepositAddressesByNetwork() generating address...')
|
|
result: dict = {}
|
|
for i in range(0, addressesLength):
|
|
value = self.safe_dict(addresses, i)
|
|
addressString = self.safe_string(value, 'address')
|
|
currencyId = self.safe_string(value, 'currency')
|
|
responseCode = self.safe_currency_code(currencyId)
|
|
address, tag = self.parse_address(addressString)
|
|
self.check_address(address)
|
|
networkId = self.safe_string(value, 'network')
|
|
network = self.network_id_to_code(networkId, responseCode)
|
|
result[network] = {
|
|
'info': value,
|
|
'currency': responseCode,
|
|
'network': network,
|
|
'address': address,
|
|
'tag': tag,
|
|
}
|
|
return result
|
|
|
|
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch the deposit address for a currency associated with self account
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-deposit-address
|
|
|
|
:param str code: unified currency code
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
network = self.safe_string_upper(params, 'network')
|
|
params = self.omit(params, ['network'])
|
|
depositAddresses = self.fetch_deposit_addresses_by_network(code, params)
|
|
if network in depositAddresses:
|
|
return depositAddresses[network]
|
|
else:
|
|
keys = list(depositAddresses.keys())
|
|
return depositAddresses[keys[0]]
|
|
|
|
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all deposits made to an account
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-deposit-history
|
|
|
|
: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 int [params.until]: timestamp in ms for the ending date filter, default is the current time
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
self.load_markets()
|
|
currency = None
|
|
request: dict = {}
|
|
if code is not None:
|
|
currency = self.safe_currency(code)
|
|
request['currency'] = currency['id']
|
|
if since is not None:
|
|
# 90 days date range
|
|
request['start_ts'] = since
|
|
if limit is not None:
|
|
request['page_size'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, ['until'])
|
|
if until is not None:
|
|
request['end_ts'] = until
|
|
response = self.v1PrivatePostPrivateGetDepositHistory(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1688701375714,
|
|
# "method": "private/get-deposit-history",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "deposit_list": [
|
|
# {
|
|
# "currency": "BTC",
|
|
# "fee": 0,
|
|
# "create_time": 1688023659000,
|
|
# "id": "6201135",
|
|
# "update_time": 1688178509000,
|
|
# "amount": 0.00114571,
|
|
# "address": "1234fggxTSmJ3H4jaMQuWyEiLBzZdAbK6d",
|
|
# "status": "1",
|
|
# "txid": "f0ae4202b76eb999c301eccdde44dc639bee42d1fdd5974105286ca3393f6065/2"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'result', {})
|
|
depositList = self.safe_list(data, 'deposit_list', [])
|
|
return self.parse_transactions(depositList, currency, since, limit)
|
|
|
|
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all withdrawals made from an account
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-withdrawal-history
|
|
|
|
: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 int [params.until]: timestamp in ms for the ending date filter, default is the current time
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
self.load_markets()
|
|
currency = None
|
|
request: dict = {}
|
|
if code is not None:
|
|
currency = self.safe_currency(code)
|
|
request['currency'] = currency['id']
|
|
if since is not None:
|
|
# 90 days date range
|
|
request['start_ts'] = since
|
|
if limit is not None:
|
|
request['page_size'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, ['until'])
|
|
if until is not None:
|
|
request['end_ts'] = until
|
|
response = self.v1PrivatePostPrivateGetWithdrawalHistory(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1688613879534,
|
|
# "method": "private/get-withdrawal-history",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "withdrawal_list": [
|
|
# {
|
|
# "currency": "BTC",
|
|
# "client_wid": "",
|
|
# "fee": 0.0005,
|
|
# "create_time": 1688613850000,
|
|
# "id": "5275977",
|
|
# "update_time": 1688613850000,
|
|
# "amount": 0.0005,
|
|
# "address": "1234NMEWbiF8ZkwUMxmfzMxi2A1MQ44bMn",
|
|
# "status": "1",
|
|
# "txid": "",
|
|
# "network_id": "BTC"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'result', {})
|
|
withdrawalList = self.safe_list(data, 'withdrawal_list', [])
|
|
return self.parse_transactions(withdrawalList, currency, since, limit)
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# fetchTicker
|
|
#
|
|
# {
|
|
# "i": "BTC_USD",
|
|
# "h": "30821.45",
|
|
# "l": "28685.11",
|
|
# "a": "30446.00",
|
|
# "v": "1767.8734",
|
|
# "vv": "52436726.42",
|
|
# "c": "0.0583",
|
|
# "b": "30442.00",
|
|
# "k": "30447.66",
|
|
# "t": 1687403045415
|
|
# }
|
|
#
|
|
# fetchTickers
|
|
#
|
|
# {
|
|
# "i": "AVAXUSD-PERP",
|
|
# "h": "13.209",
|
|
# "l": "12.148",
|
|
# "a": "13.209",
|
|
# "v": "1109.8",
|
|
# "vv": "14017.33",
|
|
# "c": "0.0732",
|
|
# "b": "13.210",
|
|
# "k": "13.230",
|
|
# "oi": "10888.9",
|
|
# "t": 1687402657575
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(ticker, 't')
|
|
marketId = self.safe_string(ticker, 'i')
|
|
market = self.safe_market(marketId, market, '_')
|
|
quote = self.safe_string(market, 'quote')
|
|
last = self.safe_string(ticker, 'a')
|
|
return self.safe_ticker({
|
|
'symbol': market['symbol'],
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.safe_number(ticker, 'h'),
|
|
'low': self.safe_number(ticker, 'l'),
|
|
'bid': self.safe_number(ticker, 'b'),
|
|
'bidVolume': None,
|
|
'ask': self.safe_number(ticker, 'k'),
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': self.safe_string(ticker, 'c'),
|
|
'average': None,
|
|
'baseVolume': self.safe_string(ticker, 'v'),
|
|
'quoteVolume': self.safe_string(ticker, 'vv') if (quote == 'USD') else None,
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades
|
|
#
|
|
# {
|
|
# "s": "sell",
|
|
# "p": "26386.00",
|
|
# "q": "0.00453",
|
|
# "tn": 1686944282062,
|
|
# "tn": 1704476468851524373,
|
|
# "d": "4611686018455979970",
|
|
# "i": "BTC_USD"
|
|
# }
|
|
#
|
|
# fetchMyTrades
|
|
#
|
|
# {
|
|
# "account_id": "ds075abc-1234-4321-bd6g-ff9007252r63",
|
|
# "event_date": "2023-06-16",
|
|
# "journal_type": "TRADING",
|
|
# "side": "BUY",
|
|
# "instrument_name": "BTC_USD",
|
|
# "fees": "-0.0000000525",
|
|
# "trade_id": "6142909898247428343",
|
|
# "trade_match_id": "4611686018455978480",
|
|
# "create_time": 1686941992887,
|
|
# "traded_price": "26347.16",
|
|
# "traded_quantity": "0.00021",
|
|
# "fee_instrument_name": "BTC",
|
|
# "client_oid": "d1c70a60-1234-4c92-b2a0-72b931cb31e0",
|
|
# "taker_side": "TAKER",
|
|
# "order_id": "6142909895036331486",
|
|
# "create_time_ns": "1686941992887207066"
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer_2(trade, 't', 'create_time')
|
|
marketId = self.safe_string_2(trade, 'i', 'instrument_name')
|
|
market = self.safe_market(marketId, market, '_')
|
|
feeCurrency = self.safe_string(trade, 'fee_instrument_name')
|
|
feeCostString = self.safe_string(trade, 'fees')
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'id': self.safe_string_2(trade, 'd', 'trade_id'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': market['symbol'],
|
|
'order': self.safe_string(trade, 'order_id'),
|
|
'side': self.safe_string_lower_2(trade, 's', 'side'),
|
|
'takerOrMaker': self.safe_string_lower(trade, 'taker_side'),
|
|
'price': self.safe_number_2(trade, 'p', 'traded_price'),
|
|
'amount': self.safe_number_2(trade, 'q', 'traded_quantity'),
|
|
'cost': None,
|
|
'type': None,
|
|
'fee': {
|
|
'currency': self.safe_currency_code(feeCurrency),
|
|
'cost': self.parse_number(Precise.string_neg(feeCostString)),
|
|
},
|
|
}, market)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# {
|
|
# "o": "26949.89",
|
|
# "h": "26957.64",
|
|
# "l": "26948.24",
|
|
# "c": "26950.00",
|
|
# "v": "0.0670",
|
|
# "t": 1687237080000
|
|
# }
|
|
#
|
|
return [
|
|
self.safe_integer(ohlcv, 't'),
|
|
self.safe_number(ohlcv, 'o'),
|
|
self.safe_number(ohlcv, 'h'),
|
|
self.safe_number(ohlcv, 'l'),
|
|
self.safe_number(ohlcv, 'c'),
|
|
self.safe_number(ohlcv, 'v'),
|
|
]
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'ACTIVE': 'open',
|
|
'CANCELED': 'canceled',
|
|
'FILLED': 'closed',
|
|
'REJECTED': 'rejected',
|
|
'EXPIRED': 'expired',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_time_in_force(self, timeInForce: Str):
|
|
timeInForces: dict = {
|
|
'GOOD_TILL_CANCEL': 'GTC',
|
|
'IMMEDIATE_OR_CANCEL': 'IOC',
|
|
'FILL_OR_KILL': 'FOK',
|
|
}
|
|
return self.safe_string(timeInForces, timeInForce, timeInForce)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# createOrder, cancelOrder
|
|
#
|
|
# {
|
|
# "order_id": "6540219377766741832",
|
|
# "client_oid": "CCXT_d6ef7c3db6c1495aa8b757"
|
|
# }
|
|
#
|
|
# fetchOpenOrders, fetchOrder, fetchOrders
|
|
#
|
|
# {
|
|
# "account_id": "ce075bef-1234-4321-bd6g-ff9007252e63",
|
|
# "order_id": "6530219477767564494",
|
|
# "client_oid": "CCXT_7ce730f0388441df9bc218",
|
|
# "order_type": "LIMIT",
|
|
# "time_in_force": "GOOD_TILL_CANCEL",
|
|
# "side": "BUY",
|
|
# "exec_inst": [],
|
|
# "quantity": "0.00020",
|
|
# "limit_price": "20000.00",
|
|
# "order_value": "4",
|
|
# "avg_price": "0",
|
|
# "trigger_price": "0",
|
|
# "ref_price": "0",
|
|
# "cumulative_quantity": "0",
|
|
# "cumulative_value": "0",
|
|
# "cumulative_fee": "0",
|
|
# "status": "ACTIVE",
|
|
# "update_user_id": "ce075bef-1234-4321-bd6g-gg9007252e63",
|
|
# "order_date": "2023-06-15",
|
|
# "instrument_name": "BTC_USD",
|
|
# "fee_instrument_name": "BTC",
|
|
# "create_time": 1686806053992,
|
|
# "create_time_ns": "1686806053992921880",
|
|
# "update_time": 1686806053993
|
|
# }
|
|
#
|
|
# createOrders
|
|
# {
|
|
# "code" : 306,
|
|
# "index" : 1,
|
|
# "client_oid" : "1698068111133_1",
|
|
# "message" : "INSUFFICIENT_AVAILABLE_BALANCE",
|
|
# "order_id" : "6142909896519488207"
|
|
# }
|
|
#
|
|
code = self.safe_integer(order, 'code')
|
|
if (code is not None) and (code != 0):
|
|
return self.safe_order({
|
|
'id': self.safe_string(order, 'order_id'),
|
|
'clientOrderId': self.safe_string(order, 'client_oid'),
|
|
'info': order,
|
|
'status': 'rejected',
|
|
})
|
|
created = self.safe_integer(order, 'create_time')
|
|
marketId = self.safe_string(order, 'instrument_name')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
execInst = self.safe_value(order, 'exec_inst')
|
|
postOnly = None
|
|
if execInst is not None:
|
|
postOnly = False
|
|
for i in range(0, len(execInst)):
|
|
inst = execInst[i]
|
|
if inst == 'POST_ONLY':
|
|
postOnly = True
|
|
break
|
|
feeCurrency = self.safe_string(order, 'fee_instrument_name')
|
|
return self.safe_order({
|
|
'info': order,
|
|
'id': self.safe_string(order, 'order_id'),
|
|
'clientOrderId': self.safe_string(order, 'client_oid'),
|
|
'timestamp': created,
|
|
'datetime': self.iso8601(created),
|
|
'lastTradeTimestamp': self.safe_integer(order, 'update_time'),
|
|
'status': self.parse_order_status(self.safe_string(order, 'status')),
|
|
'symbol': symbol,
|
|
'type': self.safe_string_lower(order, 'order_type'),
|
|
'timeInForce': self.parse_time_in_force(self.safe_string(order, 'time_in_force')),
|
|
'postOnly': postOnly,
|
|
'side': self.safe_string_lower(order, 'side'),
|
|
'price': self.safe_number(order, 'limit_price'),
|
|
'amount': self.safe_number(order, 'quantity'),
|
|
'filled': self.safe_number(order, 'cumulative_quantity'),
|
|
'remaining': None,
|
|
'average': self.safe_number(order, 'avg_price'),
|
|
'cost': self.safe_number(order, 'cumulative_value'),
|
|
'fee': {
|
|
'currency': self.safe_currency_code(feeCurrency),
|
|
'cost': self.safe_number(order, 'cumulative_fee'),
|
|
},
|
|
'trades': [],
|
|
}, market)
|
|
|
|
def parse_deposit_status(self, status):
|
|
statuses: dict = {
|
|
'0': 'pending',
|
|
'1': 'ok',
|
|
'2': 'failed',
|
|
'3': 'pending',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_withdrawal_status(self, status):
|
|
statuses: dict = {
|
|
'0': 'pending',
|
|
'1': 'pending',
|
|
'2': 'failed',
|
|
'3': 'pending',
|
|
'4': 'failed',
|
|
'5': 'ok',
|
|
'6': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# fetchDeposits
|
|
#
|
|
# {
|
|
# "currency": "BTC",
|
|
# "fee": 0,
|
|
# "create_time": 1688023659000,
|
|
# "id": "6201135",
|
|
# "update_time": 1688178509000,
|
|
# "amount": 0.00114571,
|
|
# "address": "1234fggxTSmJ3H4jaMQuWyEiLBzZdAbK6d",
|
|
# "status": "1",
|
|
# "txid": "f0ae4202b76eb999c301eccdde44dc639bee42d1fdd5974105286ca3393f6065/2"
|
|
# }
|
|
#
|
|
# fetchWithdrawals
|
|
#
|
|
# {
|
|
# "currency": "BTC",
|
|
# "client_wid": "",
|
|
# "fee": 0.0005,
|
|
# "create_time": 1688613850000,
|
|
# "id": "5775977",
|
|
# "update_time": 1688613850000,
|
|
# "amount": 0.0005,
|
|
# "address": "1234NMEWbiF8ZkwUMxmfzMxi2A1MQ44bMn",
|
|
# "status": "1",
|
|
# "txid": "",
|
|
# "network_id": "BTC"
|
|
# }
|
|
#
|
|
# withdraw
|
|
#
|
|
# {
|
|
# "id": 2220,
|
|
# "amount": 1,
|
|
# "fee": 0.0004,
|
|
# "symbol": "BTC",
|
|
# "address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBf",
|
|
# "client_wid": "my_withdrawal_002",
|
|
# "create_time":1607063412000
|
|
# }
|
|
#
|
|
type = None
|
|
rawStatus = self.safe_string(transaction, 'status')
|
|
status = None
|
|
if 'client_wid' in transaction:
|
|
type = 'withdrawal'
|
|
status = self.parse_withdrawal_status(rawStatus)
|
|
else:
|
|
type = 'deposit'
|
|
status = self.parse_deposit_status(rawStatus)
|
|
addressString = self.safe_string(transaction, 'address')
|
|
address, tag = self.parse_address(addressString)
|
|
currencyId = self.safe_string(transaction, 'currency')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
timestamp = self.safe_integer(transaction, 'create_time')
|
|
feeCost = self.safe_number(transaction, 'fee')
|
|
fee = None
|
|
if feeCost is not None:
|
|
fee = {'currency': code, 'cost': feeCost}
|
|
return {
|
|
'info': transaction,
|
|
'id': self.safe_string(transaction, 'id'),
|
|
'txid': self.safe_string(transaction, 'txid'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'network': None,
|
|
'address': address,
|
|
'addressTo': address,
|
|
'addressFrom': None,
|
|
'tag': tag,
|
|
'tagTo': tag,
|
|
'tagFrom': None,
|
|
'type': type,
|
|
'amount': self.safe_number(transaction, 'amount'),
|
|
'currency': code,
|
|
'status': status,
|
|
'updated': self.safe_integer(transaction, 'update_time'),
|
|
'internal': None,
|
|
'comment': self.safe_string(transaction, 'client_wid'),
|
|
'fee': fee,
|
|
}
|
|
|
|
def custom_handle_margin_mode_and_params(self, methodName, params={}):
|
|
"""
|
|
@ignore
|
|
marginMode specified by params["marginMode"], self.options["marginMode"], self.options["defaultMarginMode"], params["margin"] = True or self.options["defaultType"] = 'margin'
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Array: the marginMode in lowercase
|
|
"""
|
|
defaultType = self.safe_string(self.options, 'defaultType')
|
|
isMargin = self.safe_bool(params, 'margin', False)
|
|
params = self.omit(params, 'margin')
|
|
marginMode = None
|
|
marginMode, params = self.handle_margin_mode_and_params(methodName, params)
|
|
if marginMode is not None:
|
|
if marginMode != 'cross':
|
|
raise NotSupported(self.id + ' only cross margin is supported')
|
|
else:
|
|
if (defaultType == 'margin') or (isMargin is True):
|
|
marginMode = 'cross'
|
|
return [marginMode, params]
|
|
|
|
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
|
|
#
|
|
# {
|
|
# "full_name": "Alchemix",
|
|
# "default_network": "ETH",
|
|
# "network_list": [
|
|
# {
|
|
# "network_id": "ETH",
|
|
# "withdrawal_fee": "0.25000000",
|
|
# "withdraw_enabled": True,
|
|
# "min_withdrawal_amount": "0.5",
|
|
# "deposit_enabled": True,
|
|
# "confirmation_required": "0"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
networkList = self.safe_list(fee, 'network_list', [])
|
|
networkListLength = len(networkList)
|
|
result: dict = {
|
|
'info': fee,
|
|
'withdraw': {
|
|
'fee': None,
|
|
'percentage': None,
|
|
},
|
|
'deposit': {
|
|
'fee': None,
|
|
'percentage': None,
|
|
},
|
|
'networks': {},
|
|
}
|
|
if networkList is not None:
|
|
for i in range(0, networkListLength):
|
|
networkInfo = networkList[i]
|
|
networkId = self.safe_string(networkInfo, 'network_id')
|
|
currencyCode = self.safe_string(currency, 'code')
|
|
networkCode = self.network_id_to_code(networkId, currencyCode)
|
|
result['networks'][networkCode] = {
|
|
'deposit': {'fee': None, 'percentage': None},
|
|
'withdraw': {'fee': self.safe_number(networkInfo, 'withdrawal_fee'), 'percentage': False},
|
|
}
|
|
if networkListLength == 1:
|
|
result['withdraw']['fee'] = self.safe_number(networkInfo, 'withdrawal_fee')
|
|
result['withdraw']['percentage'] = False
|
|
return result
|
|
|
|
def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
|
"""
|
|
fetch deposit and withdraw fees
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-currency-networks
|
|
|
|
:param str[]|None codes: list of unified currency codes
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a list of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.v1PrivatePostPrivateGetCurrencyNetworks(params)
|
|
data = self.safe_value(response, 'result')
|
|
currencyMap = self.safe_list(data, 'currency_map')
|
|
return self.parse_deposit_withdraw_fees(currencyMap, codes, 'full_name')
|
|
|
|
def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
|
|
"""
|
|
fetch the history of changes, actions done by the user or operations that altered the balance of the user
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-transactions
|
|
|
|
:param str [code]: unified currency code
|
|
:param int [since]: timestamp in ms of the earliest ledger entry
|
|
:param int [limit]: max number of ledger entries to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms for the ending date filter, default is the current time
|
|
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.safe_currency(code)
|
|
if since is not None:
|
|
request['start_time'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, ['until'])
|
|
if until is not None:
|
|
request['end_time'] = until
|
|
response = self.v1PrivatePostPrivateGetTransactions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1686813195698,
|
|
# "method": "private/get-transactions",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "account_id": "ce075cef-1234-4321-bd6e-gf9007351e64",
|
|
# "event_date": "2023-06-15",
|
|
# "journal_type": "TRADING",
|
|
# "journal_id": "6530219460124075091",
|
|
# "transaction_qty": "6.0091224",
|
|
# "transaction_cost": "6.0091224",
|
|
# "realized_pnl": "0",
|
|
# "order_id": "6530219477766741833",
|
|
# "trade_id": "6530219495775954765",
|
|
# "trade_match_id": "4611686018455865176",
|
|
# "event_timestamp_ms": 1686804665013,
|
|
# "event_timestamp_ns": "1686804665013642422",
|
|
# "client_oid": "CCXT_d6ea7c5db6c1495aa8b758",
|
|
# "taker_side": "",
|
|
# "side": "BUY",
|
|
# "instrument_name": "USD"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
ledger = self.safe_list(result, 'data', [])
|
|
return self.parse_ledger(ledger, currency, since, limit)
|
|
|
|
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
|
|
#
|
|
# {
|
|
# "account_id": "ce075cef-1234-4321-bd6e-gf9007351e64",
|
|
# "event_date": "2023-06-15",
|
|
# "journal_type": "TRADING",
|
|
# "journal_id": "6530219460124075091",
|
|
# "transaction_qty": "6.0091224",
|
|
# "transaction_cost": "6.0091224",
|
|
# "realized_pnl": "0",
|
|
# "order_id": "6530219477766741833",
|
|
# "trade_id": "6530219495775954765",
|
|
# "trade_match_id": "4611686018455865176",
|
|
# "event_timestamp_ms": 1686804665013,
|
|
# "event_timestamp_ns": "1686804665013642422",
|
|
# "client_oid": "CCXT_d6ea7c5db6c1495aa8b758",
|
|
# "taker_side": "",
|
|
# "side": "BUY",
|
|
# "instrument_name": "USD"
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(item, 'event_timestamp_ms')
|
|
currencyId = self.safe_string(item, 'instrument_name')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
currency = self.safe_currency(currencyId, currency)
|
|
amount = self.safe_string(item, 'transaction_qty')
|
|
direction = None
|
|
if Precise.string_lt(amount, '0'):
|
|
direction = 'out'
|
|
amount = Precise.string_abs(amount)
|
|
else:
|
|
direction = 'in'
|
|
return self.safe_ledger_entry({
|
|
'info': item,
|
|
'id': self.safe_string(item, 'order_id'),
|
|
'direction': direction,
|
|
'account': self.safe_string(item, 'account_id'),
|
|
'referenceId': self.safe_string(item, 'trade_id'),
|
|
'referenceAccount': self.safe_string(item, 'trade_match_id'),
|
|
'type': self.parse_ledger_entry_type(self.safe_string(item, 'journal_type')),
|
|
'currency': code,
|
|
'amount': self.parse_number(amount),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'before': None,
|
|
'after': None,
|
|
'status': None,
|
|
'fee': {
|
|
'currency': None,
|
|
'cost': None,
|
|
},
|
|
}, currency)
|
|
|
|
def parse_ledger_entry_type(self, type):
|
|
ledgerType: dict = {
|
|
'TRADING': 'trade',
|
|
'TRADE_FEE': 'fee',
|
|
'WITHDRAW_FEE': 'fee',
|
|
'WITHDRAW': 'withdrawal',
|
|
'DEPOSIT': 'deposit',
|
|
'ROLLBACK_WITHDRAW': 'rollback',
|
|
'ROLLBACK_DEPOSIT': 'rollback',
|
|
'FUNDING': 'fee',
|
|
'REALIZED_PNL': 'trade',
|
|
'INSURANCE_FUND': 'insurance',
|
|
'SOCIALIZED_LOSS': 'trade',
|
|
'LIQUIDATION_FEE': 'fee',
|
|
'SESSION_RESET': 'reset',
|
|
'ADJUSTMENT': 'adjustment',
|
|
'SESSION_SETTLE': 'settlement',
|
|
'UNCOVERED_LOSS': 'trade',
|
|
'ADMIN_ADJUSTMENT': 'adjustment',
|
|
'DELIST': 'delist',
|
|
'SETTLEMENT_FEE': 'fee',
|
|
'AUTO_CONVERSION': 'conversion',
|
|
'MANUAL_CONVERSION': 'conversion',
|
|
}
|
|
return self.safe_string(ledgerType, type, type)
|
|
|
|
def fetch_accounts(self, params={}) -> List[Account]:
|
|
"""
|
|
fetch all the accounts associated with a profile
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-accounts
|
|
|
|
: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
|
|
"""
|
|
self.load_markets()
|
|
response = self.v1PrivatePostPrivateGetAccounts(params)
|
|
#
|
|
# {
|
|
# "id": 1234567894321,
|
|
# "method": "private/get-accounts",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "master_account": {
|
|
# "uuid": "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
|
|
# "user_uuid": "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
|
|
# "enabled": True,
|
|
# "tradable": True,
|
|
# "name": "YOUR_NAME",
|
|
# "country_code": "CAN",
|
|
# "phone_country_code": "CAN",
|
|
# "incorp_country_code": "",
|
|
# "margin_access": "DEFAULT",
|
|
# "derivatives_access": "DEFAULT",
|
|
# "create_time": 1656445188000,
|
|
# "update_time": 1660794567262,
|
|
# "two_fa_enabled": True,
|
|
# "kyc_level": "ADVANCED",
|
|
# "suspended": False,
|
|
# "terminated": False,
|
|
# "spot_enabled": False,
|
|
# "margin_enabled": False,
|
|
# "derivatives_enabled": False
|
|
# },
|
|
# "sub_account_list": []
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
masterAccount = self.safe_dict(result, 'master_account', {})
|
|
accounts = self.safe_list(result, 'sub_account_list', [])
|
|
accounts.append(masterAccount)
|
|
return self.parse_accounts(accounts, params)
|
|
|
|
def parse_account(self, account):
|
|
#
|
|
# {
|
|
# "uuid": "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
|
|
# "user_uuid": "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
|
|
# "master_account_uuid": "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
|
|
# "label": "FORMER_MASTER_MARGIN",
|
|
# "enabled": True,
|
|
# "tradable": True,
|
|
# "name": "YOUR_NAME",
|
|
# "country_code": "YOUR_COUNTRY_CODE",
|
|
# "incorp_country_code": "",
|
|
# "margin_access": "DEFAULT",
|
|
# "derivatives_access": "DEFAULT",
|
|
# "create_time": 1656481992000,
|
|
# "update_time": 1667272884594,
|
|
# "two_fa_enabled": False,
|
|
# "kyc_level": "ADVANCED",
|
|
# "suspended": False,
|
|
# "terminated": False,
|
|
# "spot_enabled": False,
|
|
# "margin_enabled": False,
|
|
# "derivatives_enabled": False,
|
|
# "system_label": "FORMER_MASTER_MARGIN"
|
|
# }
|
|
#
|
|
return {
|
|
'id': self.safe_string(account, 'uuid'),
|
|
'type': self.safe_string(account, 'label'),
|
|
'code': None,
|
|
'info': account,
|
|
}
|
|
|
|
def fetch_settlement_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches historical settlement records
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-expired-settlement-price
|
|
|
|
:param str symbol: unified market symbol of the settlement history
|
|
:param int [since]: timestamp in ms
|
|
:param int [limit]: number of records
|
|
:param dict [params]: exchange specific params
|
|
:param int [params.type]: 'future', 'option'
|
|
:returns dict[]: a list of `settlement history objects <https://docs.ccxt.com/#/?id=settlement-history-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('fetchSettlementHistory', market, params)
|
|
self.check_required_argument('fetchSettlementHistory', type, 'type', ['future', 'option', 'WARRANT', 'FUTURE'])
|
|
if type == 'option':
|
|
type = 'WARRANT'
|
|
request: dict = {
|
|
'instrument_type': type.upper(),
|
|
}
|
|
response = self.v1PublicGetPublicGetExpiredSettlementPrice(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": -1,
|
|
# "method": "public/get-expired-settlement-price",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "i": "BTCUSD-230526",
|
|
# "x": 1685088000000,
|
|
# "v": "26464.1",
|
|
# "t": 1685087999500
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(result, 'data', [])
|
|
settlements = self.parse_settlements(data, market)
|
|
sorted = self.sort_by(settlements, 'timestamp')
|
|
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
|
|
|
|
def parse_settlement(self, settlement, market):
|
|
#
|
|
# {
|
|
# "i": "BTCUSD-230526",
|
|
# "x": 1685088000000,
|
|
# "v": "26464.1",
|
|
# "t": 1685087999500
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(settlement, 'x')
|
|
marketId = self.safe_string(settlement, 'i')
|
|
return {
|
|
'info': settlement,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'price': self.safe_number(settlement, 'v'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
}
|
|
|
|
def parse_settlements(self, settlements, market):
|
|
#
|
|
# [
|
|
# {
|
|
# "i": "BTCUSD-230526",
|
|
# "x": 1685088000000,
|
|
# "v": "26464.1",
|
|
# "t": 1685087999500
|
|
# }
|
|
# ]
|
|
#
|
|
result = []
|
|
for i in range(0, len(settlements)):
|
|
result.append(self.parse_settlement(settlements[i], market))
|
|
return result
|
|
|
|
def fetch_funding_rate(self, symbol: str, params={}):
|
|
"""
|
|
fetches historical funding rates
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-valuations
|
|
|
|
:param str symbol: unified symbol of the market to fetch the funding rate history for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
if not market['swap']:
|
|
raise BadSymbol(self.id + ' fetchFundingRate() supports swap contracts only')
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
'valuation_type': 'estimated_funding_rate',
|
|
'count': 1,
|
|
}
|
|
response = self.v1PublicGetPublicGetValuations(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": -1,
|
|
# "method": "public/get-valuations",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "v": "-0.000001884",
|
|
# "t": 1687892400000
|
|
# },
|
|
# ],
|
|
# "instrument_name": "BTCUSD-PERP"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(result, 'data', [])
|
|
entry = self.safe_dict(data, 0, {})
|
|
return self.parse_funding_rate(entry, market)
|
|
|
|
def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
|
|
#
|
|
# {
|
|
# "v": "-0.000001884",
|
|
# "t": 1687892400000
|
|
# },
|
|
#
|
|
timestamp = self.safe_integer(contract, 't')
|
|
fundingTimestamp = None
|
|
if timestamp is not None:
|
|
fundingTimestamp = int(math.ceil(timestamp / 3600000)) * 3600000 # end of the next hour
|
|
return {
|
|
'info': contract,
|
|
'symbol': self.safe_symbol(None, market),
|
|
'markPrice': None,
|
|
'indexPrice': None,
|
|
'interestRate': None,
|
|
'estimatedSettlePrice': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'fundingRate': self.safe_number(contract, 'v'),
|
|
'fundingTimestamp': fundingTimestamp,
|
|
'fundingDatetime': self.iso8601(fundingTimestamp),
|
|
'nextFundingRate': None,
|
|
'nextFundingTimestamp': None,
|
|
'nextFundingDatetime': None,
|
|
'previousFundingRate': None,
|
|
'previousFundingTimestamp': None,
|
|
'previousFundingDatetime': None,
|
|
'interval': '1h',
|
|
}
|
|
|
|
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches historical funding rates
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-valuations
|
|
|
|
: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] to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms for the ending date filter, default is the current time
|
|
: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')
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params)
|
|
market = self.market(symbol)
|
|
if not market['swap']:
|
|
raise BadSymbol(self.id + ' fetchFundingRateHistory() supports swap contracts only')
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
'valuation_type': 'funding_hist',
|
|
}
|
|
if since is not None:
|
|
request['start_ts'] = since
|
|
if limit is not None:
|
|
request['count'] = limit
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, ['until'])
|
|
if until is not None:
|
|
request['end_ts'] = until
|
|
response = self.v1PublicGetPublicGetValuations(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": -1,
|
|
# "method": "public/get-valuations",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "v": "-0.000001884",
|
|
# "t": 1687892400000
|
|
# },
|
|
# ],
|
|
# "instrument_name": "BTCUSD-PERP"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(result, 'data', [])
|
|
marketId = self.safe_string(result, 'instrument_name')
|
|
rates = []
|
|
for i in range(0, len(data)):
|
|
entry = data[i]
|
|
timestamp = self.safe_integer(entry, 't')
|
|
rates.append({
|
|
'info': entry,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'fundingRate': self.safe_number(entry, 'v'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
})
|
|
sorted = self.sort_by(rates, 'timestamp')
|
|
return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)
|
|
|
|
def fetch_position(self, symbol: str, params={}):
|
|
"""
|
|
fetch data on a single open contract trade position
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-positions
|
|
|
|
: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>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
}
|
|
response = self.v1PrivatePostPrivateGetPositions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1688015952050,
|
|
# "method": "private/get-positions",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "account_id": "ce075bef-b600-4277-bd6e-ff9007251e63",
|
|
# "quantity": "0.0001",
|
|
# "cost": "3.02392",
|
|
# "open_pos_cost": "3.02392",
|
|
# "open_position_pnl": "-0.0010281328",
|
|
# "session_pnl": "-0.0010281328",
|
|
# "update_timestamp_ms": 1688015919091,
|
|
# "instrument_name": "BTCUSD-PERP",
|
|
# "type": "PERPETUAL_SWAP"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
data = self.safe_list(result, 'data', [])
|
|
return self.parse_position(self.safe_dict(data, 0), market)
|
|
|
|
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
fetch all open positions
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-positions
|
|
|
|
:param str[]|None 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>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
request: dict = {}
|
|
market = None
|
|
if symbols is not None:
|
|
symbol = None
|
|
if isinstance(symbols, list):
|
|
symbolsLength = len(symbols)
|
|
if symbolsLength > 1:
|
|
raise BadRequest(self.id + ' fetchPositions() symbols argument cannot contain more than 1 symbol')
|
|
symbol = symbols[0]
|
|
else:
|
|
symbol = symbols
|
|
market = self.market(symbol)
|
|
request['instrument_name'] = market['id']
|
|
response = self.v1PrivatePostPrivateGetPositions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1688015952050,
|
|
# "method": "private/get-positions",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "data": [
|
|
# {
|
|
# "account_id": "ce075bef-b600-4277-bd6e-ff9007251e63",
|
|
# "quantity": "0.0001",
|
|
# "cost": "3.02392",
|
|
# "open_pos_cost": "3.02392",
|
|
# "open_position_pnl": "-0.0010281328",
|
|
# "session_pnl": "-0.0010281328",
|
|
# "update_timestamp_ms": 1688015919091,
|
|
# "instrument_name": "BTCUSD-PERP",
|
|
# "type": "PERPETUAL_SWAP"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
responseResult = self.safe_dict(response, 'result', {})
|
|
positions = self.safe_list(responseResult, 'data', [])
|
|
result = []
|
|
for i in range(0, len(positions)):
|
|
entry = positions[i]
|
|
marketId = self.safe_string(entry, 'instrument_name')
|
|
marketInner = self.safe_market(marketId, None, None, 'contract')
|
|
result.append(self.parse_position(entry, marketInner))
|
|
return self.filter_by_array_positions(result, 'symbol', None, False)
|
|
|
|
def parse_position(self, position: dict, market: Market = None):
|
|
#
|
|
# {
|
|
# "account_id": "ce075bef-b600-4277-bd6e-ff9007251e63",
|
|
# "quantity": "0.0001",
|
|
# "cost": "3.02392",
|
|
# "open_pos_cost": "3.02392",
|
|
# "open_position_pnl": "-0.0010281328",
|
|
# "session_pnl": "-0.0010281328",
|
|
# "update_timestamp_ms": 1688015919091,
|
|
# "instrument_name": "BTCUSD-PERP",
|
|
# "type": "PERPETUAL_SWAP"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(position, 'instrument_name')
|
|
market = self.safe_market(marketId, market, None, 'contract')
|
|
symbol = self.safe_symbol(marketId, market, None, 'contract')
|
|
timestamp = self.safe_integer(position, 'update_timestamp_ms')
|
|
amount = self.safe_string(position, 'quantity')
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': None,
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'hedged': None,
|
|
'side': 'buy' if Precise.string_gt(amount, '0') else 'sell',
|
|
'contracts': Precise.string_abs(amount),
|
|
'contractSize': market['contractSize'],
|
|
'entryPrice': None,
|
|
'markPrice': None,
|
|
'notional': None,
|
|
'leverage': None,
|
|
'collateral': self.safe_number(position, 'open_pos_cost'),
|
|
'initialMargin': self.safe_number(position, 'cost'),
|
|
'maintenanceMargin': None,
|
|
'initialMarginPercentage': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'unrealizedPnl': self.safe_number(position, 'open_position_pnl'),
|
|
'liquidationPrice': None,
|
|
'marginMode': None,
|
|
'percentage': None,
|
|
'marginRatio': None,
|
|
'stopLossPrice': None,
|
|
'takeProfitPrice': None,
|
|
})
|
|
|
|
def nonce(self):
|
|
return self.milliseconds()
|
|
|
|
def params_to_string(self, object, level):
|
|
maxLevel = 3
|
|
if level >= maxLevel:
|
|
return str(object)
|
|
if isinstance(object, str):
|
|
return object
|
|
returnString = ''
|
|
paramsKeys = None
|
|
if isinstance(object, list):
|
|
paramsKeys = object
|
|
else:
|
|
sorted = self.keysort(object)
|
|
paramsKeys = list(sorted.keys())
|
|
for i in range(0, len(paramsKeys)):
|
|
key = paramsKeys[i]
|
|
returnString += key
|
|
value = object[key]
|
|
if value == 'None':
|
|
returnString += 'None'
|
|
elif isinstance(value, list):
|
|
for j in range(0, len(value)):
|
|
returnString += self.params_to_string(value[j], level + 1)
|
|
else:
|
|
returnString += str(value)
|
|
return returnString
|
|
|
|
def close_position(self, symbol: str, side: OrderSide = None, params={}) -> Order:
|
|
"""
|
|
closes open positions for a market
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-close-position
|
|
|
|
:param str symbol: Unified CCXT market symbol
|
|
:param str [side]: not used by cryptocom.closePositions
|
|
:param dict [params]: extra parameters specific to the okx api endpoint
|
|
|
|
EXCHANGE SPECIFIC PARAMETERS
|
|
:param str [params.type]: LIMIT or MARKET
|
|
:param number [params.price]: for limit orders only
|
|
:returns dict[]: `A list of position structures <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
'type': 'MARKET',
|
|
}
|
|
type = self.safe_string_upper(params, 'type')
|
|
price = self.safe_string(params, 'price')
|
|
if type is not None:
|
|
request['type'] = type
|
|
if price is not None:
|
|
request['price'] = self.price_to_precision(market['symbol'], price)
|
|
response = self.v1PrivatePostPrivateClosePosition(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id" : 1700830813298,
|
|
# "method" : "private/close-position",
|
|
# "code" : 0,
|
|
# "result" : {
|
|
# "client_oid" : "179a909d-5614-655b-0d0e-9e85c9a25c85",
|
|
# "order_id" : "6142909897021751347"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result')
|
|
return self.parse_order(result, market)
|
|
|
|
def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
|
|
"""
|
|
fetch the trading fees for a market
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-instrument-fee-rate
|
|
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'instrument_name': market['id'],
|
|
}
|
|
response = self.v1PrivatePostPrivateGetInstrumentFeeRate(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": 1,
|
|
# "code": 0,
|
|
# "method": "private/staking/unstake",
|
|
# "result": {
|
|
# "staking_id": "1",
|
|
# "instrument_name": "SOL.staked",
|
|
# "status": "NEW",
|
|
# "quantity": "1",
|
|
# "underlying_inst_name": "SOL",
|
|
# "reason": "NO_ERROR"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'result', {})
|
|
return self.parse_trading_fee(data, market)
|
|
|
|
def fetch_trading_fees(self, params={}) -> TradingFees:
|
|
"""
|
|
|
|
https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-fee-rate
|
|
|
|
fetch the trading fees for multiple markets
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
response = self.v1PrivatePostPrivateGetFeeRate(params)
|
|
#
|
|
# {
|
|
# "id": 1,
|
|
# "method": "/private/get-fee-rate",
|
|
# "code": 0,
|
|
# "result": {
|
|
# "spot_tier": "3",
|
|
# "deriv_tier": "3",
|
|
# "effective_spot_maker_rate_bps": "6.5",
|
|
# "effective_spot_taker_rate_bps": "6.9",
|
|
# "effective_deriv_maker_rate_bps": "1.1",
|
|
# "effective_deriv_taker_rate_bps": "3"
|
|
# }
|
|
# }
|
|
#
|
|
result = self.safe_dict(response, 'result', {})
|
|
return self.parse_trading_fees(result)
|
|
|
|
def parse_trading_fees(self, response):
|
|
#
|
|
# {
|
|
# "spot_tier": "3",
|
|
# "deriv_tier": "3",
|
|
# "effective_spot_maker_rate_bps": "6.5",
|
|
# "effective_spot_taker_rate_bps": "6.9",
|
|
# "effective_deriv_maker_rate_bps": "1.1",
|
|
# "effective_deriv_taker_rate_bps": "3"
|
|
# }
|
|
#
|
|
result: dict = {}
|
|
result['info'] = response
|
|
for i in range(0, len(self.symbols)):
|
|
symbol = self.symbols[i]
|
|
market = self.market(symbol)
|
|
isSwap = market['swap']
|
|
takerFeeKey = 'effective_deriv_taker_rate_bps' if isSwap else 'effective_spot_taker_rate_bps'
|
|
makerFeeKey = 'effective_deriv_maker_rate_bps' if isSwap else 'effective_spot_maker_rate_bps'
|
|
tradingFee = {
|
|
'info': response,
|
|
'symbol': symbol,
|
|
'maker': self.parse_number(Precise.string_div(self.safe_string(response, makerFeeKey), '10000')),
|
|
'taker': self.parse_number(Precise.string_div(self.safe_string(response, takerFeeKey), '10000')),
|
|
'percentage': None,
|
|
'tierBased': None,
|
|
}
|
|
result[symbol] = tradingFee
|
|
return result
|
|
|
|
def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
|
|
#
|
|
# {
|
|
# "instrument_name": "BTC_USD",
|
|
# "effective_maker_rate_bps": "6.5",
|
|
# "effective_taker_rate_bps": "6.9"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(fee, 'instrument_name')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
return {
|
|
'info': fee,
|
|
'symbol': symbol,
|
|
'maker': self.parse_number(Precise.string_div(self.safe_string(fee, 'effective_maker_rate_bps'), '10000')),
|
|
'taker': self.parse_number(Precise.string_div(self.safe_string(fee, 'effective_taker_rate_bps'), '10000')),
|
|
'percentage': None,
|
|
'tierBased': None,
|
|
}
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
type = self.safe_string(api, 0)
|
|
access = self.safe_string(api, 1)
|
|
url = self.urls['api'][type] + '/' + path
|
|
query = self.omit(params, self.extract_params(path))
|
|
if access == 'public':
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
else:
|
|
self.check_required_credentials()
|
|
nonce = str(self.nonce())
|
|
requestParams = self.extend({}, params)
|
|
paramsKeys = list(requestParams.keys())
|
|
strSortKey = self.params_to_string(requestParams, 0)
|
|
payload = path + nonce + self.apiKey + strSortKey + nonce
|
|
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256)
|
|
paramsKeysLength = len(paramsKeys)
|
|
body = self.json({
|
|
'id': nonce,
|
|
'method': path,
|
|
'params': params,
|
|
'api_key': self.apiKey,
|
|
'sig': signature,
|
|
'nonce': nonce,
|
|
})
|
|
# fix issue https://github.com/ccxt/ccxt/issues/11179
|
|
# php always encodes dictionaries
|
|
# if an array is empty, php will put it in square brackets
|
|
# python and js will put it in curly brackets
|
|
# the code below checks and replaces those brackets in empty requests
|
|
if paramsKeysLength == 0:
|
|
paramsString = '{}'
|
|
arrayString = '[]'
|
|
body = body.replace(arrayString, paramsString)
|
|
headers = {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
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):
|
|
errorCode = self.safe_string(response, 'code')
|
|
if errorCode != '0':
|
|
feedback = self.id + ' ' + body
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
|
raise ExchangeError(self.id + ' ' + body)
|
|
return None
|