5521 lines
255 KiB
Python
5521 lines
255 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
||
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
||
|
||
from ccxt.async_support.base.exchange import Exchange
|
||
from ccxt.abstract.kucoin import ImplicitAPI
|
||
import asyncio
|
||
import hashlib
|
||
import math
|
||
import json
|
||
from ccxt.base.types import Account, Any, Balances, BorrowInterest, Bool, Currencies, Currency, DepositAddress, Int, LedgerEntry, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Str, Strings, Ticker, Tickers, FundingRate, Trade, TradingFeeInterface, Transaction, TransferEntry
|
||
from typing import List
|
||
from ccxt.base.errors import ExchangeError
|
||
from ccxt.base.errors import AuthenticationError
|
||
from ccxt.base.errors import PermissionDenied
|
||
from ccxt.base.errors import AccountSuspended
|
||
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 InvalidAddress
|
||
from ccxt.base.errors import InvalidOrder
|
||
from ccxt.base.errors import OrderNotFound
|
||
from ccxt.base.errors import NotSupported
|
||
from ccxt.base.errors import RateLimitExceeded
|
||
from ccxt.base.errors import ExchangeNotAvailable
|
||
from ccxt.base.errors import InvalidNonce
|
||
from ccxt.base.decimal_to_precision import TRUNCATE
|
||
from ccxt.base.decimal_to_precision import TICK_SIZE
|
||
from ccxt.base.precise import Precise
|
||
|
||
|
||
class kucoin(Exchange, ImplicitAPI):
|
||
|
||
def describe(self) -> Any:
|
||
return self.deep_extend(super(kucoin, self).describe(), {
|
||
'id': 'kucoin',
|
||
'name': 'KuCoin',
|
||
'countries': ['SC'],
|
||
'rateLimit': 10, # 100 requests per second =>( 1000ms / 100 ) = 10 ms between requests on average
|
||
'version': 'v2',
|
||
'certified': True,
|
||
'pro': True,
|
||
'comment': 'Platform 2.0',
|
||
'quoteJsonNumbers': False,
|
||
'has': {
|
||
'CORS': None,
|
||
'spot': True,
|
||
'margin': True,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': False,
|
||
'borrowCrossMargin': True,
|
||
'borrowIsolatedMargin': True,
|
||
'cancelAllOrders': True,
|
||
'cancelOrder': True,
|
||
'closeAllPositions': False,
|
||
'closePosition': False,
|
||
'createDepositAddress': True,
|
||
'createMarketBuyOrderWithCost': True,
|
||
'createMarketOrderWithCost': True,
|
||
'createMarketSellOrderWithCost': True,
|
||
'createOrder': True,
|
||
'createOrders': True,
|
||
'createPostOnlyOrder': True,
|
||
'createStopLimitOrder': True,
|
||
'createStopMarketOrder': True,
|
||
'createStopOrder': True,
|
||
'createTriggerOrder': True,
|
||
'editOrder': True,
|
||
'fetchAccounts': True,
|
||
'fetchBalance': True,
|
||
'fetchBorrowInterest': True,
|
||
'fetchBorrowRateHistories': True,
|
||
'fetchBorrowRateHistory': True,
|
||
'fetchClosedOrders': True,
|
||
'fetchCrossBorrowRate': False,
|
||
'fetchCrossBorrowRates': False,
|
||
'fetchCurrencies': True,
|
||
'fetchDepositAddress': True,
|
||
'fetchDepositAddresses': False,
|
||
'fetchDepositAddressesByNetwork': True,
|
||
'fetchDeposits': True,
|
||
'fetchDepositWithdrawFee': True,
|
||
'fetchDepositWithdrawFees': True,
|
||
'fetchFundingHistory': False,
|
||
'fetchFundingRate': True,
|
||
'fetchFundingRateHistory': True,
|
||
'fetchFundingRates': False,
|
||
'fetchIndexOHLCV': False,
|
||
'fetchIsolatedBorrowRate': False,
|
||
'fetchIsolatedBorrowRates': False,
|
||
'fetchL3OrderBook': True,
|
||
'fetchLedger': True,
|
||
'fetchLeverageTiers': False,
|
||
'fetchMarginAdjustmentHistory': False,
|
||
'fetchMarginMode': False,
|
||
'fetchMarketLeverageTiers': False,
|
||
'fetchMarkets': True,
|
||
'fetchMarkOHLCV': False,
|
||
'fetchMarkPrice': True,
|
||
'fetchMarkPrices': True,
|
||
'fetchMyTrades': True,
|
||
'fetchOHLCV': True,
|
||
'fetchOpenInterest': False,
|
||
'fetchOpenInterestHistory': False,
|
||
'fetchOpenOrders': True,
|
||
'fetchOrder': True,
|
||
'fetchOrderBook': True,
|
||
'fetchOrderBooks': False,
|
||
'fetchOrdersByStatus': True,
|
||
'fetchOrderTrades': True,
|
||
'fetchPositionHistory': False,
|
||
'fetchPositionMode': False,
|
||
'fetchPositionsHistory': False,
|
||
'fetchPremiumIndexOHLCV': False,
|
||
'fetchStatus': True,
|
||
'fetchTicker': True,
|
||
'fetchTickers': True,
|
||
'fetchTime': True,
|
||
'fetchTrades': True,
|
||
'fetchTradingFee': True,
|
||
'fetchTradingFees': False,
|
||
'fetchTransactionFee': True,
|
||
'fetchTransfers': False,
|
||
'fetchWithdrawals': True,
|
||
'repayCrossMargin': True,
|
||
'repayIsolatedMargin': True,
|
||
'setLeverage': True,
|
||
'setMarginMode': False,
|
||
'setPositionMode': False,
|
||
'signIn': False,
|
||
'transfer': True,
|
||
'withdraw': True,
|
||
},
|
||
'urls': {
|
||
'logo': 'https://user-images.githubusercontent.com/51840849/87295558-132aaf80-c50e-11ea-9801-a2fb0c57c799.jpg',
|
||
'referral': 'https://www.kucoin.com/ucenter/signup?rcode=E5wkqe',
|
||
'api': {
|
||
'public': 'https://api.kucoin.com',
|
||
'private': 'https://api.kucoin.com',
|
||
'futuresPrivate': 'https://api-futures.kucoin.com',
|
||
'futuresPublic': 'https://api-futures.kucoin.com',
|
||
'webExchange': 'https://kucoin.com/_api',
|
||
'broker': 'https://api-broker.kucoin.com',
|
||
'earn': 'https://api.kucoin.com',
|
||
'uta': 'https://api.kucoin.com',
|
||
},
|
||
'www': 'https://www.kucoin.com',
|
||
'doc': [
|
||
'https://docs.kucoin.com',
|
||
],
|
||
},
|
||
'requiredCredentials': {
|
||
'apiKey': True,
|
||
'secret': True,
|
||
'password': True,
|
||
},
|
||
'api': {
|
||
# level VIP0
|
||
# Spot => 3000/30s => 100/s
|
||
# Weight = x => 100/(100/x) = x
|
||
# Futures Management Public => 2000/30s => 200/3/s
|
||
# Weight = x => 100/(200/3/x) = x*1.5
|
||
'public': {
|
||
'get': {
|
||
# spot trading
|
||
'currencies': 4.5, # 3PW
|
||
'currencies/{currency}': 4.5, # 3PW
|
||
'symbols': 6, # 4PW
|
||
'market/orderbook/level1': 3, # 2PW
|
||
'market/allTickers': 22.5, # 15PW
|
||
'market/stats': 22.5, # 15PW
|
||
'markets': 4.5, # 3PW
|
||
'market/orderbook/level{level}_{limit}': 6, # 4PW
|
||
'market/orderbook/level2_20': 3, # 2PW
|
||
'market/orderbook/level2_100': 6, # 4PW
|
||
'market/histories': 4.5, # 3PW
|
||
'market/candles': 4.5, # 3PW
|
||
'prices': 4.5, # 3PW
|
||
'timestamp': 4.5, # 3PW
|
||
'status': 4.5, # 3PW
|
||
# margin trading
|
||
'mark-price/{symbol}/current': 3, # 2PW
|
||
'mark-price/all-symbols': 3,
|
||
'margin/config': 25, # 25SW
|
||
'announcements': 20, # 20W
|
||
'margin/collateralRatio': 10,
|
||
},
|
||
'post': {
|
||
# ws
|
||
'bullet-public': 15, # 10PW
|
||
},
|
||
},
|
||
'private': {
|
||
'get': {
|
||
# account
|
||
'user-info': 30, # 20MW
|
||
'accounts': 7.5, # 5MW
|
||
'accounts/{accountId}': 7.5, # 5MW
|
||
'accounts/ledgers': 3, # 2MW
|
||
'hf/accounts/ledgers': 2, # 2SW
|
||
'hf/margin/account/ledgers': 2, # 2SW
|
||
'transaction-history': 3, # 2MW
|
||
'sub/user': 30, # 20MW
|
||
'sub-accounts/{subUserId}': 22.5, # 15MW
|
||
'sub-accounts': 30, # 20MW
|
||
'sub/api-key': 30, # 20MW
|
||
# funding
|
||
'margin/account': 40, # 40SW
|
||
'margin/accounts': 15, # 15SW
|
||
'isolated/accounts': 15, # 15SW
|
||
'deposit-addresses': 7.5, # 5MW
|
||
'deposits': 7.5, # 5MW
|
||
'hist-deposits': 7.5, # 5MW
|
||
'withdrawals': 30, # 20MW
|
||
'hist-withdrawals': 30, # 20MW
|
||
'withdrawals/quotas': 30, # 20MW
|
||
'accounts/transferable': 30, # 20MW
|
||
'transfer-list': 30, # 20MW
|
||
'base-fee': 3, # 3SW
|
||
'trade-fees': 3, # 3SW
|
||
# spot trading
|
||
'market/orderbook/level{level}': 3, # 3SW
|
||
'market/orderbook/level2': 3, # 3SW
|
||
'market/orderbook/level3': 3, # 3SW
|
||
'hf/accounts/opened': 2, #
|
||
'hf/orders/active': 2, # 2SW
|
||
'hf/orders/active/symbols': 2, # 2SW
|
||
'hf/margin/order/active/symbols': 2, # 2SW
|
||
'hf/orders/done': 2, # 2SW
|
||
'hf/orders/{orderId}': 2, # 2SW
|
||
'hf/orders/client-order/{clientOid}': 2, # 2SW
|
||
'hf/orders/dead-cancel-all/query': 2, # 2SW
|
||
'hf/fills': 2, # 2SW
|
||
'orders': 2, # 2SW
|
||
'limit/orders': 3, # 3SW
|
||
'orders/{orderId}': 2, # 2SW
|
||
'order/client-order/{clientOid}': 3, # 3SW
|
||
'fills': 10, # 10SW
|
||
'limit/fills': 20, # 20SW
|
||
'stop-order': 8, # 8SW
|
||
'stop-order/{orderId}': 3, # 3SW
|
||
'stop-order/queryOrderByClientOid': 3, # 3SW
|
||
'oco/order/{orderId}': 2, # 2SW
|
||
'oco/order/details/{orderId}': 2, # 2SW
|
||
'oco/client-order/{clientOid}': 2, # 2SW
|
||
'oco/orders': 2, # 2SW
|
||
# margin trading
|
||
'hf/margin/orders/active': 4, # 4SW
|
||
'hf/margin/orders/done': 10, # 10SW
|
||
'hf/margin/orders/{orderId}': 4, # 4SW
|
||
'hf/margin/orders/client-order/{clientOid}': 5, # 5SW
|
||
'hf/margin/fills': 5, # 5SW
|
||
'etf/info': 25, # 25SW
|
||
'margin/currencies': 20, # 20SW
|
||
'risk/limit/strategy': 20, # 20SW(Deprecate)
|
||
'isolated/symbols': 20, # 20SW
|
||
'margin/symbols': 5,
|
||
'isolated/account/{symbol}': 50, # 50SW
|
||
'margin/borrow': 15, # 15SW
|
||
'margin/repay': 15, # 15SW
|
||
'margin/interest': 20, # 20SW
|
||
'project/list': 10, # 10SW
|
||
'project/marketInterestRate': 7.5, # 5PW
|
||
'redeem/orders': 10, # 10SW
|
||
'purchase/orders': 10, # 10SW
|
||
# broker
|
||
'broker/api/rebase/download': 3,
|
||
'broker/queryMyCommission': 3,
|
||
'broker/queryUser': 3,
|
||
'broker/queryDetailByUid': 3,
|
||
'migrate/user/account/status': 3,
|
||
# affiliate
|
||
'affiliate/inviter/statistics': 30,
|
||
},
|
||
'post': {
|
||
# account
|
||
'sub/user/created': 22.5, # 15MW
|
||
'sub/api-key': 30, # 20MW
|
||
'sub/api-key/update': 45, # 30MW
|
||
# funding
|
||
'deposit-addresses': 30, # 20MW
|
||
'withdrawals': 7.5, # 5MW
|
||
'accounts/universal-transfer': 6, # 4MW
|
||
'accounts/sub-transfer': 45, # 30MW
|
||
'accounts/inner-transfer': 15, # 10MW
|
||
'transfer-out': 30, # 20MW
|
||
'transfer-in': 30, # 20MW
|
||
# spot trading
|
||
'hf/orders': 1, # 1SW
|
||
'hf/orders/test': 1, # 1SW
|
||
'hf/orders/sync': 1, # 1SW
|
||
'hf/orders/multi': 1, # 1SW
|
||
'hf/orders/multi/sync': 1, # 1SW
|
||
'hf/orders/alter': 3, # 3SW
|
||
'hf/orders/dead-cancel-all': 2, # 2SW
|
||
'orders': 2, # 2SW
|
||
'orders/test': 2, # 2SW
|
||
'orders/multi': 3, # 3SW
|
||
'stop-order': 2, # 2SW
|
||
'oco/order': 2, # 2SW
|
||
# margin trading
|
||
'hf/margin/order': 5, # 5SW
|
||
'hf/margin/order/test': 5, # 5SW
|
||
'margin/order': 5, # 5SW
|
||
'margin/order/test': 5, # 5SW
|
||
'margin/borrow': 15, # 15SW
|
||
'margin/repay': 10, # 10SW
|
||
'purchase': 15, # 15SW
|
||
'redeem': 15, # 15SW
|
||
'lend/purchase/update': 10, # 10SW
|
||
# ws
|
||
'bullet-private': 10, # 10SW
|
||
'position/update-user-leverage': 5,
|
||
'deposit-address/create': 20,
|
||
},
|
||
'delete': {
|
||
# account
|
||
'sub/api-key': 45, # 30MW
|
||
# funding
|
||
'withdrawals/{withdrawalId}': 30, # 20MW
|
||
# spot trading
|
||
'hf/orders/{orderId}': 1, # 1SW
|
||
'hf/orders/sync/{orderId}': 1, # 1SW
|
||
'hf/orders/client-order/{clientOid}': 1, # 1SW
|
||
'hf/orders/sync/client-order/{clientOid}': 1, # 1SW
|
||
'hf/orders/cancel/{orderId}': 2, # 2SW
|
||
'hf/orders': 2, # 2SW
|
||
'hf/orders/cancelAll': 30, # 30SW
|
||
'orders/{orderId}': 3, # 3SW
|
||
'order/client-order/{clientOid}': 5, # 5SW
|
||
'orders': 20, # 20SW
|
||
'stop-order/{orderId}': 3, # 3SW
|
||
'stop-order/cancelOrderByClientOid': 5, # 5SW
|
||
'stop-order/cancel': 3, # 3SW
|
||
'oco/order/{orderId}': 3, # 3SW
|
||
'oco/client-order/{clientOid}': 3, # 3SW
|
||
'oco/orders': 3, # 3SW
|
||
# margin trading
|
||
'hf/margin/orders/{orderId}': 5, # 5SW
|
||
'hf/margin/orders/client-order/{clientOid}': 5, # 5SW
|
||
'hf/margin/orders': 10, # 10SW
|
||
},
|
||
},
|
||
'futuresPublic': {
|
||
'get': {
|
||
'contracts/active': 4.5, # 3PW
|
||
'contracts/{symbol}': 4.5, # 3PW
|
||
'ticker': 3, # 2PW
|
||
'level2/snapshot': 4.5, # 3PW
|
||
'level2/depth20': 7.5, # 5PW
|
||
'level2/depth100': 15, # 10PW
|
||
'trade/history': 7.5, # 5PW
|
||
'kline/query': 4.5, # 3PW
|
||
'interest/query': 7.5, # 5PW
|
||
'index/query': 3, # 2PW
|
||
'mark-price/{symbol}/current': 4.5, # 3PW
|
||
'premium/query': 4.5, # 3PW
|
||
'trade-statistics': 4.5, # 3PW
|
||
'funding-rate/{symbol}/current': 3, # 2PW
|
||
'contract/funding-rates': 7.5, # 5PW
|
||
'timestamp': 3, # 2PW
|
||
'status': 6, # 4PW
|
||
# ?
|
||
'level2/message/query': 1.3953,
|
||
},
|
||
'post': {
|
||
# ws
|
||
'bullet-public': 15, # 10PW
|
||
},
|
||
},
|
||
'futuresPrivate': {
|
||
'get': {
|
||
# account
|
||
'transaction-history': 3, # 2MW
|
||
# funding
|
||
'account-overview': 7.5, # 5FW
|
||
'account-overview-all': 9, # 6FW
|
||
'transfer-list': 30, # 20MW
|
||
# futures
|
||
'orders': 3, # 2FW
|
||
'stopOrders': 9, # 6FW
|
||
'recentDoneOrders': 7.5, # 5FW
|
||
'orders/{orderId}': 7.5, # 5FW
|
||
'orders/byClientOid': 7.5, # 5FW
|
||
'fills': 7.5, # 5FW
|
||
'recentFills': 4.5, # 3FW
|
||
'openOrderStatistics': 15, # 10FW
|
||
'position': 3, # 2FW
|
||
'positions': 3, # 2FW
|
||
'margin/maxWithdrawMargin': 15, # 10FW
|
||
'contracts/risk-limit/{symbol}': 7.5, # 5FW
|
||
'funding-history': 7.5, # 5FW
|
||
'copy-trade/futures/get-max-open-size': 6, # 4FW
|
||
'copy-trade/futures/position/margin/max-withdraw-margin': 15, # 10FW
|
||
},
|
||
'post': {
|
||
# funding
|
||
'transfer-out': 30, # 20MW
|
||
'transfer-in': 30, # 20MW
|
||
# futures
|
||
'orders': 3, # 2FW
|
||
'orders/test': 3, # 2FW
|
||
'orders/multi': 4.5, # 3FW
|
||
'position/margin/auto-deposit-status': 6, # 4FW
|
||
'margin/withdrawMargin': 15, # 10FW
|
||
'position/margin/deposit-margin': 6, # 4FW
|
||
'position/risk-limit-level/change': 6, # 4FW
|
||
'copy-trade/futures/orders': 3, # 2FW
|
||
'copy-trade/futures/orders/test': 3, # 2FW
|
||
'copy-trade/futures/st-orders': 3, # 2FW
|
||
'copy-trade/futures/position/margin/deposit-margin': 6, # 4FW
|
||
'copy-trade/futures/position/margin/withdraw-margin': 15, # 10FW
|
||
'copy-trade/futures/position/risk-limit-level/change': 3, # 2FW
|
||
'copy-trade/futures/position/margin/auto-deposit-status': 6, # 4FW
|
||
'copy-trade/futures/position/changeMarginMode': 3, # 2FW
|
||
'copy-trade/futures/position/changeCrossUserLeverage': 3, # 2FW
|
||
'copy-trade/getCrossModeMarginRequirement': 4.5, # 3FW
|
||
'copy-trade/position/switchPositionMode': 3, # 2FW
|
||
# ws
|
||
'bullet-private': 15, # 10FW
|
||
},
|
||
'delete': {
|
||
'orders/{orderId}': 1.5, # 1FW
|
||
'orders/client-order/{clientOid}': 1.5, # 1FW
|
||
'orders': 45, # 30FW
|
||
'stopOrders': 22.5, # 15FW
|
||
'copy-trade/futures/orders': 1.5, # 1FW
|
||
'copy-trade/futures/orders/client-order': 1.5, # 1FW
|
||
},
|
||
},
|
||
'webExchange': {
|
||
'get': {
|
||
'currency/currency/chain-info': 1, # self is temporary from webApi
|
||
},
|
||
},
|
||
'broker': {
|
||
'get': {
|
||
'broker/nd/info': 2,
|
||
'broker/nd/account': 2,
|
||
'broker/nd/account/apikey': 2,
|
||
'broker/nd/rebase/download': 3,
|
||
'asset/ndbroker/deposit/list': 1,
|
||
'broker/nd/transfer/detail': 1,
|
||
'broker/nd/deposit/detail': 1,
|
||
'broker/nd/withdraw/detail': 1,
|
||
},
|
||
'post': {
|
||
'broker/nd/transfer': 1,
|
||
'broker/nd/account': 3,
|
||
'broker/nd/account/apikey': 3,
|
||
'broker/nd/account/update-apikey': 3,
|
||
},
|
||
'delete': {
|
||
'broker/nd/account/apikey': 3,
|
||
},
|
||
},
|
||
'earn': {
|
||
'get': {
|
||
'otc-loan/loan': 1,
|
||
'otc-loan/accounts': 1,
|
||
'earn/redeem-preview': 7.5, # 5EW
|
||
'earn/saving/products': 7.5, # 5EW
|
||
'earn/hold-assets': 7.5, # 5EW
|
||
'earn/promotion/products': 7.5, # 5EW
|
||
'earn/kcs-staking/products': 7.5, # 5EW
|
||
'earn/staking/products': 7.5, # 5EW
|
||
'earn/eth-staking/products': 7.5, # 5EW
|
||
},
|
||
'post': {
|
||
'earn/orders': 7.5, # 5EW
|
||
},
|
||
'delete': {
|
||
'earn/orders': 7.5, # 5EW
|
||
},
|
||
},
|
||
'uta': {
|
||
'get': {
|
||
'market/announcement': 20,
|
||
'market/currency': 3,
|
||
'market/instrument': 4,
|
||
'market/ticker': 15,
|
||
'market/orderbook': 3,
|
||
'market/trade': 3,
|
||
'market/kline': 3,
|
||
'market/funding-rate': 2,
|
||
'market/funding-rate-history': 5,
|
||
'market/cross-config': 25,
|
||
'server/status': 3,
|
||
},
|
||
},
|
||
},
|
||
'timeframes': {
|
||
'1m': '1min',
|
||
'3m': '3min',
|
||
'5m': '5min',
|
||
'15m': '15min',
|
||
'30m': '30min',
|
||
'1h': '1hour',
|
||
'2h': '2hour',
|
||
'4h': '4hour',
|
||
'6h': '6hour',
|
||
'8h': '8hour',
|
||
'12h': '12hour',
|
||
'1d': '1day',
|
||
'1w': '1week',
|
||
'1M': '1month',
|
||
},
|
||
'precisionMode': TICK_SIZE,
|
||
'exceptions': {
|
||
'exact': {
|
||
'Order not exist or not allow to be cancelled': OrderNotFound,
|
||
'The order does not exist.': OrderNotFound,
|
||
'order not exist': OrderNotFound,
|
||
'order not exist.': OrderNotFound, # duplicated error temporarily
|
||
'order_not_exist': OrderNotFound, # {"code":"order_not_exist","msg":"order_not_exist"} ¯\_(ツ)_/¯
|
||
'order_not_exist_or_not_allow_to_cancel': InvalidOrder, # {"code":"400100","msg":"order_not_exist_or_not_allow_to_cancel"}
|
||
'Order size below the minimum requirement.': InvalidOrder, # {"code":"400100","msg":"Order size below the minimum requirement."}
|
||
'Order size increment invalid.': InvalidOrder, # {"msg":"Order size increment invalid.","code":"600100"}
|
||
'The withdrawal amount is below the minimum requirement.': ExchangeError, # {"code":"400100","msg":"The withdrawal amount is below the minimum requirement."}
|
||
'Unsuccessful! Exceeded the max. funds out-transfer limit': InsufficientFunds, # {"code":"200000","msg":"Unsuccessful! Exceeded the max. funds out-transfer limit"}
|
||
'The amount increment is invalid.': BadRequest,
|
||
'The quantity is below the minimum requirement.': InvalidOrder, # {"msg":"The quantity is below the minimum requirement.","code":"400100"}
|
||
'400': BadRequest,
|
||
'401': AuthenticationError,
|
||
'403': NotSupported,
|
||
'404': NotSupported,
|
||
'405': NotSupported,
|
||
'415': NotSupported,
|
||
'429': RateLimitExceeded,
|
||
'500': ExchangeNotAvailable, # Internal Server Error -- We had a problem with our server. Try again later.
|
||
'503': ExchangeNotAvailable,
|
||
'101030': PermissionDenied, # {"code":"101030","msg":"You haven't yet enabled the margin trading"}
|
||
'103000': InvalidOrder, # {"code":"103000","msg":"Exceed the borrowing limit, the remaining borrowable amount is: 0USDT"}
|
||
'130101': BadRequest, # Parameter error
|
||
'130102': ExchangeError, # Maximum subscription amount has been exceeded.
|
||
'130103': OrderNotFound, # Subscription order does not exist.
|
||
'130104': ExchangeError, # Maximum number of subscription orders has been exceeded.
|
||
'130105': InsufficientFunds, # Insufficient balance.
|
||
'130106': NotSupported, # The currency does not support redemption.
|
||
'130107': ExchangeError, # Redemption amount exceeds subscription amount.
|
||
'130108': OrderNotFound, # Redemption order does not exist.
|
||
'130201': PermissionDenied, # Your account has restricted access to certain features. Please contact customer service for further assistance
|
||
'130202': ExchangeError, # The system is renewing the loan automatically. Please try again later
|
||
'130203': InsufficientFunds, # Insufficient account balance
|
||
'130204': BadRequest, # As the total lending amount for platform leverage reaches the platform's maximum position limit, the system suspends the borrowing function of leverage
|
||
'130301': InsufficientFunds, # Insufficient account balance
|
||
'130302': PermissionDenied, # Your relevant permission rights have been restricted, you can contact customer service for processing
|
||
'130303': NotSupported, # The current trading pair does not support isolated positions
|
||
'130304': NotSupported, # The trading function of the current trading pair is not enabled
|
||
'130305': NotSupported, # The current trading pair does not support cross position
|
||
'130306': NotSupported, # The account has not opened leveraged trading
|
||
'130307': NotSupported, # Please reopen the leverage agreement
|
||
'130308': InvalidOrder, # Position renewal freeze
|
||
'130309': InvalidOrder, # Position forced liquidation freeze
|
||
'130310': ExchangeError, # Abnormal leverage account status
|
||
'130311': InvalidOrder, # Failed to place an order, triggering buy limit
|
||
'130312': InvalidOrder, # Trigger global position limit, suspend buying
|
||
'130313': InvalidOrder, # Trigger global position limit, suspend selling
|
||
'130314': InvalidOrder, # Trigger the global position limit and prompt the remaining quantity available for purchase
|
||
'130315': NotSupported, # This feature has been suspended due to country restrictions
|
||
'126000': ExchangeError, # Abnormal margin trading
|
||
'126001': NotSupported, # Users currently do not support high frequency
|
||
'126002': ExchangeError, # There is a risk problem in your account and transactions are temporarily not allowed!
|
||
'126003': InvalidOrder, # The commission amount is less than the minimum transaction amount for a single commission
|
||
'126004': ExchangeError, # Trading pair does not exist or is prohibited
|
||
'126005': PermissionDenied, # This trading pair requires advanced KYC certification before trading
|
||
'126006': ExchangeError, # Trading pair is not available
|
||
'126007': ExchangeError, # Trading pair suspended
|
||
'126009': ExchangeError, # Trading pair is suspended from creating orders
|
||
'126010': ExchangeError, # Trading pair suspended order cancellation
|
||
'126011': ExchangeError, # There are too many orders in the order
|
||
'126013': InsufficientFunds, # Insufficient account balance
|
||
'126015': ExchangeError, # It is prohibited to place orders on self trading pair
|
||
'126021': NotSupported, # This digital asset does not support user participation in your region, thank you for your understanding!
|
||
'126022': InvalidOrder, # The final transaction price of your order will trigger the price protection strategy. To protect the price from deviating too much, please place an order again.
|
||
'126027': InvalidOrder, # Only limit orders are supported
|
||
'126028': InvalidOrder, # Only limit orders are supported before the specified time
|
||
'126029': InvalidOrder, # The maximum order price is: xxx
|
||
'126030': InvalidOrder, # The minimum order price is: xxx
|
||
'126033': InvalidOrder, # Duplicate order
|
||
'126034': InvalidOrder, # Failed to create take profit and stop loss order
|
||
'126036': InvalidOrder, # Failed to create margin order
|
||
'126037': ExchangeError, # Due to country and region restrictions, self function has been suspended!
|
||
'126038': ExchangeError, # Third-party service call failed(internal exception)
|
||
'126039': ExchangeError, # Third-party service call failed, reason: xxx
|
||
'126041': ExchangeError, # clientTimestamp parameter error
|
||
'126042': ExchangeError, # Exceeded maximum position limit
|
||
'126043': OrderNotFound, # Order does not exist
|
||
'126044': InvalidOrder, # clientOid duplicate
|
||
'126045': NotSupported, # This digital asset does not support user participation in your region, thank you for your understanding!
|
||
'126046': NotSupported, # This digital asset does not support your IP region, thank you for your understanding!
|
||
'126047': PermissionDenied, # Please complete identity verification
|
||
'126048': PermissionDenied, # Please complete authentication for the master account
|
||
'135005': ExchangeError, # Margin order query business abnormality
|
||
'135018': ExchangeError, # Margin order query service abnormality
|
||
'200004': InsufficientFunds,
|
||
'210014': InvalidOrder, # {"code":"210014","msg":"Exceeds the max. borrowing amount, the remaining amount you can borrow: 0USDT"}
|
||
'210021': InsufficientFunds, # {"code":"210021","msg":"Balance not enough"}
|
||
'230003': InsufficientFunds, # {"code":"230003","msg":"Balance insufficient!"}
|
||
'260000': InvalidAddress, # {"code":"260000","msg":"Deposit address already exists."}
|
||
'260100': InsufficientFunds, # {"code":"260100","msg":"account.noBalance"}
|
||
'300000': InvalidOrder,
|
||
'400000': BadSymbol,
|
||
'400001': AuthenticationError,
|
||
'400002': InvalidNonce,
|
||
'400003': AuthenticationError,
|
||
'400004': AuthenticationError,
|
||
'400005': AuthenticationError,
|
||
'400006': AuthenticationError,
|
||
'400007': AuthenticationError,
|
||
'400008': NotSupported,
|
||
'400100': InsufficientFunds, # {"msg":"account.available.amount","code":"400100"} or {"msg":"Withdrawal amount is below the minimum requirement.","code":"400100"}
|
||
'400200': InvalidOrder, # {"code":"400200","msg":"Forbidden to place an order"}
|
||
'400330': InvalidOrder, # {"msg":"Order price can't deviate from NAV by 50%","code":"400330"}
|
||
'400350': InvalidOrder, # {"code":"400350","msg":"Upper limit for holding: 10,000USDT, you can still buy 10,000USDT worth of coin."}
|
||
'400370': InvalidOrder, # {"code":"400370","msg":"Max. price: 0.02500000000000000000"}
|
||
'400400': BadRequest, # Parameter error
|
||
'400401': AuthenticationError, # User is not logged in
|
||
'400500': InvalidOrder, # {"code":"400500","msg":"Your located country/region is currently not supported for the trading of self token"}
|
||
'400600': BadSymbol, # {"code":"400600","msg":"validation.createOrder.symbolNotAvailable"}
|
||
'400760': InvalidOrder, # {"code":"400760","msg":"order price should be more than XX"}
|
||
'401000': BadRequest, # {"code":"401000","msg":"The interface has been deprecated"}
|
||
'408000': BadRequest, # Network timeout, please try again later
|
||
'411100': AccountSuspended,
|
||
'415000': BadRequest, # {"code":"415000","msg":"Unsupported Media Type"}
|
||
'400303': PermissionDenied, # {"msg":"To enjoy the full range of our products and services, we kindly request you complete the identity verification process.","code":"400303"}
|
||
'500000': ExchangeNotAvailable, # {"code":"500000","msg":"Internal Server Error"}
|
||
'260220': InvalidAddress, # {"code": "260220", "msg": "deposit.address.not.exists"}
|
||
'600100': InsufficientFunds, # {"msg":"Funds below the minimum requirement.","code":"600100"}
|
||
'600101': InvalidOrder, # {"msg":"The order funds should more then 0.1 USDT.","code":"600101"}
|
||
'900014': BadRequest, # {"code":"900014","msg":"Invalid chainId"}
|
||
},
|
||
'broad': {
|
||
'Exceeded the access frequency': RateLimitExceeded,
|
||
'require more permission': PermissionDenied,
|
||
},
|
||
},
|
||
'fees': {
|
||
'trading': {
|
||
'tierBased': True,
|
||
'percentage': True,
|
||
'taker': self.parse_number('0.001'),
|
||
'maker': self.parse_number('0.001'),
|
||
'tiers': {
|
||
'taker': [
|
||
[self.parse_number('0'), self.parse_number('0.001')],
|
||
[self.parse_number('50'), self.parse_number('0.001')],
|
||
[self.parse_number('200'), self.parse_number('0.0009')],
|
||
[self.parse_number('500'), self.parse_number('0.0008')],
|
||
[self.parse_number('1000'), self.parse_number('0.0007')],
|
||
[self.parse_number('2000'), self.parse_number('0.0007')],
|
||
[self.parse_number('4000'), self.parse_number('0.0006')],
|
||
[self.parse_number('8000'), self.parse_number('0.0005')],
|
||
[self.parse_number('15000'), self.parse_number('0.00045')],
|
||
[self.parse_number('25000'), self.parse_number('0.0004')],
|
||
[self.parse_number('40000'), self.parse_number('0.00035')],
|
||
[self.parse_number('60000'), self.parse_number('0.0003')],
|
||
[self.parse_number('80000'), self.parse_number('0.00025')],
|
||
],
|
||
'maker': [
|
||
[self.parse_number('0'), self.parse_number('0.001')],
|
||
[self.parse_number('50'), self.parse_number('0.0009')],
|
||
[self.parse_number('200'), self.parse_number('0.0007')],
|
||
[self.parse_number('500'), self.parse_number('0.0005')],
|
||
[self.parse_number('1000'), self.parse_number('0.0003')],
|
||
[self.parse_number('2000'), self.parse_number('0')],
|
||
[self.parse_number('4000'), self.parse_number('0')],
|
||
[self.parse_number('8000'), self.parse_number('0')],
|
||
[self.parse_number('15000'), self.parse_number('-0.00005')],
|
||
[self.parse_number('25000'), self.parse_number('-0.00005')],
|
||
[self.parse_number('40000'), self.parse_number('-0.00005')],
|
||
[self.parse_number('60000'), self.parse_number('-0.00005')],
|
||
[self.parse_number('80000'), self.parse_number('-0.00005')],
|
||
],
|
||
},
|
||
},
|
||
'funding': {
|
||
'tierBased': False,
|
||
'percentage': False,
|
||
'withdraw': {},
|
||
'deposit': {},
|
||
},
|
||
},
|
||
'commonCurrencies': {
|
||
'BIFI': 'BIFIF',
|
||
'VAI': 'VAIOT',
|
||
'WAX': 'WAXP',
|
||
'ALT': 'APTOSLAUNCHTOKEN',
|
||
'KALT': 'ALT', # ALTLAYER
|
||
'FUD': 'FTX Users\' Debt',
|
||
},
|
||
'options': {
|
||
'hf': None, # would be auto set to `true/false` after first load
|
||
'version': 'v1',
|
||
'symbolSeparator': '-',
|
||
'fetchMyTradesMethod': 'private_get_fills',
|
||
'timeDifference': 0, # the difference between system clock and Binance clock
|
||
'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
|
||
'fetchCurrencies': {
|
||
'webApiEnable': True, # fetches from WEB
|
||
'webApiRetries': 1,
|
||
'webApiMuteFailure': True,
|
||
},
|
||
'fetchMarkets': {
|
||
'fetchTickersFees': True,
|
||
},
|
||
'withdraw': {
|
||
'includeFee': False,
|
||
},
|
||
# endpoint versions
|
||
'versions': {
|
||
'public': {
|
||
'GET': {
|
||
# spot trading
|
||
'currencies': 'v3',
|
||
'currencies/{currency}': 'v3',
|
||
'symbols': 'v2',
|
||
'mark-price/all-symbols': 'v3',
|
||
'announcements': 'v3',
|
||
},
|
||
},
|
||
'private': {
|
||
'GET': {
|
||
# account
|
||
'user-info': 'v2',
|
||
'hf/margin/account/ledgers': 'v3',
|
||
'sub/user': 'v2',
|
||
'sub-accounts': 'v2',
|
||
# funding
|
||
'margin/accounts': 'v3',
|
||
'isolated/accounts': 'v3',
|
||
# 'deposit-addresses': 'v2',
|
||
'deposit-addresses': 'v1', # 'v1' for fetchDepositAddress, 'v2' for fetchDepositAddressesByNetwork
|
||
# spot trading
|
||
'market/orderbook/level2': 'v3',
|
||
'market/orderbook/level3': 'v3',
|
||
'market/orderbook/level{level}': 'v3',
|
||
'oco/order/{orderId}': 'v3',
|
||
'oco/order/details/{orderId}': 'v3',
|
||
'oco/client-order/{clientOid}': 'v3',
|
||
'oco/orders': 'v3',
|
||
# margin trading
|
||
'hf/margin/orders/active': 'v3',
|
||
'hf/margin/order/active/symbols': 'v3',
|
||
'hf/margin/orders/done': 'v3',
|
||
'hf/margin/orders/{orderId}': 'v3',
|
||
'hf/margin/orders/client-order/{clientOid}': 'v3',
|
||
'hf/margin/fills': 'v3',
|
||
'etf/info': 'v3',
|
||
'margin/currencies': 'v3',
|
||
'margin/borrow': 'v3',
|
||
'margin/repay': 'v3',
|
||
'margin/interest': 'v3',
|
||
'project/list': 'v3',
|
||
'project/marketInterestRate': 'v3',
|
||
'redeem/orders': 'v3',
|
||
'purchase/orders': 'v3',
|
||
'migrate/user/account/status': 'v3',
|
||
'margin/symbols': 'v3',
|
||
'affiliate/inviter/statistics': 'v2',
|
||
'asset/ndbroker/deposit/list': 'v1',
|
||
},
|
||
'POST': {
|
||
# account
|
||
'sub/user/created': 'v2',
|
||
# funding
|
||
'accounts/universal-transfer': 'v3',
|
||
'accounts/sub-transfer': 'v2',
|
||
'accounts/inner-transfer': 'v2',
|
||
'transfer-out': 'v3',
|
||
'deposit-address/create': 'v3',
|
||
# spot trading
|
||
'oco/order': 'v3',
|
||
# margin trading
|
||
'hf/margin/order': 'v3',
|
||
'hf/margin/order/test': 'v3',
|
||
'margin/borrow': 'v3',
|
||
'margin/repay': 'v3',
|
||
'purchase': 'v3',
|
||
'redeem': 'v3',
|
||
'lend/purchase/update': 'v3',
|
||
'position/update-user-leverage': 'v3',
|
||
'withdrawals': 'v3',
|
||
},
|
||
'DELETE': {
|
||
# account
|
||
# funding
|
||
# spot trading
|
||
'hf/margin/orders/{orderId}': 'v3',
|
||
'hf/margin/orders/client-order/{clientOid}': 'v3',
|
||
'hf/margin/orders': 'v3',
|
||
'oco/order/{orderId}': 'v3',
|
||
'oco/client-order/{clientOid}': 'v3',
|
||
'oco/orders': 'v3',
|
||
# margin trading
|
||
},
|
||
},
|
||
'futuresPrivate': {
|
||
'POST': {
|
||
'transfer-out': 'v3',
|
||
},
|
||
},
|
||
},
|
||
'partner': {
|
||
# the support for spot and future exchanges settings
|
||
'spot': {
|
||
'id': 'ccxt',
|
||
'key': '9e58cc35-5b5e-4133-92ec-166e3f077cb8',
|
||
},
|
||
'future': {
|
||
'id': 'ccxtfutures',
|
||
'key': '1b327198-f30c-4f14-a0ac-918871282f15',
|
||
},
|
||
# exchange-wide settings are also supported
|
||
# 'id': 'ccxt'
|
||
# 'key': '9e58cc35-5b5e-4133-92ec-166e3f077cb8',
|
||
},
|
||
'accountsByType': {
|
||
'spot': 'trade',
|
||
'margin': 'margin',
|
||
'cross': 'margin',
|
||
'isolated': 'isolated',
|
||
'main': 'main',
|
||
'funding': 'main',
|
||
'future': 'contract',
|
||
'swap': 'contract',
|
||
'mining': 'pool',
|
||
'hf': 'trade_hf',
|
||
},
|
||
'networks': {
|
||
'BRC20': 'btc',
|
||
'BTCNATIVESEGWIT': 'bech32',
|
||
'ERC20': 'eth',
|
||
'TRC20': 'trx',
|
||
'HRC20': 'heco',
|
||
'MATIC': 'matic',
|
||
'KCC': 'kcc', # kucoin community chain
|
||
'SOL': 'sol',
|
||
'ALGO': 'algo',
|
||
'EOS': 'eos',
|
||
'BEP20': 'bsc',
|
||
'BEP2': 'bnb',
|
||
'ARBONE': 'arbitrum',
|
||
'AVAXX': 'avax',
|
||
'AVAXC': 'avaxc',
|
||
'TLOS': 'tlos', # tlosevm is different
|
||
'CFX': 'cfx',
|
||
'ACA': 'aca',
|
||
'OPTIMISM': 'optimism',
|
||
'ONT': 'ont',
|
||
'GLMR': 'glmr',
|
||
'CSPR': 'cspr',
|
||
'KLAY': 'klay',
|
||
'XRD': 'xrd',
|
||
'RVN': 'rvn',
|
||
'NEAR': 'near',
|
||
'APT': 'aptos',
|
||
'ETHW': 'ethw',
|
||
'TON': 'ton',
|
||
'BCH': 'bch',
|
||
'BSV': 'bchsv',
|
||
'BCHA': 'bchabc',
|
||
'OSMO': 'osmo',
|
||
'NANO': 'nano',
|
||
'XLM': 'xlm',
|
||
'VET': 'vet',
|
||
'IOST': 'iost',
|
||
'ZIL': 'zil',
|
||
'XRP': 'xrp',
|
||
'TOMO': 'tomo',
|
||
'XMR': 'xmr',
|
||
'COTI': 'coti',
|
||
'XTZ': 'xtz',
|
||
'ADA': 'ada',
|
||
'WAX': 'waxp',
|
||
'THETA': 'theta',
|
||
'ONE': 'one',
|
||
'IOTEX': 'iotx',
|
||
'NULS': 'nuls',
|
||
'KSM': 'ksm',
|
||
'LTC': 'ltc',
|
||
'WAVES': 'waves',
|
||
'DOT': 'dot',
|
||
'STEEM': 'steem',
|
||
'QTUM': 'qtum',
|
||
'DOGE': 'doge',
|
||
'FIL': 'fil',
|
||
'XYM': 'xym',
|
||
'FLUX': 'flux',
|
||
'ATOM': 'atom',
|
||
'XDC': 'xdc',
|
||
'KDA': 'kda',
|
||
'ICP': 'icp',
|
||
'CELO': 'celo',
|
||
'LSK': 'lsk',
|
||
'VSYS': 'vsys',
|
||
'KAR': 'kar',
|
||
'XCH': 'xch',
|
||
'FLOW': 'flow',
|
||
'BAND': 'band',
|
||
'EGLD': 'egld',
|
||
'HBAR': 'hbar',
|
||
'XPR': 'xpr',
|
||
'AR': 'ar',
|
||
'FTM': 'ftm',
|
||
'KAVA': 'kava',
|
||
'KMA': 'kma',
|
||
'XEC': 'xec',
|
||
'IOTA': 'iota',
|
||
'HNT': 'hnt',
|
||
'ASTR': 'astr',
|
||
'PDEX': 'pdex',
|
||
'METIS': 'metis',
|
||
'ZEC': 'zec',
|
||
'POKT': 'pokt',
|
||
'OASYS': 'oas',
|
||
'OASIS': 'oasis', # a.k.a. ROSE
|
||
'ETC': 'etc',
|
||
'AKT': 'akt',
|
||
'FSN': 'fsn',
|
||
'SCRT': 'scrt',
|
||
'CFG': 'cfg',
|
||
'ICX': 'icx',
|
||
'KMD': 'kmd',
|
||
'NEM': 'NEM',
|
||
'STX': 'stx',
|
||
'DGB': 'dgb',
|
||
'DCR': 'dcr',
|
||
'CKB': 'ckb', # ckb2 is just odd entry
|
||
'ELA': 'ela', # esc might be another chain elastos smart chain
|
||
'HYDRA': 'hydra',
|
||
'BTM': 'btm',
|
||
'KARDIA': 'kai',
|
||
'SXP': 'sxp', # a.k.a. solar swipe
|
||
'NEBL': 'nebl',
|
||
'ZEN': 'zen',
|
||
'SDN': 'sdn',
|
||
'LTO': 'lto',
|
||
'WEMIX': 'wemix',
|
||
# 'BOBA': 'boba', # tbd
|
||
'EVER': 'ever',
|
||
'BNC': 'bnc',
|
||
'BNCDOT': 'bncdot',
|
||
# 'CMP': 'cmp', # todo: after consensus
|
||
'AION': 'aion',
|
||
'GRIN': 'grin',
|
||
'LOKI': 'loki',
|
||
'QKC': 'qkc',
|
||
'TT': 'TT',
|
||
'PIVX': 'pivx',
|
||
'SERO': 'sero',
|
||
'METER': 'meter',
|
||
'STATEMINE': 'statemine', # a.k.a. RMRK
|
||
'DVPN': 'dvpn',
|
||
'XPRT': 'xprt',
|
||
'MOVR': 'movr',
|
||
'ERGO': 'ergo',
|
||
'ABBC': 'abbc',
|
||
'DIVI': 'divi',
|
||
'PURA': 'pura',
|
||
'DFI': 'dfi',
|
||
# 'NEO': 'neo', # tbd neo legacy
|
||
'NEON3': 'neon3',
|
||
'DOCK': 'dock',
|
||
'TRUE': 'true',
|
||
'CS': 'cs',
|
||
'ORAI': 'orai',
|
||
'BASE': 'base',
|
||
'TARA': 'tara',
|
||
# below will be uncommented after consensus
|
||
# 'BITCOINDIAMON': 'bcd',
|
||
# 'BITCOINGOLD': 'btg',
|
||
# 'HTR': 'htr',
|
||
# 'DEROHE': 'derohe',
|
||
# 'NDAU': 'ndau',
|
||
# 'HPB': 'hpb',
|
||
# 'AXE': 'axe',
|
||
# 'BITCOINPRIVATE': 'btcp',
|
||
# 'EDGEWARE': 'edg',
|
||
# 'JUPITER': 'jup',
|
||
# 'VELAS': 'vlx', # vlxevm is different
|
||
# # 'terra' luna lunc TBD
|
||
# 'DIGITALBITS': 'xdb',
|
||
# # fra is fra-emv on kucoin
|
||
# 'PASTEL': 'psl',
|
||
# # sysevm
|
||
# 'CONCORDIUM': 'ccd',
|
||
# 'AURORA': 'aurora',
|
||
# 'PHA': 'pha', # a.k.a. khala
|
||
# 'PAL': 'pal',
|
||
# 'RSK': 'rbtc',
|
||
# 'NIX': 'nix',
|
||
# 'NIM': 'nim',
|
||
# 'NRG': 'nrg',
|
||
# 'RFOX': 'rfox',
|
||
# 'PIONEER': 'neer',
|
||
# 'PIXIE': 'pix',
|
||
# 'ALEPHZERO': 'azero',
|
||
# 'ACHAIN': 'act', # actevm is different
|
||
# 'BOSCOIN': 'bos',
|
||
# 'ELECTRONEUM': 'etn',
|
||
# 'GOCHAIN': 'go',
|
||
# 'SOPHIATX': 'sphtx',
|
||
# 'WANCHAIN': 'wan',
|
||
# 'ZEEPIN': 'zpt',
|
||
# 'MATRIXAI': 'man',
|
||
# 'METADIUM': 'meta',
|
||
# 'METAHASH': 'mhc',
|
||
# # eosc --"eosforce" tbd
|
||
# 'IOTCHAIN': 'itc',
|
||
# 'CONTENTOS': 'cos',
|
||
# 'CPCHAIN': 'cpc',
|
||
# 'INTCHAIN': 'int',
|
||
# # 'DASH': 'dash', tbd digita-cash
|
||
# 'WALTONCHAIN': 'wtc',
|
||
# 'CONSTELLATION': 'dag',
|
||
# 'ONELEDGER': 'olt',
|
||
# 'AIRDAO': 'amb', # a.k.a. AMBROSUS
|
||
# 'ENERGYWEB': 'ewt',
|
||
# 'WAVESENTERPRISE': 'west',
|
||
# 'HYPERCASH': 'hc',
|
||
# 'ENECUUM': 'enq',
|
||
# 'HAVEN': 'xhv',
|
||
# 'CHAINX': 'pcx',
|
||
# # 'FLUXOLD': 'zel', # zel seems old chain(with uppercase FLUX in kucoin UI and with id 'zel')
|
||
# 'BUMO': 'bu',
|
||
# 'DEEPONION': 'onion',
|
||
# 'ULORD': 'ut',
|
||
# 'ASCH': 'xas',
|
||
# 'SOLARIS': 'xlr',
|
||
# 'APOLLO': 'apl',
|
||
# 'PIRATECHAIN': 'arrr',
|
||
# 'ULTRA': 'uos',
|
||
# 'EMONEY': 'ngm',
|
||
# 'AURORACHAIN': 'aoa',
|
||
# 'KLEVER': 'klv',
|
||
# undetermined: xns(insolar), rhoc, luk(luniverse), kts(klimatas), bchn(bitcoin cash node), god(shallow entry), lit(litmus),
|
||
},
|
||
'marginModes': {
|
||
'cross': 'MARGIN_TRADE',
|
||
'isolated': 'MARGIN_ISOLATED_TRADE',
|
||
'spot': 'TRADE',
|
||
},
|
||
},
|
||
'features': {
|
||
'spot': {
|
||
'sandbox': False,
|
||
'createOrder': {
|
||
'marginMode': True,
|
||
'triggerPrice': True,
|
||
'triggerPriceType': None,
|
||
'triggerDirection': False,
|
||
'stopLossPrice': True,
|
||
'takeProfitPrice': True,
|
||
'attachedStopLossTakeProfit': None, # not supported
|
||
'timeInForce': {
|
||
'IOC': True,
|
||
'FOK': True,
|
||
'PO': True,
|
||
'GTD': True,
|
||
},
|
||
'hedged': False,
|
||
'trailing': False,
|
||
'leverage': False,
|
||
'marketBuyByCost': True,
|
||
'marketBuyRequiresPrice': False,
|
||
'selfTradePrevention': True, # todo implement
|
||
'iceberg': True, # todo implement
|
||
},
|
||
'createOrders': {
|
||
'max': 5,
|
||
},
|
||
'fetchMyTrades': {
|
||
'marginMode': True,
|
||
'limit': None,
|
||
'daysBack': None,
|
||
'untilDays': 7, # per implementation comments
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOrder': {
|
||
'marginMode': False,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': True,
|
||
'limit': 500,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOrders': None,
|
||
'fetchClosedOrders': {
|
||
'marginMode': True,
|
||
'limit': 500,
|
||
'daysBack': None,
|
||
'daysBackCanceled': None,
|
||
'untilDays': 7,
|
||
'trigger': True,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOHLCV': {
|
||
'limit': 1500,
|
||
},
|
||
},
|
||
'swap': {
|
||
'linear': None,
|
||
'inverse': None,
|
||
},
|
||
'future': {
|
||
'linear': None,
|
||
'inverse': None,
|
||
},
|
||
},
|
||
})
|
||
|
||
def nonce(self):
|
||
return self.milliseconds() - self.options['timeDifference']
|
||
|
||
async def fetch_time(self, params={}) -> Int:
|
||
"""
|
||
fetches the current integer timestamp in milliseconds from the exchange server
|
||
|
||
https://docs.kucoin.com/#server-time
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns int: the current integer timestamp in milliseconds from the exchange server
|
||
"""
|
||
response = await self.publicGetTimestamp(params)
|
||
#
|
||
# {
|
||
# "code":"200000",
|
||
# "msg":"success",
|
||
# "data":1546837113087
|
||
# }
|
||
#
|
||
return self.safe_integer(response, 'data')
|
||
|
||
async def fetch_status(self, params={}):
|
||
"""
|
||
the latest known information on the availability of the exchange API
|
||
|
||
https://docs.kucoin.com/#service-status
|
||
https://www.kucoin.com/docs-new/rest/ua/get-service-status
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.uta]: set to True for the unified trading account(uta), defaults to False
|
||
:param str [params.tradeType]: *uta only* set to SPOT or FUTURES
|
||
:returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
|
||
"""
|
||
uta = None
|
||
uta, params = self.handle_option_and_params(params, 'fetchStatus', 'uta', False)
|
||
response = None
|
||
if uta:
|
||
defaultType = self.safe_string(self.options, 'defaultType', 'spot')
|
||
defaultTradeType = 'SPOT' if (defaultType == 'spot') else 'FUTURES'
|
||
tradeType = self.safe_string_upper(params, 'tradeType', defaultTradeType)
|
||
request = {
|
||
'tradeType': tradeType,
|
||
}
|
||
response = await self.utaGetServerStatus(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "tradeType": "SPOT",
|
||
# "serverStatus": "open",
|
||
# "msg": ""
|
||
# }
|
||
# }
|
||
#
|
||
else:
|
||
response = await self.publicGetStatus(params)
|
||
#
|
||
# {
|
||
# "code":"200000",
|
||
# "data":{
|
||
# "status":"open", #open, close, cancelonly
|
||
# "msg":"upgrade match engine" #remark for operation
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
status = self.safe_string_2(data, 'status', 'serverStatus')
|
||
return {
|
||
'status': 'ok' if (status == 'open') else 'maintenance',
|
||
'updated': None,
|
||
'eta': None,
|
||
'url': None,
|
||
'info': response,
|
||
}
|
||
|
||
async def fetch_markets(self, params={}) -> List[Market]:
|
||
"""
|
||
retrieves data on all markets for kucoin
|
||
|
||
https://docs.kucoin.com/#get-symbols-list-deprecated
|
||
https://docs.kucoin.com/#get-all-tickers
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.uta]: set to True for the unified trading account(uta), defaults to False
|
||
:returns dict[]: an array of objects representing market data
|
||
"""
|
||
fetchTickersFees = None
|
||
fetchTickersFees, params = self.handle_option_and_params(params, 'fetchMarkets', 'fetchTickersFees', True)
|
||
uta = None
|
||
uta, params = self.handle_option_and_params(params, 'fetchMarkets', 'uta', False)
|
||
if uta:
|
||
return await self.fetch_uta_markets(params)
|
||
promises = []
|
||
promises.append(self.publicGetSymbols(params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": [
|
||
# {
|
||
# "symbol": "XLM-USDT",
|
||
# "name": "XLM-USDT",
|
||
# "baseCurrency": "XLM",
|
||
# "quoteCurrency": "USDT",
|
||
# "feeCurrency": "USDT",
|
||
# "market": "USDS",
|
||
# "baseMinSize": "0.1",
|
||
# "quoteMinSize": "0.01",
|
||
# "baseMaxSize": "10000000000",
|
||
# "quoteMaxSize": "99999999",
|
||
# "baseIncrement": "0.0001",
|
||
# "quoteIncrement": "0.000001",
|
||
# "priceIncrement": "0.000001",
|
||
# "priceLimitRate": "0.1",
|
||
# "isMarginEnabled": True,
|
||
# "enableTrading": True
|
||
# },
|
||
#
|
||
credentialsSet = self.check_required_credentials(False)
|
||
requestMarginables = credentialsSet and self.safe_bool(params, 'marginables', True)
|
||
if requestMarginables:
|
||
promises.append(self.privateGetMarginSymbols(params)) # cross margin symbols
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "timestamp": 1719393213421,
|
||
# "items": [
|
||
# {
|
||
# # same object market, with one additional field:
|
||
# "minFunds": "0.1"
|
||
# },
|
||
#
|
||
promises.append(self.privateGetIsolatedSymbols(params)) # isolated margin symbols
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": [
|
||
# {
|
||
# "symbol": "NKN-USDT",
|
||
# "symbolName": "NKN-USDT",
|
||
# "baseCurrency": "NKN",
|
||
# "quoteCurrency": "USDT",
|
||
# "maxLeverage": 5,
|
||
# "flDebtRatio": "0.97",
|
||
# "tradeEnable": True,
|
||
# "autoRenewMaxDebtRatio": "0.96",
|
||
# "baseBorrowEnable": True,
|
||
# "quoteBorrowEnable": True,
|
||
# "baseTransferInEnable": True,
|
||
# "quoteTransferInEnable": True,
|
||
# "baseBorrowCoefficient": "1",
|
||
# "quoteBorrowCoefficient": "1"
|
||
# },
|
||
#
|
||
if fetchTickersFees:
|
||
promises.append(self.publicGetMarketAllTickers(params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "time":1602832092060,
|
||
# "ticker":[
|
||
# {
|
||
# "symbol": "BTC-USDT", # symbol
|
||
# "symbolName":"BTC-USDT", # Name of trading pairs, it would change after renaming
|
||
# "buy": "11328.9", # bestAsk
|
||
# "sell": "11329", # bestBid
|
||
# "changeRate": "-0.0055", # 24h change rate
|
||
# "changePrice": "-63.6", # 24h change price
|
||
# "high": "11610", # 24h highest price
|
||
# "low": "11200", # 24h lowest price
|
||
# "vol": "2282.70993217", # 24h volume,the aggregated trading volume in BTC
|
||
# "volValue": "25984946.157790431", # 24h total, the trading volume in quote currency of last 24 hours
|
||
# "last": "11328.9", # last price
|
||
# "averagePrice": "11360.66065903", # 24h average transaction price yesterday
|
||
# "takerFeeRate": "0.001", # Basic Taker Fee
|
||
# "makerFeeRate": "0.001", # Basic Maker Fee
|
||
# "takerCoefficient": "1", # Taker Fee Coefficient
|
||
# "makerCoefficient": "1" # Maker Fee Coefficient
|
||
# }
|
||
#
|
||
if credentialsSet:
|
||
# load migration status for account
|
||
promises.append(self.load_migration_status())
|
||
responses = await asyncio.gather(*promises)
|
||
symbolsData = self.safe_list(responses[0], 'data')
|
||
crossData = self.safe_dict(responses[1], 'data', {}) if requestMarginables else {}
|
||
crossItems = self.safe_list(crossData, 'items', [])
|
||
crossById = self.index_by(crossItems, 'symbol')
|
||
isolatedData = responses[2] if requestMarginables else {}
|
||
isolatedItems = self.safe_list(isolatedData, 'data', [])
|
||
isolatedById = self.index_by(isolatedItems, 'symbol')
|
||
tickersIdx = 3 if requestMarginables else 1
|
||
tickersResponse = self.safe_dict(responses, tickersIdx, {})
|
||
tickerItems = self.safe_list(self.safe_dict(tickersResponse, 'data', {}), 'ticker', [])
|
||
tickersById = self.index_by(tickerItems, 'symbol')
|
||
result = []
|
||
for i in range(0, len(symbolsData)):
|
||
market = symbolsData[i]
|
||
id = self.safe_string(market, 'symbol')
|
||
baseId, quoteId = id.split('-')
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
# quoteIncrement = self.safe_number(market, 'quoteIncrement')
|
||
ticker = self.safe_dict(tickersById, id, {})
|
||
makerFeeRate = self.safe_string(ticker, 'makerFeeRate')
|
||
takerFeeRate = self.safe_string(ticker, 'takerFeeRate')
|
||
makerCoefficient = self.safe_string(ticker, 'makerCoefficient')
|
||
takerCoefficient = self.safe_string(ticker, 'takerCoefficient')
|
||
hasCrossMargin = (id in crossById)
|
||
hasIsolatedMargin = (id in isolatedById)
|
||
isMarginable = self.safe_bool(market, 'isMarginEnabled', False) or hasCrossMargin or hasIsolatedMargin
|
||
result.append({
|
||
'id': id,
|
||
'symbol': base + '/' + quote,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': None,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': None,
|
||
'type': 'spot',
|
||
'spot': True,
|
||
'margin': isMarginable,
|
||
'marginModes': {
|
||
'cross': hasCrossMargin,
|
||
'isolated': hasIsolatedMargin,
|
||
},
|
||
'swap': False,
|
||
'future': False,
|
||
'option': False,
|
||
'active': self.safe_bool(market, 'enableTrading'),
|
||
'contract': False,
|
||
'linear': None,
|
||
'inverse': None,
|
||
'taker': self.parse_number(Precise.string_mul(takerFeeRate, takerCoefficient)),
|
||
'maker': self.parse_number(Precise.string_mul(makerFeeRate, makerCoefficient)),
|
||
'contractSize': None,
|
||
'expiry': None,
|
||
'expiryDatetime': None,
|
||
'strike': None,
|
||
'optionType': None,
|
||
'precision': {
|
||
'amount': self.safe_number(market, 'baseIncrement'),
|
||
'price': self.safe_number(market, 'priceIncrement'),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number(market, 'baseMinSize'),
|
||
'max': self.safe_number(market, 'baseMaxSize'),
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': self.safe_number(market, 'quoteMinSize'),
|
||
'max': self.safe_number(market, 'quoteMaxSize'),
|
||
},
|
||
},
|
||
'created': None,
|
||
'info': market,
|
||
})
|
||
if self.options['adjustForTimeDifference']:
|
||
await self.load_time_difference()
|
||
return result
|
||
|
||
async def fetch_uta_markets(self, params={}) -> List[Market]:
|
||
promises = []
|
||
promises.append(self.utaGetMarketInstrument(self.extend(params, {'tradeType': 'SPOT'})))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "tradeType": "SPOT",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "AVA-USDT",
|
||
# "name": "AVA-USDT",
|
||
# "baseCurrency": "AVA",
|
||
# "quoteCurrency": "USDT",
|
||
# "market": "USDS",
|
||
# "minBaseOrderSize": "0.1",
|
||
# "minQuoteOrderSize": "0.1",
|
||
# "maxBaseOrderSize": "10000000000",
|
||
# "maxQuoteOrderSize": "99999999",
|
||
# "baseOrderStep": "0.01",
|
||
# "quoteOrderStep": "0.0001",
|
||
# "tickSize": "0.0001",
|
||
# "feeCurrency": "USDT",
|
||
# "tradingStatus": "1",
|
||
# "marginMode": "2",
|
||
# "priceLimitRatio": "0.05",
|
||
# "feeCategory": 1,
|
||
# "makerFeeCoefficient": "1.00",
|
||
# "takerFeeCoefficient": "1.00",
|
||
# "st": False
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
promises.append(self.utaGetMarketInstrument(self.extend(params, {'tradeType': 'FUTURES'})))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "tradeType": "FUTURES",
|
||
# "list": [
|
||
# {
|
||
# "symbol": "XBTUSDTM",
|
||
# "baseCurrency": "XBT",
|
||
# "quoteCurrency": "USDT",
|
||
# "maxBaseOrderSize": "1000000",
|
||
# "tickSize": "0.1",
|
||
# "tradingStatus": "1",
|
||
# "settlementCurrency": "USDT",
|
||
# "contractType": "0",
|
||
# "isInverse": False,
|
||
# "launchTime": 1585555200000,
|
||
# "expiryTime": null,
|
||
# "settlementTime": null,
|
||
# "maxPrice": "1000000.0",
|
||
# "lotSize": "1",
|
||
# "unitSize": "0.001",
|
||
# "makerFeeRate": "0.00020",
|
||
# "takerFeeRate": "0.00060",
|
||
# "settlementFeeRate": null,
|
||
# "maxLeverage": 125,
|
||
# "indexSourceExchanges": ["okex","binance","kucoin","bybit","bitmart","gateio"],
|
||
# "k": "490.0",
|
||
# "m": "300.0",
|
||
# "f": "1.3",
|
||
# "mmrLimit": "0.3",
|
||
# "mmrLevConstant": "125.0"
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
responses = await asyncio.gather(*promises)
|
||
data = self.safe_dict(responses[0], 'data', {})
|
||
contractData = self.safe_dict(responses[1], 'data', {})
|
||
spotData = self.safe_list(data, 'list', [])
|
||
contractSymbolsData = self.safe_list(contractData, 'list', [])
|
||
symbolsData = self.array_concat(spotData, contractSymbolsData)
|
||
result = []
|
||
for i in range(0, len(symbolsData)):
|
||
market = symbolsData[i]
|
||
id = self.safe_string(market, 'symbol')
|
||
baseId = self.safe_string(market, 'baseCurrency')
|
||
quoteId = self.safe_string(market, 'quoteCurrency')
|
||
settleId = self.safe_string(market, 'settlementCurrency')
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
settle = self.safe_currency_code(settleId)
|
||
hasMargin = self.safe_string(market, 'marginMode')
|
||
isMarginable = True if (hasMargin == '1') else False
|
||
symbol = base + '/' + quote
|
||
if settle is not None:
|
||
symbol += ':' + settle
|
||
contractType = self.safe_string(market, 'contractType')
|
||
expiry = self.safe_integer(market, 'expiryTime')
|
||
active = self.safe_string(market, 'tradingStatus')
|
||
type = None
|
||
spot = False
|
||
swap = False
|
||
future = False
|
||
contract = False
|
||
linear = False
|
||
inverse = False
|
||
if contractType is not None:
|
||
contract = True
|
||
if quote == settle:
|
||
linear = True
|
||
else:
|
||
inverse = True
|
||
if contractType == '0':
|
||
type = 'swap'
|
||
swap = True
|
||
else:
|
||
type = 'future'
|
||
future = True
|
||
else:
|
||
type = 'spot'
|
||
spot = True
|
||
result.append({
|
||
'id': id,
|
||
'symbol': symbol,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': settle,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': settleId,
|
||
'type': type,
|
||
'spot': spot,
|
||
'margin': isMarginable,
|
||
'swap': swap,
|
||
'future': future,
|
||
'option': False,
|
||
'active': (active == '1'),
|
||
'contract': contract,
|
||
'linear': linear,
|
||
'inverse': inverse,
|
||
'taker': self.safe_number(market, 'makerFeeRate'),
|
||
'maker': self.safe_number(market, 'takerFeeRate'),
|
||
'contractSize': self.safe_number(market, 'unitSize'),
|
||
'expiry': expiry,
|
||
'expiryDatetime': self.iso8601(expiry),
|
||
'strike': None,
|
||
'optionType': None,
|
||
'precision': {
|
||
'amount': self.safe_number(market, 'lotSize'),
|
||
'price': self.safe_number(market, 'tickSize'),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': None,
|
||
'max': self.safe_integer(market, 'maxLeverage'),
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number(market, 'minBaseOrderSize'),
|
||
'max': self.safe_number(market, 'maxBaseOrderSize'),
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': self.safe_number(market, 'maxPrice'),
|
||
},
|
||
'cost': {
|
||
'min': self.safe_number(market, 'minQuoteOrderSize'),
|
||
'max': self.safe_number(market, 'maxQuoteOrderSize'),
|
||
},
|
||
},
|
||
'created': self.safe_integer(market, 'launchTime'),
|
||
'info': market,
|
||
})
|
||
if self.options['adjustForTimeDifference']:
|
||
await self.load_time_difference()
|
||
return result
|
||
|
||
async def load_migration_status(self, force: bool = False):
|
||
"""
|
||
:param boolean force: load account state for non hf
|
||
loads the migration status for the account(hf or not)
|
||
|
||
https://www.kucoin.com/docs/rest/spot-trading/spot-hf-trade-pro-account/get-user-type
|
||
|
||
:returns any: ignore
|
||
"""
|
||
if not ('hf' in self.options) or (self.options['hf'] is None) or force:
|
||
result: dict = await self.privateGetHfAccountsOpened()
|
||
self.options['hf'] = self.safe_bool(result, 'data')
|
||
return True
|
||
|
||
def handle_hf_and_params(self, params={}):
|
||
migrated: Bool = self.safe_bool(self.options, 'hf', False)
|
||
loadedHf: Bool = None
|
||
if migrated is not None:
|
||
if migrated:
|
||
loadedHf = True
|
||
else:
|
||
loadedHf = False
|
||
hf: Bool = self.safe_bool(params, 'hf', loadedHf)
|
||
params = self.omit(params, 'hf')
|
||
return [hf, params]
|
||
|
||
async def fetch_currencies(self, params={}) -> Currencies:
|
||
"""
|
||
fetches all available currencies on an exchange
|
||
|
||
https://docs.kucoin.com/#get-currencies
|
||
|
||
:param dict params: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an associative dictionary of currencies
|
||
"""
|
||
response = await self.publicGetCurrencies(params)
|
||
#
|
||
# {
|
||
# "code":"200000",
|
||
# "data":[
|
||
# {
|
||
# "currency":"CSP",
|
||
# "name":"CSP",
|
||
# "fullName":"Caspian",
|
||
# "precision":8,
|
||
# "confirms":null,
|
||
# "contractAddress":null,
|
||
# "isMarginEnabled":false,
|
||
# "isDebitEnabled":false,
|
||
# "chains":[
|
||
# {
|
||
# "chainName":"ERC20",
|
||
# "chainId": "eth"
|
||
# "withdrawalMinSize":"2999",
|
||
# "depositMinSize":null,
|
||
# "withdrawFeeRate":"0",
|
||
# "withdrawalMinFee":"2999",
|
||
# "isWithdrawEnabled":false,
|
||
# "isDepositEnabled":false,
|
||
# "confirms":12,
|
||
# "preConfirms":12,
|
||
# "withdrawPrecision": 8,
|
||
# "maxWithdraw": null,
|
||
# "maxDeposit": null,
|
||
# "needTag": False,
|
||
# "contractAddress":"0xa6446d655a0c34bc4f05042ee88170d056cbaf45",
|
||
# "depositFeeRate": "0.001", # present for some currencies/networks
|
||
# }
|
||
# ]
|
||
# },
|
||
# ]
|
||
# }
|
||
#
|
||
currenciesData = self.safe_list(response, 'data', [])
|
||
brokenCurrencies = self.safe_list(self.options, 'brokenCurrencies', ['00', 'OPEN_ERROR', 'HUF', 'BDT'])
|
||
result: dict = {}
|
||
for i in range(0, len(currenciesData)):
|
||
entry = currenciesData[i]
|
||
id = self.safe_string(entry, 'currency')
|
||
if self.in_array(id, brokenCurrencies):
|
||
continue # skip buggy entries: https://t.me/KuCoin_API/217798
|
||
code = self.safe_currency_code(id)
|
||
networks: dict = {}
|
||
chains = self.safe_list(entry, 'chains', [])
|
||
chainsLength = len(chains)
|
||
for j in range(0, chainsLength):
|
||
chain = chains[j]
|
||
chainId = self.safe_string(chain, 'chainId')
|
||
networkCode = self.network_id_to_code(chainId, code)
|
||
networks[networkCode] = {
|
||
'info': chain,
|
||
'id': chainId,
|
||
'name': self.safe_string(chain, 'chainName'),
|
||
'code': networkCode,
|
||
'active': None,
|
||
'fee': self.safe_number(chain, 'withdrawalMinFee'),
|
||
'deposit': self.safe_bool(chain, 'isDepositEnabled'),
|
||
'withdraw': self.safe_bool(chain, 'isWithdrawEnabled'),
|
||
'precision': self.parse_number(self.parse_precision(self.safe_string(chain, 'withdrawPrecision'))),
|
||
'limits': {
|
||
'withdraw': {
|
||
'min': self.safe_number(chain, 'withdrawalMinSize'),
|
||
'max': self.safe_number(chain, 'maxWithdraw'),
|
||
},
|
||
'deposit': {
|
||
'min': self.safe_number(chain, 'depositMinSize'),
|
||
'max': self.safe_number(chain, 'maxDeposit'),
|
||
},
|
||
},
|
||
}
|
||
# kucoin has determined 'fiat' currencies with below logic
|
||
rawPrecision = self.safe_string(entry, 'precision')
|
||
precision = self.parse_number(self.parse_precision(rawPrecision))
|
||
isFiat = chainsLength == 0
|
||
result[code] = self.safe_currency_structure({
|
||
'id': id,
|
||
'name': self.safe_string(entry, 'fullName'),
|
||
'code': code,
|
||
'type': 'fiat' if isFiat else 'crypto',
|
||
'precision': precision,
|
||
'info': entry,
|
||
'networks': networks,
|
||
'deposit': None,
|
||
'withdraw': None,
|
||
'active': None,
|
||
'fee': None,
|
||
'limits': None,
|
||
})
|
||
return result
|
||
|
||
async def fetch_accounts(self, params={}) -> List[Account]:
|
||
"""
|
||
fetch all the accounts associated with a profile
|
||
|
||
https://docs.kucoin.com/#list-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
|
||
"""
|
||
response = await self.privateGetAccounts(params)
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": [
|
||
# {
|
||
# "balance": "0.00009788",
|
||
# "available": "0.00009788",
|
||
# "holds": "0",
|
||
# "currency": "BTC",
|
||
# "id": "5c6a4fd399a1d81c4f9cc4d0",
|
||
# "type": "trade"
|
||
# },
|
||
# {
|
||
# "balance": "0.00000001",
|
||
# "available": "0.00000001",
|
||
# "holds": "0",
|
||
# "currency": "ETH",
|
||
# "id": "5c6a49ec99a1d819392e8e9f",
|
||
# "type": "trade"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
result = []
|
||
for i in range(0, len(data)):
|
||
account = data[i]
|
||
accountId = self.safe_string(account, 'id')
|
||
currencyId = self.safe_string(account, 'currency')
|
||
code = self.safe_currency_code(currencyId)
|
||
type = self.safe_string(account, 'type') # main or trade
|
||
result.append({
|
||
'id': accountId,
|
||
'type': type,
|
||
'currency': code,
|
||
'code': code,
|
||
'info': account,
|
||
})
|
||
return result
|
||
|
||
async def fetch_transaction_fee(self, code: str, params={}):
|
||
"""
|
||
*DEPRECATED* please use fetchDepositWithdrawFee instead
|
||
|
||
https://docs.kucoin.com/#get-withdrawal-quotas
|
||
|
||
:param str code: unified currency code
|
||
:param dict params: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
}
|
||
networkCode = None
|
||
networkCode, params = self.handle_network_code_and_params(params)
|
||
if networkCode is not None:
|
||
request['chain'] = self.network_code_to_id(networkCode).lower()
|
||
response = await self.privateGetWithdrawalsQuotas(self.extend(request, params))
|
||
data = self.safe_dict(response, 'data', {})
|
||
withdrawFees: dict = {}
|
||
withdrawFees[code] = self.safe_number(data, 'withdrawMinFee')
|
||
return {
|
||
'info': response,
|
||
'withdraw': withdrawFees,
|
||
'deposit': {},
|
||
}
|
||
|
||
async def fetch_deposit_withdraw_fee(self, code: str, params={}):
|
||
"""
|
||
fetch the fee for deposits and withdrawals
|
||
|
||
https://docs.kucoin.com/#get-withdrawal-quotas
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.network]: The chain of currency. This only apply for multi-chain currency, and there is no need for single chain currency; you can query the chain through the response of the GET /api/v2/currencies/{currency} interface
|
||
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
}
|
||
networkCode = None
|
||
networkCode, params = self.handle_network_code_and_params(params)
|
||
if networkCode is not None:
|
||
request['chain'] = self.network_code_to_id(networkCode).lower()
|
||
response = await self.privateGetWithdrawalsQuotas(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "currency": "USDT",
|
||
# "limitBTCAmount": "1.00000000",
|
||
# "usedBTCAmount": "0.00000000",
|
||
# "remainAmount": "16548.072149",
|
||
# "availableAmount": "0",
|
||
# "withdrawMinFee": "25",
|
||
# "innerWithdrawMinFee": "0",
|
||
# "withdrawMinSize": "50",
|
||
# "isWithdrawEnabled": True,
|
||
# "precision": 6,
|
||
# "chain": "ERC20"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data')
|
||
return self.parse_deposit_withdraw_fee(data, currency)
|
||
|
||
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
|
||
#
|
||
# {
|
||
# "currency": "USDT",
|
||
# "limitBTCAmount": "1.00000000",
|
||
# "usedBTCAmount": "0.00000000",
|
||
# "remainAmount": "16548.072149",
|
||
# "availableAmount": "0",
|
||
# "withdrawMinFee": "25",
|
||
# "innerWithdrawMinFee": "0",
|
||
# "withdrawMinSize": "50",
|
||
# "isWithdrawEnabled": True,
|
||
# "precision": 6,
|
||
# "chain": "ERC20"
|
||
# }
|
||
#
|
||
if 'chains' in fee:
|
||
# if data obtained through `currencies` endpoint
|
||
resultNew: dict = {
|
||
'info': fee,
|
||
'withdraw': {
|
||
'fee': None,
|
||
'percentage': False,
|
||
},
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
'networks': {},
|
||
}
|
||
chains = self.safe_list(fee, 'chains', [])
|
||
for i in range(0, len(chains)):
|
||
chain = chains[i]
|
||
networkCodeNew = self.network_id_to_code(self.safe_string(chain, 'chainId'), self.safe_string(currency, 'code'))
|
||
resultNew['networks'][networkCodeNew] = {
|
||
'withdraw': {
|
||
'fee': self.safe_number_2(chain, 'withdrawalMinFee', 'withdrawMinFee'),
|
||
'percentage': False,
|
||
},
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
}
|
||
return resultNew
|
||
minWithdrawFee = self.safe_number(fee, 'withdrawMinFee')
|
||
result: dict = {
|
||
'info': fee,
|
||
'withdraw': {
|
||
'fee': minWithdrawFee,
|
||
'percentage': False,
|
||
},
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
'networks': {},
|
||
}
|
||
networkId = self.safe_string(fee, 'chain')
|
||
networkCode = self.network_id_to_code(networkId, self.safe_string(currency, 'code'))
|
||
result['networks'][networkCode] = {
|
||
'withdraw': minWithdrawFee,
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
}
|
||
return result
|
||
|
||
def is_futures_method(self, methodName, params):
|
||
#
|
||
# Helper
|
||
# @methodName(string): The name of the method
|
||
# @params(dict): The parameters passed into {methodName}
|
||
# @return: True if the method used is meant for futures trading, False otherwise
|
||
#
|
||
defaultType = self.safe_string_2(self.options, methodName, 'defaultType', 'trade')
|
||
requestedType = self.safe_string(params, 'type', defaultType)
|
||
accountsByType = self.safe_dict(self.options, 'accountsByType')
|
||
type = self.safe_string(accountsByType, requestedType)
|
||
if type is None:
|
||
keys = list(accountsByType.keys())
|
||
raise ExchangeError(self.id + ' isFuturesMethod() type must be one of ' + ', '.join(keys))
|
||
params = self.omit(params, 'type')
|
||
return(type == 'contract') or (type == 'future') or (type == 'futures') # * (type == 'futures') deprecated, use(type == 'future')
|
||
|
||
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
||
#
|
||
# {
|
||
# "symbol": "BTC-USDT", # symbol
|
||
# "symbolName":"BTC-USDT", # Name of trading pairs, it would change after renaming
|
||
# "buy": "11328.9", # bestAsk
|
||
# "sell": "11329", # bestBid
|
||
# "changeRate": "-0.0055", # 24h change rate
|
||
# "changePrice": "-63.6", # 24h change price
|
||
# "high": "11610", # 24h highest price
|
||
# "low": "11200", # 24h lowest price
|
||
# "vol": "2282.70993217", # 24h volume,the aggregated trading volume in BTC
|
||
# "volValue": "25984946.157790431", # 24h total, the trading volume in quote currency of last 24 hours
|
||
# "last": "11328.9", # last price
|
||
# "averagePrice": "11360.66065903", # 24h average transaction price yesterday
|
||
# "takerFeeRate": "0.001", # Basic Taker Fee
|
||
# "makerFeeRate": "0.001", # Basic Maker Fee
|
||
# "takerCoefficient": "1", # Taker Fee Coefficient
|
||
# "makerCoefficient": "1" # Maker Fee Coefficient
|
||
# }
|
||
#
|
||
# {
|
||
# "trading": True,
|
||
# "symbol": "KCS-BTC",
|
||
# "buy": 0.00011,
|
||
# "sell": 0.00012,
|
||
# "sort": 100,
|
||
# "volValue": 3.13851792584, #total
|
||
# "baseCurrency": "KCS",
|
||
# "market": "BTC",
|
||
# "quoteCurrency": "BTC",
|
||
# "symbolCode": "KCS-BTC",
|
||
# "datetime": 1548388122031,
|
||
# "high": 0.00013,
|
||
# "vol": 27514.34842,
|
||
# "low": 0.0001,
|
||
# "changePrice": -1.0e-5,
|
||
# "changeRate": -0.0769,
|
||
# "lastTradedPrice": 0.00012,
|
||
# "board": 0,
|
||
# "mark": 0
|
||
# }
|
||
#
|
||
# market/ticker ws subscription
|
||
#
|
||
# {
|
||
# "bestAsk": "62258.9",
|
||
# "bestAskSize": "0.38579986",
|
||
# "bestBid": "62258.8",
|
||
# "bestBidSize": "0.0078381",
|
||
# "price": "62260.7",
|
||
# "sequence": "1621383297064",
|
||
# "size": "0.00002841",
|
||
# "time": 1634641777363
|
||
# }
|
||
#
|
||
# uta
|
||
#
|
||
# {
|
||
# "symbol": "BTC-USDT",
|
||
# "name": "BTC-USDT",
|
||
# "bestBidSize": "0.69207954",
|
||
# "bestBidPrice": "110417.5",
|
||
# "bestAskSize": "0.08836606",
|
||
# "bestAskPrice": "110417.6",
|
||
# "lastPrice": "110417.5",
|
||
# "size": "0.00016",
|
||
# "open": "110105.1",
|
||
# "high": "110838.9",
|
||
# "low": "109705.5",
|
||
# "baseVolume": "1882.10069442",
|
||
# "quoteVolume": "207325626.822922498"
|
||
# }
|
||
#
|
||
percentage = self.safe_string(ticker, 'changeRate')
|
||
if percentage is not None:
|
||
percentage = Precise.string_mul(percentage, '100')
|
||
last = self.safe_string_n(ticker, ['last', 'lastTradedPrice', 'lastPrice'])
|
||
last = self.safe_string(ticker, 'price', last)
|
||
marketId = self.safe_string(ticker, 'symbol')
|
||
market = self.safe_market(marketId, market, '-')
|
||
symbol = market['symbol']
|
||
baseVolume = self.safe_string_2(ticker, 'vol', 'baseVolume')
|
||
quoteVolume = self.safe_string_2(ticker, 'volValue', 'quoteVolume')
|
||
timestamp = self.safe_integer_n(ticker, ['time', 'datetime', 'timePoint'])
|
||
return self.safe_ticker({
|
||
'symbol': symbol,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'high': self.safe_string(ticker, 'high'),
|
||
'low': self.safe_string(ticker, 'low'),
|
||
'bid': self.safe_string_n(ticker, ['buy', 'bestBid', 'bestBidPrice']),
|
||
'bidVolume': self.safe_string(ticker, 'bestBidSize'),
|
||
'ask': self.safe_string_n(ticker, ['sell', 'bestAsk', 'bestAskPrice']),
|
||
'askVolume': self.safe_string(ticker, 'bestAskSize'),
|
||
'vwap': None,
|
||
'open': self.safe_string(ticker, 'open'),
|
||
'close': last,
|
||
'last': last,
|
||
'previousClose': None,
|
||
'change': self.safe_string(ticker, 'changePrice'),
|
||
'percentage': percentage,
|
||
'average': self.safe_string(ticker, 'averagePrice'),
|
||
'baseVolume': baseVolume,
|
||
'quoteVolume': quoteVolume,
|
||
'markPrice': self.safe_string(ticker, 'value'),
|
||
'info': ticker,
|
||
}, market)
|
||
|
||
async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
||
"""
|
||
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
||
|
||
https://docs.kucoin.com/#get-all-tickers
|
||
https://www.kucoin.com/docs-new/rest/ua/get-ticker
|
||
|
||
: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
|
||
:param boolean [params.uta]: set to True for the unified trading account(uta), defaults to False
|
||
:param str [params.tradeType]: *uta only* set to SPOT or FUTURES
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
request: dict = {}
|
||
symbols = self.market_symbols(symbols)
|
||
uta = None
|
||
uta, params = self.handle_option_and_params(params, 'fetchTickers', 'uta', False)
|
||
response = None
|
||
if uta:
|
||
if symbols is not None:
|
||
symbol = self.safe_string(symbols, 0)
|
||
market = self.market(symbol)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchTickers', market, params)
|
||
if type == 'spot':
|
||
request['tradeType'] = 'SPOT'
|
||
else:
|
||
request['tradeType'] = 'FUTURES'
|
||
else:
|
||
tradeType = self.safe_string_upper(params, 'tradeType')
|
||
if tradeType is None:
|
||
raise ArgumentsRequired(self.id + ' fetchTickers() requires a tradeType parameter for uta, either SPOT or FUTURES')
|
||
request['tradeType'] = tradeType
|
||
params = self.omit(params, 'tradeType')
|
||
response = await self.utaGetMarketTicker(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "tradeType": "SPOT",
|
||
# "ts": 1762061290067,
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTC-USDT",
|
||
# "name": "BTC-USDT",
|
||
# "bestBidSize": "0.69207954",
|
||
# "bestBidPrice": "110417.5",
|
||
# "bestAskSize": "0.08836606",
|
||
# "bestAskPrice": "110417.6",
|
||
# "lastPrice": "110417.5",
|
||
# "size": "0.00016",
|
||
# "open": "110105.1",
|
||
# "high": "110838.9",
|
||
# "low": "109705.5",
|
||
# "baseVolume": "1882.10069442",
|
||
# "quoteVolume": "207325626.822922498"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
else:
|
||
response = await self.publicGetMarketAllTickers(params)
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "time":1602832092060,
|
||
# "ticker":[
|
||
# {
|
||
# "symbol": "BTC-USDT", # symbol
|
||
# "symbolName":"BTC-USDT", # Name of trading pairs, it would change after renaming
|
||
# "buy": "11328.9", # bestAsk
|
||
# "sell": "11329", # bestBid
|
||
# "changeRate": "-0.0055", # 24h change rate
|
||
# "changePrice": "-63.6", # 24h change price
|
||
# "high": "11610", # 24h highest price
|
||
# "low": "11200", # 24h lowest price
|
||
# "vol": "2282.70993217", # 24h volume,the aggregated trading volume in BTC
|
||
# "volValue": "25984946.157790431", # 24h total, the trading volume in quote currency of last 24 hours
|
||
# "last": "11328.9", # last price
|
||
# "averagePrice": "11360.66065903", # 24h average transaction price yesterday
|
||
# "takerFeeRate": "0.001", # Basic Taker Fee
|
||
# "makerFeeRate": "0.001", # Basic Maker Fee
|
||
# "takerCoefficient": "1", # Taker Fee Coefficient
|
||
# "makerCoefficient": "1" # Maker Fee Coefficient
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
tickers = self.safe_list_2(data, 'ticker', 'list', [])
|
||
time = self.safe_integer_2(data, 'time', 'ts')
|
||
result: dict = {}
|
||
for i in range(0, len(tickers)):
|
||
tickers[i]['time'] = time
|
||
ticker = self.parse_ticker(tickers[i])
|
||
symbol = self.safe_string(ticker, 'symbol')
|
||
if symbol is not None:
|
||
result[symbol] = ticker
|
||
return self.filter_by_array_tickers(result, 'symbol', symbols)
|
||
|
||
async def fetch_mark_prices(self, symbols: Strings = None, params={}) -> Tickers:
|
||
"""
|
||
fetches the mark price for multiple markets
|
||
|
||
https://www.kucoin.com/docs/rest/margin-trading/margin-info/get-all-margin-trading-pairs-mark-prices
|
||
|
||
:param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
symbols = self.market_symbols(symbols)
|
||
response = await self.publicGetMarkPriceAllSymbols(params)
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_tickers(data)
|
||
|
||
async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
||
"""
|
||
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
||
|
||
https://docs.kucoin.com/#get-24hr-stats
|
||
https://www.kucoin.com/docs-new/rest/ua/get-ticker
|
||
|
||
:param str symbol: unified symbol of the market to fetch the ticker for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.uta]: set to True for the unified trading account(uta), defaults to False
|
||
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
uta = None
|
||
uta, params = self.handle_option_and_params(params, 'fetchTicker', 'uta', False)
|
||
response = None
|
||
result = None
|
||
if uta:
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchTicker', market, params)
|
||
if type == 'spot':
|
||
request['tradeType'] = 'SPOT'
|
||
else:
|
||
request['tradeType'] = 'FUTURES'
|
||
response = await self.utaGetMarketTicker(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "tradeType": "SPOT",
|
||
# "ts": 1762061290067,
|
||
# "list": [
|
||
# {
|
||
# "symbol": "BTC-USDT",
|
||
# "name": "BTC-USDT",
|
||
# "bestBidSize": "0.69207954",
|
||
# "bestBidPrice": "110417.5",
|
||
# "bestAskSize": "0.08836606",
|
||
# "bestAskPrice": "110417.6",
|
||
# "lastPrice": "110417.5",
|
||
# "size": "0.00016",
|
||
# "open": "110105.1",
|
||
# "high": "110838.9",
|
||
# "low": "109705.5",
|
||
# "baseVolume": "1882.10069442",
|
||
# "quoteVolume": "207325626.822922498"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
resultList = self.safe_list(data, 'list', [])
|
||
result = self.safe_dict(resultList, 0, {})
|
||
else:
|
||
response = await self.publicGetMarketStats(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "time": 1602832092060, # time
|
||
# "symbol": "BTC-USDT", # symbol
|
||
# "buy": "11328.9", # bestAsk
|
||
# "sell": "11329", # bestBid
|
||
# "changeRate": "-0.0055", # 24h change rate
|
||
# "changePrice": "-63.6", # 24h change price
|
||
# "high": "11610", # 24h highest price
|
||
# "low": "11200", # 24h lowest price
|
||
# "vol": "2282.70993217", # 24h volume,the aggregated trading volume in BTC
|
||
# "volValue": "25984946.157790431", # 24h total, the trading volume in quote currency of last 24 hours
|
||
# "last": "11328.9", # last price
|
||
# "averagePrice": "11360.66065903", # 24h average transaction price yesterday
|
||
# "takerFeeRate": "0.001", # Basic Taker Fee
|
||
# "makerFeeRate": "0.001", # Basic Maker Fee
|
||
# "takerCoefficient": "1", # Taker Fee Coefficient
|
||
# "makerCoefficient": "1" # Maker Fee Coefficient
|
||
# }
|
||
# }
|
||
#
|
||
result = self.safe_dict(response, 'data', {})
|
||
return self.parse_ticker(result, market)
|
||
|
||
async def fetch_mark_price(self, symbol: str, params={}) -> Ticker:
|
||
"""
|
||
fetches the mark price for a specific market
|
||
|
||
https://www.kucoin.com/docs/rest/margin-trading/margin-info/get-mark-price
|
||
|
||
:param str symbol: unified symbol of the market to fetch the ticker for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = await self.publicGetMarkPriceSymbolCurrent(self.extend(request, params))
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_ticker(data, market)
|
||
|
||
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
||
#
|
||
# [
|
||
# "1545904980", # Start time of the candle cycle
|
||
# "0.058", # opening price
|
||
# "0.049", # closing price
|
||
# "0.058", # highest price
|
||
# "0.049", # lowest price
|
||
# "0.018", # base volume
|
||
# "0.000945", # quote volume
|
||
# ]
|
||
#
|
||
return [
|
||
self.safe_timestamp(ohlcv, 0),
|
||
self.safe_number(ohlcv, 1),
|
||
self.safe_number(ohlcv, 3),
|
||
self.safe_number(ohlcv, 4),
|
||
self.safe_number(ohlcv, 2),
|
||
self.safe_number(ohlcv, 5),
|
||
]
|
||
|
||
async 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://docs.kucoin.com/#get-klines
|
||
https://www.kucoin.com/docs-new/rest/ua/get-klines
|
||
|
||
: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 boolean [params.uta]: set to True for the unified trading account(uta), defaults to False
|
||
: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
|
||
"""
|
||
await self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate')
|
||
if paginate:
|
||
return await self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 1500)
|
||
market = self.market(symbol)
|
||
marketId = market['id']
|
||
request: dict = {
|
||
'symbol': marketId,
|
||
}
|
||
duration = self.parse_timeframe(timeframe) * 1000
|
||
endAt = self.milliseconds() # required param
|
||
if since is not None:
|
||
request['startAt'] = self.parse_to_int(int(math.floor(since / 1000)))
|
||
if limit is None:
|
||
# https://docs.kucoin.com/#get-klines
|
||
# https://docs.kucoin.com/#details
|
||
# For each query, the system would return at most 1500 pieces of data.
|
||
# To obtain more data, please page the data by time.
|
||
limit = self.safe_integer(self.options, 'fetchOHLCVLimit', 1500)
|
||
endAt = self.sum(since, limit * duration)
|
||
elif limit is not None:
|
||
since = endAt - limit * duration
|
||
request['startAt'] = self.parse_to_int(int(math.floor(since / 1000)))
|
||
request['endAt'] = self.parse_to_int(int(math.floor(endAt / 1000)))
|
||
uta = None
|
||
uta, params = self.handle_option_and_params(params, 'fetchOHLCV', 'uta', False)
|
||
response = None
|
||
result = None
|
||
if uta:
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchOHLCV', market, params)
|
||
if type == 'spot':
|
||
request['tradeType'] = 'SPOT'
|
||
else:
|
||
request['tradeType'] = 'FUTURES'
|
||
request['interval'] = self.safe_string(self.timeframes, timeframe, timeframe)
|
||
response = await self.utaGetMarketKline(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "tradeType": "SPOT",
|
||
# "symbol": "BTC-USDT",
|
||
# "list": [
|
||
# ["1762240200","104581.4","104527.1","104620.1","104526.4","5.57665554","583263.661804122"],
|
||
# ["1762240140","104565.6","104581.3","104601.7","104511.3","6.48505114","677973.775916968"],
|
||
# ["1762240080","104621.5","104571.3","104704.7","104571.3","14.51713618","1519468.954060838"]
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
result = self.safe_list(data, 'list', [])
|
||
else:
|
||
request['type'] = self.safe_string(self.timeframes, timeframe, timeframe)
|
||
response = await self.publicGetMarketCandles(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code":"200000",
|
||
# "data":[
|
||
# ["1591517700","0.025078","0.025069","0.025084","0.025064","18.9883256","0.4761861079404"],
|
||
# ["1591516800","0.025089","0.025079","0.025089","0.02506","99.4716622","2.494143499081"],
|
||
# ["1591515900","0.025079","0.02509","0.025091","0.025068","59.83701271","1.50060885172798"],
|
||
# ]
|
||
# }
|
||
#
|
||
result = self.safe_list(response, 'data', [])
|
||
return self.parse_ohlcvs(result, market, timeframe, since, limit)
|
||
|
||
async def create_deposit_address(self, code: str, params={}) -> DepositAddress:
|
||
"""
|
||
|
||
https://www.kucoin.com/docs/rest/funding/deposit/create-deposit-address-v3-
|
||
|
||
create a currency 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
|
||
:param str [params.network]: the blockchain network name
|
||
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
}
|
||
networkCode = None
|
||
networkCode, params = self.handle_network_code_and_params(params)
|
||
if networkCode is not None:
|
||
request['chain'] = self.network_code_to_id(networkCode) # docs mention "chain-name", but seems "chain-id" is used, like in "fetchDepositAddress"
|
||
response = await self.privatePostDepositAddressCreate(self.extend(request, params))
|
||
# {"code":"260000","msg":"Deposit address already exists."}
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "address": "0x2336d1834faab10b2dac44e468f2627138417431",
|
||
# "memo": null,
|
||
# "chainId": "bsc",
|
||
# "to": "MAIN",
|
||
# "expirationDate": 0,
|
||
# "currency": "BNB",
|
||
# "chainName": "BEP20"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_deposit_address(data, currency)
|
||
|
||
async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
||
"""
|
||
fetch the deposit address for a currency associated with self account
|
||
|
||
https://docs.kucoin.com/#get-deposit-addresses-v2
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.network]: the blockchain network name
|
||
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
# for USDT - OMNI, ERC20, TRC20, default is ERC20
|
||
# for BTC - Native, Segwit, TRC20, the parameters are bech32, btc, trx, default is Native
|
||
# 'chain': 'ERC20', # optional
|
||
}
|
||
networkCode = None
|
||
networkCode, params = self.handle_network_code_and_params(params)
|
||
if networkCode is not None:
|
||
request['chain'] = self.network_code_to_id(networkCode).lower()
|
||
version = self.options['versions']['private']['GET']['deposit-addresses']
|
||
self.options['versions']['private']['GET']['deposit-addresses'] = 'v1'
|
||
response = await self.privateGetDepositAddresses(self.extend(request, params))
|
||
# BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}}
|
||
# BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}}
|
||
self.options['versions']['private']['GET']['deposit-addresses'] = version
|
||
data = self.safe_value(response, 'data')
|
||
if data is None:
|
||
raise ExchangeError(self.id + ' fetchDepositAddress() returned an empty response, you might try to run createDepositAddress() first and try again')
|
||
return self.parse_deposit_address(data, currency)
|
||
|
||
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
|
||
address = self.safe_string(depositAddress, 'address')
|
||
# BCH/BSV is returned with a "bitcoincash:" prefix, which we cut off here and only keep the address
|
||
if address is not None:
|
||
address = address.replace('bitcoincash:', '')
|
||
code = None
|
||
if currency is not None:
|
||
code = self.safe_currency_code(currency['id'])
|
||
if code != 'NIM':
|
||
# contains spaces
|
||
self.check_address(address)
|
||
return {
|
||
'info': depositAddress,
|
||
'currency': code,
|
||
'network': self.network_id_to_code(self.safe_string(depositAddress, 'chainId')),
|
||
'address': address,
|
||
'tag': self.safe_string(depositAddress, 'memo'),
|
||
}
|
||
|
||
async def fetch_deposit_addresses_by_network(self, code: str, params={}) -> List[DepositAddress]:
|
||
"""
|
||
|
||
https://docs.kucoin.com/#get-deposit-addresses-v2
|
||
|
||
fetch the deposit address for a currency associated with self account
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an array of `address structures <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
}
|
||
version = self.options['versions']['private']['GET']['deposit-addresses']
|
||
self.options['versions']['private']['GET']['deposit-addresses'] = 'v2'
|
||
response = await self.privateGetDepositAddresses(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": [
|
||
# {
|
||
# "address": "fr1qvus7d4d5fgxj5e7zvqe6yhxd7txm95h2and69r",
|
||
# "memo": "",
|
||
# "chain": "BTC-Segwit",
|
||
# "contractAddress": ""
|
||
# },
|
||
# {"address":"37icNMEWbiF8ZkwUMxmfzMxi2A1MQ44bMn","memo":"","chain":"BTC","contractAddress":""},
|
||
# {"address":"Deposit temporarily blocked","memo":"","chain":"TRC20","contractAddress":""}
|
||
# ]
|
||
# }
|
||
#
|
||
self.options['versions']['private']['GET']['deposit-addresses'] = version
|
||
chains = self.safe_list(response, 'data', [])
|
||
parsed = self.parse_deposit_addresses(chains, [currency['code']], False, {
|
||
'currency': currency['code'],
|
||
})
|
||
return self.index_by(parsed, 'network')
|
||
|
||
async 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://www.kucoin.com/docs/rest/spot-trading/market-data/get-part-order-book-aggregated-
|
||
https://www.kucoin.com/docs/rest/spot-trading/market-data/get-full-order-book-aggregated-
|
||
https://www.kucoin.com/docs-new/rest/ua/get-orderbook
|
||
|
||
:param str symbol: unified symbol of the market to fetch the order book for
|
||
:param int [limit]: the maximum amount of order book entries to return
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.uta]: set to True for the unified trading account(uta), defaults to False
|
||
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
level = self.safe_integer(params, 'level', 2)
|
||
request: dict = {'symbol': market['id']}
|
||
isAuthenticated = self.check_required_credentials(False)
|
||
uta = None
|
||
uta, params = self.handle_option_and_params(params, 'fetchOrderBook', 'uta', False)
|
||
response = None
|
||
if uta:
|
||
if limit is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrderBook() requires a limit argument for uta, either 20, 50, 100 or FULL')
|
||
request['limit'] = limit
|
||
request['symbol'] = market['id']
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchOrderBook', market, params)
|
||
if type == 'spot':
|
||
request['tradeType'] = 'SPOT'
|
||
else:
|
||
request['tradeType'] = 'FUTURES'
|
||
response = await self.utaGetMarketOrderbook(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "tradeType": "SPOT",
|
||
# "symbol": "BTC-USDT",
|
||
# "sequence": "23136002402",
|
||
# "bids": [
|
||
# ["104700","10.25940068"],
|
||
# ["104698.9","0.00057076"],
|
||
# ],
|
||
# "asks": [
|
||
# ["104700.1","1.4082106"],
|
||
# ["104700.5","0.02866269"],
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
elif not isAuthenticated or limit is not None:
|
||
if level == 2:
|
||
request['level'] = level
|
||
if limit is not None:
|
||
if (limit == 20) or (limit == 100):
|
||
request['limit'] = limit
|
||
else:
|
||
raise ExchangeError(self.id + ' fetchOrderBook() limit argument must be 20 or 100')
|
||
request['limit'] = limit if limit else 100
|
||
response = await self.publicGetMarketOrderbookLevelLevelLimit(self.extend(request, params))
|
||
else:
|
||
response = await self.privateGetMarketOrderbookLevel2(self.extend(request, params))
|
||
#
|
||
# public(v1) market/orderbook/level2_20 and market/orderbook/level2_100
|
||
#
|
||
# {
|
||
# "sequence": "3262786978",
|
||
# "time": 1550653727731,
|
||
# "bids": [
|
||
# ["6500.12", "0.45054140"],
|
||
# ["6500.11", "0.45054140"],
|
||
# ],
|
||
# "asks": [
|
||
# ["6500.16", "0.57753524"],
|
||
# ["6500.15", "0.57753524"],
|
||
# ]
|
||
# }
|
||
#
|
||
# private(v3) market/orderbook/level2
|
||
#
|
||
# {
|
||
# "sequence": "3262786978",
|
||
# "time": 1550653727731,
|
||
# "bids": [
|
||
# ["6500.12", "0.45054140"],
|
||
# ["6500.11", "0.45054140"],
|
||
# ],
|
||
# "asks": [
|
||
# ["6500.16", "0.57753524"],
|
||
# ["6500.15", "0.57753524"],
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
timestamp = self.safe_integer(data, 'time')
|
||
orderbook = self.parse_order_book(data, market['symbol'], timestamp, 'bids', 'asks', level - 2, level - 1)
|
||
orderbook['nonce'] = self.safe_integer(data, 'sequence')
|
||
return orderbook
|
||
|
||
def handle_trigger_prices(self, params):
|
||
triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
|
||
stopLossPrice = self.safe_value(params, 'stopLossPrice')
|
||
takeProfitPrice = self.safe_value(params, 'takeProfitPrice')
|
||
isStopLoss = stopLossPrice is not None
|
||
isTakeProfit = takeProfitPrice is not None
|
||
if (isStopLoss and isTakeProfit) or (triggerPrice and stopLossPrice) or (triggerPrice and isTakeProfit):
|
||
raise ExchangeError(self.id + ' createOrder() - you should use either triggerPrice or stopLossPrice or takeProfitPrice')
|
||
return [triggerPrice, stopLossPrice, takeProfitPrice]
|
||
|
||
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
Create an order on the exchange
|
||
|
||
https://docs.kucoin.com/spot#place-a-new-order
|
||
https://docs.kucoin.com/spot#place-a-new-order-2
|
||
https://docs.kucoin.com/spot#place-a-margin-order
|
||
https://docs.kucoin.com/spot-hf/#place-hf-order
|
||
https://www.kucoin.com/docs/rest/spot-trading/orders/place-order-test
|
||
https://www.kucoin.com/docs/rest/margin-trading/orders/place-margin-order-test
|
||
https://www.kucoin.com/docs/rest/spot-trading/spot-hf-trade-pro-account/sync-place-hf-order
|
||
|
||
:param str symbol: Unified CCXT market symbol
|
||
:param str type: 'limit' or 'market'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: the amount of currency to trade
|
||
: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 float [params.triggerPrice]: The price at which a trigger order is triggered at
|
||
:param str [params.marginMode]: 'cross', # cross(cross mode) and isolated(isolated mode), set to cross by default, the isolated mode will be released soon, stay tuned
|
||
:param str [params.timeInForce]: GTC, GTT, IOC, or FOK, default is GTC, limit orders only
|
||
:param str [params.postOnly]: Post only flag, invalid when timeInForce is IOC or FOK
|
||
|
||
EXCHANGE SPECIFIC PARAMETERS
|
||
:param str [params.clientOid]: client order id, defaults to uuid if not passed
|
||
:param str [params.remark]: remark for the order, length cannot exceed 100 utf8 characters
|
||
:param str [params.tradeType]: 'TRADE', # TRADE, MARGIN_TRADE # not used with margin orders
|
||
limit orders ---------------------------------------------------
|
||
:param float [params.cancelAfter]: long, # cancel after n seconds, requires timeInForce to be GTT
|
||
:param bool [params.hidden]: False, # Order will not be displayed in the order book
|
||
:param bool [params.iceberg]: False, # Only a portion of the order is displayed in the order book
|
||
:param str [params.visibleSize]: self.amount_to_precision(symbol, visibleSize), # The maximum visible size of an iceberg order
|
||
market orders --------------------------------------------------
|
||
:param str [params.funds]: # Amount of quote currency to use
|
||
stop orders ----------------------------------------------------
|
||
:param str [params.stop]: Either loss or entry, the default is loss. Requires triggerPrice to be defined
|
||
margin orders --------------------------------------------------
|
||
:param float [params.leverage]: Leverage size of the order
|
||
:param str [params.stp]: '', # self trade prevention, CN, CO, CB or DC
|
||
:param bool [params.autoBorrow]: False, # The system will first borrow you funds at the optimal interest rate and then place an order for you
|
||
:param bool [params.hf]: False, # True for hf order
|
||
:param bool [params.test]: set to True to test an order, no order will be created but the request will be validated
|
||
:param bool [params.sync]: set to True to use the hf sync call
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
testOrder = self.safe_bool(params, 'test', False)
|
||
params = self.omit(params, 'test')
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
useSync = False
|
||
useSync, params = self.handle_option_and_params(params, 'createOrder', 'sync', False)
|
||
triggerPrice, stopLossPrice, takeProfitPrice = self.handle_trigger_prices(params)
|
||
tradeType = self.safe_string(params, 'tradeType') # keep it for backward compatibility
|
||
isTriggerOrder = (triggerPrice or stopLossPrice or takeProfitPrice)
|
||
marginResult = self.handle_margin_mode_and_params('createOrder', params)
|
||
marginMode = self.safe_string(marginResult, 0)
|
||
isMarginOrder = tradeType == 'MARGIN_TRADE' or marginMode is not None
|
||
# don't omit anything before calling createOrderRequest
|
||
orderRequest = self.create_order_request(symbol, type, side, amount, price, params)
|
||
response = None
|
||
if testOrder:
|
||
if isMarginOrder:
|
||
response = await self.privatePostMarginOrderTest(orderRequest)
|
||
elif hf:
|
||
response = await self.privatePostHfOrdersTest(orderRequest)
|
||
else:
|
||
response = await self.privatePostOrdersTest(orderRequest)
|
||
elif isTriggerOrder:
|
||
response = await self.privatePostStopOrder(orderRequest)
|
||
elif isMarginOrder:
|
||
response = await self.privatePostMarginOrder(orderRequest)
|
||
elif useSync:
|
||
response = await self.privatePostHfOrdersSync(orderRequest)
|
||
elif hf:
|
||
response = await self.privatePostHfOrders(orderRequest)
|
||
else:
|
||
response = await self.privatePostOrders(orderRequest)
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "orderId": "5bd6e9286d99522a52e458de"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_order(data, market)
|
||
|
||
async def create_market_order_with_cost(self, symbol: str, side: OrderSide, cost: float, params={}):
|
||
"""
|
||
create a market order by providing the symbol, side and cost
|
||
|
||
https://www.kucoin.com/docs/rest/spot-trading/orders/place-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str side: 'buy' or 'sell'
|
||
:param float cost: how much you want to trade in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
req = {
|
||
'cost': cost,
|
||
}
|
||
return await self.create_order(symbol, 'market', side, cost, None, self.extend(req, params))
|
||
|
||
async def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
|
||
"""
|
||
create a market buy order by providing the symbol and cost
|
||
|
||
https://www.kucoin.com/docs/rest/spot-trading/orders/place-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param float cost: how much you want to trade in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
return await self.create_market_order_with_cost(symbol, 'buy', cost, params)
|
||
|
||
async def create_market_sell_order_with_cost(self, symbol: str, cost: float, params={}):
|
||
"""
|
||
create a market sell order by providing the symbol and cost
|
||
|
||
https://www.kucoin.com/docs/rest/spot-trading/orders/place-order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param float cost: how much you want to trade in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
return await self.create_market_order_with_cost(symbol, 'sell', cost, params)
|
||
|
||
async def create_orders(self, orders: List[OrderRequest], params={}):
|
||
"""
|
||
create a list of trade orders
|
||
|
||
https://www.kucoin.com/docs/rest/spot-trading/orders/place-multiple-orders
|
||
https://www.kucoin.com/docs/rest/spot-trading/spot-hf-trade-pro-account/place-multiple-hf-orders
|
||
https://www.kucoin.com/docs/rest/spot-trading/spot-hf-trade-pro-account/sync-place-multiple-hf-orders
|
||
|
||
: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
|
||
:param bool [params.hf]: False, # True for hf orders
|
||
:param bool [params.sync]: False, # True to use the hf sync call
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
ordersRequests = []
|
||
symbol = None
|
||
for i in range(0, len(orders)):
|
||
rawOrder = orders[i]
|
||
marketId = self.safe_string(rawOrder, 'symbol')
|
||
if symbol is None:
|
||
symbol = marketId
|
||
else:
|
||
if symbol != marketId:
|
||
raise BadRequest(self.id + ' createOrders() requires all orders to have the same symbol')
|
||
type = self.safe_string(rawOrder, 'type')
|
||
if type != 'limit':
|
||
raise BadRequest(self.id + ' createOrders() only supports limit orders')
|
||
side = self.safe_string(rawOrder, 'side')
|
||
amount = self.safe_value(rawOrder, 'amount')
|
||
price = self.safe_value(rawOrder, 'price')
|
||
orderParams = self.safe_value(rawOrder, 'params', {})
|
||
orderRequest = self.create_order_request(marketId, type, side, amount, price, orderParams)
|
||
ordersRequests.append(orderRequest)
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'orderList': ordersRequests,
|
||
}
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
useSync = False
|
||
useSync, params = self.handle_option_and_params(params, 'createOrders', 'sync', False)
|
||
response = None
|
||
if useSync:
|
||
response = await self.privatePostHfOrdersMultiSync(self.extend(request, params))
|
||
elif hf:
|
||
response = await self.privatePostHfOrdersMulti(self.extend(request, params))
|
||
else:
|
||
response = await self.privatePostOrdersMulti(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "data": [
|
||
# {
|
||
# "symbol": "LTC-USDT",
|
||
# "type": "limit",
|
||
# "side": "sell",
|
||
# "price": "90",
|
||
# "size": "0.1",
|
||
# "funds": null,
|
||
# "stp": "",
|
||
# "stop": "",
|
||
# "stopPrice": null,
|
||
# "timeInForce": "GTC",
|
||
# "cancelAfter": 0,
|
||
# "postOnly": False,
|
||
# "hidden": False,
|
||
# "iceberge": False,
|
||
# "iceberg": False,
|
||
# "visibleSize": null,
|
||
# "channel": "API",
|
||
# "id": "6539148443fcf500079d15e5",
|
||
# "status": "success",
|
||
# "failMsg": null,
|
||
# "clientOid": "5c4c5398-8ab2-4b4e-af8a-e2d90ad2488f"
|
||
# },
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
data = self.safe_list(data, 'data', [])
|
||
return self.parse_orders(data)
|
||
|
||
def market_order_amount_to_precision(self, symbol: str, amount):
|
||
market = self.market(symbol)
|
||
result = self.decimal_to_precision(amount, TRUNCATE, market['info']['quoteIncrement'], self.precisionMode, self.paddingMode)
|
||
if result == '0':
|
||
raise InvalidOrder(self.id + ' amount of ' + market['symbol'] + ' must be greater than minimum amount precision of ' + self.number_to_string(market['precision']['amount']))
|
||
return result
|
||
|
||
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
market = self.market(symbol)
|
||
# required param, cannot be used twice
|
||
clientOrderId = self.safe_string_2(params, 'clientOid', 'clientOrderId', self.uuid())
|
||
params = self.omit(params, ['clientOid', 'clientOrderId'])
|
||
request: dict = {
|
||
'clientOid': clientOrderId,
|
||
'side': side,
|
||
'symbol': market['id'],
|
||
'type': type, # limit or market
|
||
}
|
||
quoteAmount = self.safe_number_2(params, 'cost', 'funds')
|
||
amountString = None
|
||
costString = None
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('createOrder', params)
|
||
if type == 'market':
|
||
if quoteAmount is not None:
|
||
params = self.omit(params, ['cost', 'funds'])
|
||
# kucoin uses base precision even for quote values
|
||
costString = self.market_order_amount_to_precision(symbol, quoteAmount)
|
||
request['funds'] = costString
|
||
else:
|
||
amountString = self.amount_to_precision(symbol, amount)
|
||
request['size'] = self.amount_to_precision(symbol, amount)
|
||
else:
|
||
amountString = self.amount_to_precision(symbol, amount)
|
||
request['size'] = amountString
|
||
request['price'] = self.price_to_precision(symbol, price)
|
||
tradeType = self.safe_string(params, 'tradeType') # keep it for backward compatibility
|
||
triggerPrice, stopLossPrice, takeProfitPrice = self.handle_trigger_prices(params)
|
||
isTriggerOrder = (triggerPrice or stopLossPrice or takeProfitPrice)
|
||
isMarginOrder = tradeType == 'MARGIN_TRADE' or marginMode is not None
|
||
params = self.omit(params, ['stopLossPrice', 'takeProfitPrice', 'triggerPrice', 'stopPrice'])
|
||
if isTriggerOrder:
|
||
if triggerPrice:
|
||
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
|
||
elif stopLossPrice or takeProfitPrice:
|
||
if stopLossPrice:
|
||
request['stop'] = 'entry' if (side == 'buy') else 'loss'
|
||
request['stopPrice'] = self.price_to_precision(symbol, stopLossPrice)
|
||
else:
|
||
request['stop'] = 'loss' if (side == 'buy') else 'entry'
|
||
request['stopPrice'] = self.price_to_precision(symbol, takeProfitPrice)
|
||
if marginMode == 'isolated':
|
||
raise BadRequest(self.id + ' createOrder does not support isolated margin for stop orders')
|
||
elif marginMode == 'cross':
|
||
request['tradeType'] = self.options['marginModes'][marginMode]
|
||
elif isMarginOrder:
|
||
if marginMode == 'isolated':
|
||
request['marginModel'] = 'isolated'
|
||
postOnly = None
|
||
postOnly, params = self.handle_post_only(type == 'market', False, params)
|
||
if postOnly:
|
||
request['postOnly'] = True
|
||
return self.extend(request, params)
|
||
|
||
async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
||
"""
|
||
edit an order, kucoin currently only supports the modification of HF orders
|
||
|
||
https://docs.kucoin.com/spot-hf/#modify-order
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: not used
|
||
:param str side: not used
|
||
:param float amount: how much of the currency you want to trade in units of the base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.clientOrderId]: client order id, defaults to id if not passed
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
clientOrderId = self.safe_string_2(params, 'clientOid', 'clientOrderId')
|
||
if clientOrderId is not None:
|
||
request['clientOid'] = clientOrderId
|
||
else:
|
||
request['orderId'] = id
|
||
if amount is not None:
|
||
request['newSize'] = self.amount_to_precision(symbol, amount)
|
||
if price is not None:
|
||
request['newPrice'] = self.price_to_precision(symbol, price)
|
||
response = await self.privatePostHfOrdersAlter(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code":"200000",
|
||
# "data":{
|
||
# "newOrderId":"6478d7a6c883280001e92d8b"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_order(data, market)
|
||
|
||
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
cancels an open order
|
||
|
||
https://docs.kucoin.com/spot#cancel-an-order
|
||
https://docs.kucoin.com/spot#cancel-an-order-2
|
||
https://docs.kucoin.com/spot#cancel-single-order-by-clientoid
|
||
https://docs.kucoin.com/spot#cancel-single-order-by-clientoid-2
|
||
https://docs.kucoin.com/spot-hf/#cancel-orders-by-orderid
|
||
https://docs.kucoin.com/spot-hf/#cancel-order-by-clientoid
|
||
https://www.kucoin.com/docs/rest/spot-trading/spot-hf-trade-pro-account/sync-cancel-hf-order-by-orderid
|
||
https://www.kucoin.com/docs/rest/spot-trading/spot-hf-trade-pro-account/sync-cancel-hf-order-by-clientoid
|
||
|
||
: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
|
||
:param bool [params.trigger]: True if cancelling a stop order
|
||
:param bool [params.hf]: False, # True for hf order
|
||
:param bool [params.sync]: False, # True to use the hf sync call
|
||
:returns: Response from the exchange
|
||
"""
|
||
await self.load_markets()
|
||
request: dict = {}
|
||
clientOrderId = self.safe_string_2(params, 'clientOid', 'clientOrderId')
|
||
trigger = self.safe_bool_2(params, 'stop', 'trigger', False)
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
useSync = False
|
||
useSync, params = self.handle_option_and_params(params, 'cancelOrder', 'sync', False)
|
||
if hf or useSync:
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol parameter for hf orders')
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
response = None
|
||
params = self.omit(params, ['clientOid', 'clientOrderId', 'stop', 'trigger'])
|
||
if clientOrderId is not None:
|
||
request['clientOid'] = clientOrderId
|
||
if trigger:
|
||
response = await self.privateDeleteStopOrderCancelOrderByClientOid(self.extend(request, params))
|
||
#
|
||
# {
|
||
# code: '200000',
|
||
# data: {
|
||
# cancelledOrderId: 'vs8lgpiuao41iaft003khbbk',
|
||
# clientOid: '123456'
|
||
# }
|
||
# }
|
||
#
|
||
elif useSync:
|
||
response = await self.privateDeleteHfOrdersSyncClientOrderClientOid(self.extend(request, params))
|
||
elif hf:
|
||
response = await self.privateDeleteHfOrdersClientOrderClientOid(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "clientOid": "6d539dc614db3"
|
||
# }
|
||
# }
|
||
#
|
||
else:
|
||
response = await self.privateDeleteOrderClientOrderClientOid(self.extend(request, params))
|
||
#
|
||
# {
|
||
# code: '200000',
|
||
# data: {
|
||
# cancelledOrderId: '665e580f6660500007aba341',
|
||
# clientOid: '1234567',
|
||
# cancelledOcoOrderIds: null
|
||
# }
|
||
# }
|
||
#
|
||
response = self.safe_dict(response, 'data')
|
||
return self.parse_order(response)
|
||
else:
|
||
request['orderId'] = id
|
||
if trigger:
|
||
response = await self.privateDeleteStopOrderOrderId(self.extend(request, params))
|
||
#
|
||
# {
|
||
# code: '200000',
|
||
# data: {cancelledOrderIds: ['vs8lgpiuaco91qk8003vebu9']}
|
||
# }
|
||
#
|
||
elif useSync:
|
||
response = await self.privateDeleteHfOrdersSyncOrderId(self.extend(request, params))
|
||
elif hf:
|
||
response = await self.privateDeleteHfOrdersOrderId(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "orderId": "630625dbd9180300014c8d52"
|
||
# }
|
||
# }
|
||
#
|
||
response = self.safe_dict(response, 'data')
|
||
return self.parse_order(response)
|
||
else:
|
||
response = await self.privateDeleteOrdersOrderId(self.extend(request, params))
|
||
#
|
||
# {
|
||
# code: '200000',
|
||
# data: {cancelledOrderIds: ['665e4fbe28051a0007245c41']}
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data')
|
||
orderIds = self.safe_list(data, 'cancelledOrderIds', [])
|
||
orderId = self.safe_string(orderIds, 0)
|
||
return self.safe_order({
|
||
'info': data,
|
||
'id': orderId,
|
||
})
|
||
|
||
async def cancel_all_orders(self, symbol: Str = None, params={}):
|
||
"""
|
||
cancel all open orders
|
||
|
||
https://docs.kucoin.com/spot#cancel-all-orders
|
||
https://docs.kucoin.com/spot#cancel-orders
|
||
https://docs.kucoin.com/spot-hf/#cancel-all-hf-orders-by-symbol
|
||
|
||
:param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param bool [params.trigger]: *invalid for isolated margin* True if cancelling all stop orders
|
||
:param str [params.marginMode]: 'cross' or 'isolated'
|
||
:param str [params.orderIds]: *stop orders only* Comma seperated order IDs
|
||
:param bool [params.hf]: False, # True for hf order
|
||
:returns: Response from the exchange
|
||
"""
|
||
await self.load_markets()
|
||
request: dict = {}
|
||
trigger = self.safe_bool_2(params, 'trigger', 'stop', False)
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
params = self.omit(params, 'stop')
|
||
marginMode, query = self.handle_margin_mode_and_params('cancelAllOrders', params)
|
||
if symbol is not None:
|
||
request['symbol'] = self.market_id(symbol)
|
||
if marginMode is not None:
|
||
request['tradeType'] = self.options['marginModes'][marginMode]
|
||
if marginMode == 'isolated' and trigger:
|
||
raise BadRequest(self.id + ' cancelAllOrders does not support isolated margin for stop orders')
|
||
response = None
|
||
if trigger:
|
||
response = await self.privateDeleteStopOrderCancel(self.extend(request, query))
|
||
elif hf:
|
||
if symbol is None:
|
||
response = await self.privateDeleteHfOrdersCancelAll(self.extend(request, query))
|
||
else:
|
||
response = await self.privateDeleteHfOrders(self.extend(request, query))
|
||
else:
|
||
response = await self.privateDeleteOrders(self.extend(request, query))
|
||
return [self.safe_order({'info': response})]
|
||
|
||
async def fetch_orders_by_status(self, status, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch a list of orders
|
||
|
||
https://docs.kucoin.com/spot#list-orders
|
||
https://docs.kucoin.com/spot#list-stop-orders
|
||
https://docs.kucoin.com/spot-hf/#obtain-list-of-active-hf-orders
|
||
https://docs.kucoin.com/spot-hf/#obtain-list-of-filled-hf-orders
|
||
|
||
:param str status: *not used for stop orders* 'open' or 'closed'
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: timestamp in ms of the earliest order
|
||
:param int [limit]: max number of orders to return
|
||
:param dict [params]: exchange specific params
|
||
:param int [params.until]: end time in ms
|
||
:param str [params.side]: buy or sell
|
||
:param str [params.type]: limit, market, limit_stop or market_stop
|
||
:param str [params.tradeType]: TRADE for spot trading, MARGIN_TRADE for Margin Trading
|
||
:param int [params.currentPage]: *trigger orders only* current page
|
||
:param str [params.orderIds]: *trigger orders only* comma seperated order ID list
|
||
:param bool [params.trigger]: True if fetching a trigger order
|
||
:param bool [params.hf]: False, # True for hf order
|
||
:returns: An `array of order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
lowercaseStatus = status.lower()
|
||
until = self.safe_integer(params, 'until')
|
||
trigger = self.safe_bool_2(params, 'stop', 'trigger', False)
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
if hf and (symbol is None):
|
||
raise ArgumentsRequired(self.id + ' fetchOrdersByStatus() requires a symbol parameter for hf orders')
|
||
params = self.omit(params, ['stop', 'trigger', 'till', 'until'])
|
||
marginMode, query = self.handle_margin_mode_and_params('fetchOrdersByStatus', params)
|
||
if lowercaseStatus == 'open':
|
||
lowercaseStatus = 'active'
|
||
elif lowercaseStatus == 'closed':
|
||
lowercaseStatus = 'done'
|
||
request: dict = {
|
||
'status': lowercaseStatus,
|
||
}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
if since is not None:
|
||
request['startAt'] = since
|
||
if limit is not None:
|
||
request['pageSize'] = limit
|
||
if until:
|
||
request['endAt'] = until
|
||
request['tradeType'] = self.safe_string(self.options['marginModes'], marginMode, 'TRADE')
|
||
response = None
|
||
if trigger:
|
||
response = await self.privateGetStopOrder(self.extend(request, query))
|
||
elif hf:
|
||
if lowercaseStatus == 'active':
|
||
response = await self.privateGetHfOrdersActive(self.extend(request, query))
|
||
elif lowercaseStatus == 'done':
|
||
response = await self.privateGetHfOrdersDone(self.extend(request, query))
|
||
else:
|
||
response = await self.privateGetOrders(self.extend(request, query))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "currentPage": 1,
|
||
# "pageSize": 1,
|
||
# "totalNum": 153408,
|
||
# "totalPage": 153408,
|
||
# "items": [
|
||
# {
|
||
# "id": "5c35c02703aa673ceec2a168", #orderid
|
||
# "symbol": "BTC-USDT", #symbol
|
||
# "opType": "DEAL", # operation type,deal is pending order,cancel is cancel order
|
||
# "type": "limit", # order type,e.g. limit,markrt,stop_limit.
|
||
# "side": "buy", # transaction direction,include buy and sell
|
||
# "price": "10", # order price
|
||
# "size": "2", # order quantity
|
||
# "funds": "0", # order funds
|
||
# "dealFunds": "0.166", # deal funds
|
||
# "dealSize": "2", # deal quantity
|
||
# "fee": "0", # fee
|
||
# "feeCurrency": "USDT", # charge fee currency
|
||
# "stp": "", # self trade prevention,include CN,CO,DC,CB
|
||
# "stop": "", # stop type
|
||
# "stopTriggered": False, # stop order is triggered
|
||
# "stopPrice": "0", # stop price
|
||
# "timeInForce": "GTC", # time InForce,include GTC,GTT,IOC,FOK
|
||
# "postOnly": False, # postOnly
|
||
# "hidden": False, # hidden order
|
||
# "iceberg": False, # iceberg order
|
||
# "visibleSize": "0", # display quantity for iceberg order
|
||
# "cancelAfter": 0, # cancel orders time,requires timeInForce to be GTT
|
||
# "channel": "IOS", # order source
|
||
# "clientOid": "", # user-entered order unique mark
|
||
# "remark": "", # remark
|
||
# "tags": "", # tag order source
|
||
# "isActive": False, # status before unfilled or uncancelled
|
||
# "cancelExist": False, # order cancellation transaction record
|
||
# "createdAt": 1547026471000 # time
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
listData = self.safe_list(response, 'data')
|
||
if listData is not None:
|
||
return self.parse_orders(listData, market, since, limit)
|
||
responseData = self.safe_dict(response, 'data', {})
|
||
orders = self.safe_list(responseData, 'items', [])
|
||
return self.parse_orders(orders, market, since, limit)
|
||
|
||
async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple closed orders made by the user
|
||
|
||
https://docs.kucoin.com/spot#list-orders
|
||
https://docs.kucoin.com/spot#list-stop-orders
|
||
https://docs.kucoin.com/spot-hf/#obtain-list-of-active-hf-orders
|
||
https://docs.kucoin.com/spot-hf/#obtain-list-of-filled-hf-orders
|
||
|
||
:param str symbol: unified market symbol of the market orders were made in
|
||
:param int [since]: the earliest time in ms to fetch orders for
|
||
:param int [limit]: the maximum number of order structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: end time in ms
|
||
:param str [params.side]: buy or sell
|
||
:param str [params.type]: limit, market, limit_stop or market_stop
|
||
:param str [params.tradeType]: TRADE for spot trading, MARGIN_TRADE for Margin Trading
|
||
:param bool [params.trigger]: True if fetching a trigger order
|
||
:param bool [params.hf]: False, # True for hf order
|
||
: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>`
|
||
"""
|
||
await self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchClosedOrders', 'paginate')
|
||
if paginate:
|
||
return await self.fetch_paginated_call_dynamic('fetchClosedOrders', symbol, since, limit, params)
|
||
return await self.fetch_orders_by_status('done', symbol, since, limit, params)
|
||
|
||
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetch all unfilled currently open orders
|
||
|
||
https://docs.kucoin.com/spot#list-orders
|
||
https://docs.kucoin.com/spot#list-stop-orders
|
||
https://docs.kucoin.com/spot-hf/#obtain-list-of-active-hf-orders
|
||
https://docs.kucoin.com/spot-hf/#obtain-list-of-filled-hf-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 orders structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: end time in ms
|
||
:param bool [params.trigger]: True if fetching trigger orders
|
||
:param str [params.side]: buy or sell
|
||
:param str [params.type]: limit, market, limit_stop or market_stop
|
||
:param str [params.tradeType]: TRADE for spot trading, MARGIN_TRADE for Margin Trading
|
||
:param int [params.currentPage]: *trigger orders only* current page
|
||
:param str [params.orderIds]: *trigger orders only* comma seperated order ID list
|
||
:param bool [params.hf]: False, # True for hf order
|
||
: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>`
|
||
"""
|
||
await self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'paginate')
|
||
if paginate:
|
||
return await self.fetch_paginated_call_dynamic('fetchOpenOrders', symbol, since, limit, params)
|
||
return await self.fetch_orders_by_status('active', symbol, since, limit, params)
|
||
|
||
async def fetch_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
fetch an order
|
||
|
||
https://docs.kucoin.com/spot#get-an-order
|
||
https://docs.kucoin.com/spot#get-single-active-order-by-clientoid
|
||
https://docs.kucoin.com/spot#get-single-order-info
|
||
https://docs.kucoin.com/spot#get-single-order-by-clientoid
|
||
https://docs.kucoin.com/spot-hf/#details-of-a-single-hf-order
|
||
https://docs.kucoin.com/spot-hf/#obtain-details-of-a-single-hf-order-using-clientoid
|
||
|
||
:param str id: Order id
|
||
:param str symbol: not sent to exchange except for trigger orders with clientOid, but used internally by CCXT to filter
|
||
:param dict [params]: exchange specific parameters
|
||
:param bool [params.trigger]: True if fetching a trigger order
|
||
:param bool [params.hf]: False, # True for hf order
|
||
:param bool [params.clientOid]: unique order id created by users to identify their orders
|
||
:returns: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
request: dict = {}
|
||
clientOrderId = self.safe_string_2(params, 'clientOid', 'clientOrderId')
|
||
trigger = self.safe_bool_2(params, 'stop', 'trigger', False)
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
if hf:
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol parameter for hf orders')
|
||
request['symbol'] = market['id']
|
||
params = self.omit(params, ['stop', 'clientOid', 'clientOrderId', 'trigger'])
|
||
response = None
|
||
if clientOrderId is not None:
|
||
request['clientOid'] = clientOrderId
|
||
if trigger:
|
||
if symbol is not None:
|
||
request['symbol'] = market['id']
|
||
response = await self.privateGetStopOrderQueryOrderByClientOid(self.extend(request, params))
|
||
elif hf:
|
||
response = await self.privateGetHfOrdersClientOrderClientOid(self.extend(request, params))
|
||
else:
|
||
response = await self.privateGetOrderClientOrderClientOid(self.extend(request, params))
|
||
else:
|
||
# a special case for None ids
|
||
# otherwise a wrong endpoint for all orders will be triggered
|
||
# https://github.com/ccxt/ccxt/issues/7234
|
||
if id is None:
|
||
raise InvalidOrder(self.id + ' fetchOrder() requires an order id')
|
||
request['orderId'] = id
|
||
if trigger:
|
||
response = await self.privateGetStopOrderOrderId(self.extend(request, params))
|
||
elif hf:
|
||
response = await self.privateGetHfOrdersOrderId(self.extend(request, params))
|
||
else:
|
||
response = await self.privateGetOrdersOrderId(self.extend(request, params))
|
||
responseData = self.safe_dict(response, 'data', {})
|
||
if isinstance(responseData, list):
|
||
responseData = self.safe_value(responseData, 0)
|
||
return self.parse_order(responseData, market)
|
||
|
||
def parse_order(self, order: dict, market: Market = None) -> Order:
|
||
#
|
||
# createOrder
|
||
#
|
||
# {
|
||
# "orderId": "63c97e47d686c5000159a656"
|
||
# }
|
||
#
|
||
# cancelOrder
|
||
#
|
||
# {
|
||
# "cancelledOrderIds": ["63c97e47d686c5000159a656"]
|
||
# }
|
||
#
|
||
# fetchOpenOrders, fetchClosedOrders
|
||
#
|
||
# {
|
||
# "id": "63c97ce8d686c500015793bb",
|
||
# "symbol": "USDC-USDT",
|
||
# "opType": "DEAL",
|
||
# "type": "limit",
|
||
# "side": "sell",
|
||
# "price": "1.05",
|
||
# "size": "1",
|
||
# "funds": "0",
|
||
# "dealFunds": "0",
|
||
# "dealSize": "0",
|
||
# "fee": "0",
|
||
# "feeCurrency": "USDT",
|
||
# "stp": "",
|
||
# "stop": "",
|
||
# "stopTriggered": False,
|
||
# "stopPrice": "0",
|
||
# "timeInForce": "GTC",
|
||
# "postOnly": False,
|
||
# "hidden": False,
|
||
# "iceberg": False,
|
||
# "visibleSize": "0",
|
||
# "cancelAfter": 0,
|
||
# "channel": "API",
|
||
# "clientOid": "d602d73f-5424-4751-bef0-8debce8f0a82",
|
||
# "remark": null,
|
||
# "tags": "partner:ccxt",
|
||
# "isActive": True,
|
||
# "cancelExist": False,
|
||
# "createdAt": 1674149096927,
|
||
# "tradeType": "TRADE"
|
||
# }
|
||
#
|
||
# stop orders(fetchOpenOrders, fetchClosedOrders)
|
||
#
|
||
# {
|
||
# "id": "vs9f6ou9e864rgq8000t4qnm",
|
||
# "symbol": "USDC-USDT",
|
||
# "userId": "613a896885d8660006151f01",
|
||
# "status": "NEW",
|
||
# "type": "market",
|
||
# "side": "sell",
|
||
# "price": null,
|
||
# "size": "1.00000000000000000000",
|
||
# "funds": null,
|
||
# "stp": null,
|
||
# "timeInForce": "GTC",
|
||
# "cancelAfter": -1,
|
||
# "postOnly": False,
|
||
# "hidden": False,
|
||
# "iceberg": False,
|
||
# "visibleSize": null,
|
||
# "channel": "API",
|
||
# "clientOid": "5d3fd727-6456-438d-9550-40d9d85eee0b",
|
||
# "remark": null,
|
||
# "tags": "partner:ccxt",
|
||
# "relatedNo": null,
|
||
# "orderTime": 1674146316994000028,
|
||
# "domainId": "kucoin",
|
||
# "tradeSource": "USER",
|
||
# "tradeType": "MARGIN_TRADE",
|
||
# "feeCurrency": "USDT",
|
||
# "takerFeeRate": "0.00100000000000000000",
|
||
# "makerFeeRate": "0.00100000000000000000",
|
||
# "createdAt": 1674146316994,
|
||
# "stop": "loss",
|
||
# "stopTriggerTime": null,
|
||
# "stopPrice": "0.97000000000000000000"
|
||
# }
|
||
# hf order
|
||
# {
|
||
# "id":"6478cf1439bdfc0001528a1d",
|
||
# "symbol":"LTC-USDT",
|
||
# "opType":"DEAL",
|
||
# "type":"limit",
|
||
# "side":"buy",
|
||
# "price":"50",
|
||
# "size":"0.1",
|
||
# "funds":"5",
|
||
# "dealSize":"0",
|
||
# "dealFunds":"0",
|
||
# "fee":"0",
|
||
# "feeCurrency":"USDT",
|
||
# "stp":null,
|
||
# "timeInForce":"GTC",
|
||
# "postOnly":false,
|
||
# "hidden":false,
|
||
# "iceberg":false,
|
||
# "visibleSize":"0",
|
||
# "cancelAfter":0,
|
||
# "channel":"API",
|
||
# "clientOid":"d4d2016b-8e3a-445c-aa5d-dc6df5d1678d",
|
||
# "remark":null,
|
||
# "tags":"partner:ccxt",
|
||
# "cancelExist":false,
|
||
# "createdAt":1685638932074,
|
||
# "lastUpdatedAt":1685639013735,
|
||
# "tradeType":"TRADE",
|
||
# "inOrderBook":true,
|
||
# "cancelledSize":"0",
|
||
# "cancelledFunds":"0",
|
||
# "remainSize":"0.1",
|
||
# "remainFunds":"5",
|
||
# "active":true
|
||
# }
|
||
#
|
||
marketId = self.safe_string(order, 'symbol')
|
||
timestamp = self.safe_integer(order, 'createdAt')
|
||
feeCurrencyId = self.safe_string(order, 'feeCurrency')
|
||
cancelExist = self.safe_bool(order, 'cancelExist', False)
|
||
responseStop = self.safe_string(order, 'stop')
|
||
trigger = responseStop is not None
|
||
stopTriggered = self.safe_bool(order, 'stopTriggered', False)
|
||
isActive = self.safe_bool_2(order, 'isActive', 'active')
|
||
responseStatus = self.safe_string(order, 'status')
|
||
status = None
|
||
if isActive is not None:
|
||
if isActive is True:
|
||
status = 'open'
|
||
else:
|
||
status = 'closed'
|
||
if trigger:
|
||
if responseStatus == 'NEW':
|
||
status = 'open'
|
||
elif not isActive and not stopTriggered:
|
||
status = 'cancelled'
|
||
if cancelExist:
|
||
status = 'canceled'
|
||
if responseStatus == 'fail':
|
||
status = 'rejected'
|
||
return self.safe_order({
|
||
'info': order,
|
||
'id': self.safe_string_n(order, ['id', 'orderId', 'newOrderId', 'cancelledOrderId']),
|
||
'clientOrderId': self.safe_string(order, 'clientOid'),
|
||
'symbol': self.safe_symbol(marketId, market, '-'),
|
||
'type': self.safe_string(order, 'type'),
|
||
'timeInForce': self.safe_string(order, 'timeInForce'),
|
||
'postOnly': self.safe_bool(order, 'postOnly'),
|
||
'side': self.safe_string(order, 'side'),
|
||
'amount': self.safe_string(order, 'size'),
|
||
'price': self.safe_string(order, 'price'), # price is zero for market order, omitZero is called in safeOrder2
|
||
'triggerPrice': self.safe_number(order, 'stopPrice'),
|
||
'cost': self.safe_string(order, 'dealFunds'),
|
||
'filled': self.safe_string(order, 'dealSize'),
|
||
'remaining': None,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'fee': {
|
||
'currency': self.safe_currency_code(feeCurrencyId),
|
||
'cost': self.safe_number(order, 'fee'),
|
||
},
|
||
'status': status,
|
||
'lastTradeTimestamp': None,
|
||
'average': self.safe_string(order, 'avgDealPrice'),
|
||
'trades': None,
|
||
}, market)
|
||
|
||
async def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch all the trades made from a single order
|
||
|
||
https://docs.kucoin.com/#list-fills
|
||
https://docs.kucoin.com/spot-hf/#transaction-details
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch trades for
|
||
:param int [limit]: the maximum number of trades to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
||
"""
|
||
request: dict = {
|
||
'orderId': id,
|
||
}
|
||
return await self.fetch_my_trades(symbol, since, limit, self.extend(request, params))
|
||
|
||
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
|
||
https://docs.kucoin.com/#list-fills
|
||
https://docs.kucoin.com/spot-hf/#transaction-details
|
||
|
||
fetch all trades made by the user
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch trades for
|
||
:param int [limit]: the maximum number of trades structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param bool [params.hf]: False, # True for hf order
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
|
||
if paginate:
|
||
return await self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params)
|
||
request: dict = {}
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
if hf and symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol parameter for hf orders')
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
method = self.options['fetchMyTradesMethod']
|
||
parseResponseData = False
|
||
response = None
|
||
request, params = self.handle_until_option('endAt', request, params)
|
||
if hf:
|
||
# does not return trades earlier than 2019-02-18T00:00:00Z
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
if since is not None:
|
||
# only returns trades up to one week after the since param
|
||
request['startAt'] = since
|
||
response = await self.privateGetHfFills(self.extend(request, params))
|
||
elif method == 'private_get_fills':
|
||
# does not return trades earlier than 2019-02-18T00:00:00Z
|
||
if since is not None:
|
||
# only returns trades up to one week after the since param
|
||
request['startAt'] = since
|
||
response = await self.privateGetFills(self.extend(request, params))
|
||
elif method == 'private_get_limit_fills':
|
||
# does not return trades earlier than 2019-02-18T00:00:00Z
|
||
# takes no params
|
||
# only returns first 1000 trades(not only "in the last 24 hours" in the docs)
|
||
parseResponseData = True
|
||
response = await self.privateGetLimitFills(self.extend(request, params))
|
||
else:
|
||
raise ExchangeError(self.id + ' fetchMyTradesMethod() invalid method')
|
||
#
|
||
# {
|
||
# "currentPage": 1,
|
||
# "pageSize": 50,
|
||
# "totalNum": 1,
|
||
# "totalPage": 1,
|
||
# "items": [
|
||
# {
|
||
# "symbol":"BTC-USDT", # symbol
|
||
# "tradeId":"5c35c02709e4f67d5266954e", # trade id
|
||
# "orderId":"5c35c02703aa673ceec2a168", # order id
|
||
# "counterOrderId":"5c1ab46003aa676e487fa8e3", # counter order id
|
||
# "side":"buy", # transaction direction,include buy and sell
|
||
# "liquidity":"taker", # include taker and maker
|
||
# "forceTaker":true, # forced to become taker
|
||
# "price":"0.083", # order price
|
||
# "size":"0.8424304", # order quantity
|
||
# "funds":"0.0699217232", # order funds
|
||
# "fee":"0", # fee
|
||
# "feeRate":"0", # fee rate
|
||
# "feeCurrency":"USDT", # charge fee currency
|
||
# "stop":"", # stop type
|
||
# "type":"limit", # order type, e.g. limit, market, stop_limit.
|
||
# "createdAt":1547026472000 # time
|
||
# },
|
||
# #------------------------------------------------------
|
||
# # v1(historical) trade response structure
|
||
# {
|
||
# "symbol": "SNOV-ETH",
|
||
# "dealPrice": "0.0000246",
|
||
# "dealValue": "0.018942",
|
||
# "amount": "770",
|
||
# "fee": "0.00001137",
|
||
# "side": "sell",
|
||
# "createdAt": 1540080199
|
||
# "id":"5c4d389e4c8c60413f78e2e5",
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
trades = None
|
||
if parseResponseData:
|
||
trades = data
|
||
else:
|
||
trades = self.safe_list(data, 'items', [])
|
||
return self.parse_trades(trades, market, since, limit)
|
||
|
||
async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
||
"""
|
||
get the list of most recent trades for a particular symbol
|
||
|
||
https://www.kucoin.com/docs/rest/spot-trading/market-data/get-trade-histories
|
||
https://www.kucoin.com/docs-new/rest/ua/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
|
||
:param int [limit]: the maximum amount of trades to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.uta]: set to True for the unified trading account(uta), defaults to False
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
# pagination is not supported on the exchange side anymore
|
||
# if since is not None:
|
||
# request['startAt'] = int(math.floor(since / 1000))
|
||
# }
|
||
# if limit is not None:
|
||
# request['pageSize'] = limit
|
||
# }
|
||
uta = None
|
||
uta, params = self.handle_option_and_params(params, 'fetchTrades', 'uta', False)
|
||
response = None
|
||
trades = None
|
||
if uta:
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchTrades', market, params)
|
||
if type == 'spot':
|
||
request['tradeType'] = 'SPOT'
|
||
else:
|
||
request['tradeType'] = 'FUTURES'
|
||
response = await self.utaGetMarketTrade(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "tradeType": "SPOT",
|
||
# "list": [
|
||
# {
|
||
# "sequence": "18746044393340932",
|
||
# "tradeId": "18746044393340932",
|
||
# "price": "104355.6",
|
||
# "size": "0.00011886",
|
||
# "side": "sell",
|
||
# "ts": 1762242540829000000
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
trades = self.safe_list(data, 'list', [])
|
||
else:
|
||
response = await self.publicGetMarketHistories(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": [
|
||
# {
|
||
# "sequence": "1548764654235",
|
||
# "side": "sell",
|
||
# "size":"0.6841354",
|
||
# "price":"0.03202",
|
||
# "time":1548848575203567174
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
trades = self.safe_list(response, 'data', [])
|
||
return self.parse_trades(trades, market, since, limit)
|
||
|
||
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
||
#
|
||
# fetchTrades(public)
|
||
#
|
||
# {
|
||
# "sequence": "1548764654235",
|
||
# "side": "sell",
|
||
# "size":"0.6841354",
|
||
# "price":"0.03202",
|
||
# "time":1548848575203567174
|
||
# }
|
||
#
|
||
# {
|
||
# "sequence": "1568787654360",
|
||
# "symbol": "BTC-USDT",
|
||
# "side": "buy",
|
||
# "size": "0.00536577",
|
||
# "price": "9345",
|
||
# "takerOrderId": "5e356c4a9f1a790008f8d921",
|
||
# "time": "1580559434436443257",
|
||
# "type": "match",
|
||
# "makerOrderId": "5e356bffedf0010008fa5d7f",
|
||
# "tradeId": "5e356c4aeefabd62c62a1ece"
|
||
# }
|
||
#
|
||
# fetchMyTrades(private) v2
|
||
#
|
||
# {
|
||
# "symbol":"BTC-USDT",
|
||
# "tradeId":"5c35c02709e4f67d5266954e",
|
||
# "orderId":"5c35c02703aa673ceec2a168",
|
||
# "counterOrderId":"5c1ab46003aa676e487fa8e3",
|
||
# "side":"buy",
|
||
# "liquidity":"taker",
|
||
# "forceTaker":true,
|
||
# "price":"0.083",
|
||
# "size":"0.8424304",
|
||
# "funds":"0.0699217232",
|
||
# "fee":"0",
|
||
# "feeRate":"0",
|
||
# "feeCurrency":"USDT",
|
||
# "stop":"",
|
||
# "type":"limit",
|
||
# "createdAt":1547026472000
|
||
# }
|
||
#
|
||
# fetchMyTrades v2 alternative format since 2019-05-21 https://github.com/ccxt/ccxt/pull/5162
|
||
#
|
||
# {
|
||
# "symbol": "OPEN-BTC",
|
||
# "forceTaker": False,
|
||
# "orderId": "5ce36420054b4663b1fff2c9",
|
||
# "fee": "0",
|
||
# "feeCurrency": "",
|
||
# "type": "",
|
||
# "feeRate": "0",
|
||
# "createdAt": 1558417615000,
|
||
# "size": "12.8206",
|
||
# "stop": "",
|
||
# "price": "0",
|
||
# "funds": "0",
|
||
# "tradeId": "5ce390cf6e0db23b861c6e80"
|
||
# }
|
||
#
|
||
# fetchMyTrades(private) v1(historical)
|
||
#
|
||
# {
|
||
# "symbol": "SNOV-ETH",
|
||
# "dealPrice": "0.0000246",
|
||
# "dealValue": "0.018942",
|
||
# "amount": "770",
|
||
# "fee": "0.00001137",
|
||
# "side": "sell",
|
||
# "createdAt": 1540080199
|
||
# "id":"5c4d389e4c8c60413f78e2e5",
|
||
# }
|
||
#
|
||
# uta fetchTrades
|
||
#
|
||
# {
|
||
# "sequence": "18746044393340932",
|
||
# "tradeId": "18746044393340932",
|
||
# "price": "104355.6",
|
||
# "size": "0.00011886",
|
||
# "side": "sell",
|
||
# "ts": 1762242540829000000
|
||
# }
|
||
#
|
||
marketId = self.safe_string(trade, 'symbol')
|
||
market = self.safe_market(marketId, market, '-')
|
||
id = self.safe_string_2(trade, 'tradeId', 'id')
|
||
orderId = self.safe_string(trade, 'orderId')
|
||
takerOrMaker = self.safe_string(trade, 'liquidity')
|
||
timestamp = self.safe_integer_2(trade, 'time', 'ts')
|
||
if timestamp is not None:
|
||
timestamp = self.parse_to_int(timestamp / 1000000)
|
||
else:
|
||
timestamp = self.safe_integer(trade, 'createdAt')
|
||
# if it's a historical v1 trade, the exchange returns timestamp in seconds
|
||
if ('dealValue' in trade) and (timestamp is not None):
|
||
timestamp = timestamp * 1000
|
||
priceString = self.safe_string_2(trade, 'price', 'dealPrice')
|
||
amountString = self.safe_string_2(trade, 'size', 'amount')
|
||
side = self.safe_string(trade, 'side')
|
||
fee = None
|
||
feeCostString = self.safe_string(trade, 'fee')
|
||
if feeCostString is not None:
|
||
feeCurrencyId = self.safe_string(trade, 'feeCurrency')
|
||
feeCurrency = self.safe_currency_code(feeCurrencyId)
|
||
if feeCurrency is None:
|
||
feeCurrency = market['quote'] if (side == 'sell') else market['base']
|
||
fee = {
|
||
'cost': feeCostString,
|
||
'currency': feeCurrency,
|
||
'rate': self.safe_string(trade, 'feeRate'),
|
||
}
|
||
type = self.safe_string(trade, 'type')
|
||
if type == 'match':
|
||
type = None
|
||
costString = self.safe_string_2(trade, 'funds', 'dealValue')
|
||
return self.safe_trade({
|
||
'info': trade,
|
||
'id': id,
|
||
'order': orderId,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'symbol': market['symbol'],
|
||
'type': type,
|
||
'takerOrMaker': takerOrMaker,
|
||
'side': side,
|
||
'price': priceString,
|
||
'amount': amountString,
|
||
'cost': costString,
|
||
'fee': fee,
|
||
}, market)
|
||
|
||
async def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
|
||
"""
|
||
fetch the trading fees for a market
|
||
|
||
https://www.kucoin.com/docs/rest/funding/trade-fee/trading-pair-actual-fee-spot-margin-trade_hf
|
||
|
||
: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>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbols': market['id'],
|
||
}
|
||
response = await self.privateGetTradeFees(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": [
|
||
# {
|
||
# "symbol": "BTC-USDT",
|
||
# "takerFeeRate": "0.001",
|
||
# "makerFeeRate": "0.001"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
first = self.safe_dict(data, 0)
|
||
marketId = self.safe_string(first, 'symbol')
|
||
return {
|
||
'info': response,
|
||
'symbol': self.safe_symbol(marketId, market),
|
||
'maker': self.safe_number(first, 'makerFeeRate'),
|
||
'taker': self.safe_number(first, 'takerFeeRate'),
|
||
'percentage': True,
|
||
'tierBased': True,
|
||
}
|
||
|
||
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
||
"""
|
||
make a withdrawal
|
||
|
||
https://www.kucoin.com/docs/rest/funding/withdrawals/apply-withdraw-v3-
|
||
|
||
: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)
|
||
await self.load_markets()
|
||
self.check_address(address)
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
'toAddress': address,
|
||
'withdrawType': 'ADDRESS',
|
||
# 'memo': tag,
|
||
# 'isInner': False, # internal transfer or external withdrawal
|
||
# 'remark': 'optional',
|
||
# 'chain': 'OMNI', # 'ERC20', 'TRC20', default is ERC20, This only apply for multi-chain currency, and there is no need for single chain currency.
|
||
}
|
||
if tag is not None:
|
||
request['memo'] = tag
|
||
networkCode = None
|
||
networkCode, params = self.handle_network_code_and_params(params)
|
||
if networkCode is not None:
|
||
request['chain'] = self.network_code_to_id(networkCode).lower()
|
||
request['amount'] = float(self.currency_to_precision(code, amount, networkCode))
|
||
includeFee = None
|
||
includeFee, params = self.handle_option_and_params(params, 'withdraw', 'includeFee', False)
|
||
if includeFee:
|
||
request['feeDeductType'] = 'INTERNAL'
|
||
response = await self.privatePostWithdrawals(self.extend(request, params))
|
||
#
|
||
# the id is inside "data"
|
||
#
|
||
# {
|
||
# "code": 200000,
|
||
# "data": {
|
||
# "withdrawalId": "5bffb63303aa675e8bbe18f9"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_transaction(data, currency)
|
||
|
||
def parse_transaction_status(self, status: Str):
|
||
statuses: dict = {
|
||
'SUCCESS': 'ok',
|
||
'PROCESSING': 'pending',
|
||
'WALLET_PROCESSING': 'pending',
|
||
'FAILURE': 'failed',
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
||
#
|
||
# fetchDeposits
|
||
#
|
||
# {
|
||
# "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989",
|
||
# "memo": "5c247c8a03aa677cea2a251d",
|
||
# "amount": 1,
|
||
# "fee": 0.0001,
|
||
# "currency": "KCS",
|
||
# "chain": "",
|
||
# "isInner": False,
|
||
# "walletTxId": "5bbb57386d99522d9f954c5a@test004",
|
||
# "status": "SUCCESS",
|
||
# "createdAt": 1544178843000,
|
||
# "updatedAt": 1544178891000
|
||
# "remark":"foobar"
|
||
# }
|
||
#
|
||
# fetchWithdrawals
|
||
#
|
||
# {
|
||
# "id": "5c2dc64e03aa675aa263f1ac",
|
||
# "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9",
|
||
# "memo": "",
|
||
# "currency": "ETH",
|
||
# "chain": "",
|
||
# "amount": 1.0000000,
|
||
# "fee": 0.0100000,
|
||
# "walletTxId": "3e2414d82acce78d38be7fe9",
|
||
# "isInner": False,
|
||
# "status": "FAILURE",
|
||
# "createdAt": 1546503758000,
|
||
# "updatedAt": 1546504603000
|
||
# "remark":"foobar"
|
||
# }
|
||
#
|
||
# withdraw
|
||
#
|
||
# {
|
||
# "withdrawalId": "5bffb63303aa675e8bbe18f9"
|
||
# }
|
||
#
|
||
currencyId = self.safe_string(transaction, 'currency')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
address = self.safe_string(transaction, 'address')
|
||
amount = self.safe_string(transaction, 'amount')
|
||
txid = self.safe_string(transaction, 'walletTxId')
|
||
if txid is not None:
|
||
txidParts = txid.split('@')
|
||
numTxidParts = len(txidParts)
|
||
if numTxidParts > 1:
|
||
if address is None:
|
||
if len(txidParts[1]) > 1:
|
||
address = txidParts[1]
|
||
txid = txidParts[0]
|
||
type = 'withdrawal' if (txid is None) else 'deposit'
|
||
rawStatus = self.safe_string(transaction, 'status')
|
||
fee = None
|
||
feeCost = self.safe_string(transaction, 'fee')
|
||
if feeCost is not None:
|
||
rate = None
|
||
if amount is not None:
|
||
rate = Precise.string_div(feeCost, amount)
|
||
fee = {
|
||
'cost': self.parse_number(feeCost),
|
||
'rate': self.parse_number(rate),
|
||
'currency': code,
|
||
}
|
||
timestamp = self.safe_integer_2(transaction, 'createdAt', 'createAt')
|
||
updated = self.safe_integer(transaction, 'updatedAt')
|
||
isV1 = not ('createdAt' in transaction)
|
||
# if it's a v1 structure
|
||
if isV1:
|
||
type = 'withdrawal' if ('address' in transaction) else 'deposit'
|
||
if timestamp is not None:
|
||
timestamp = timestamp * 1000
|
||
if updated is not None:
|
||
updated = updated * 1000
|
||
internal = self.safe_bool(transaction, 'isInner')
|
||
tag = self.safe_string(transaction, 'memo')
|
||
return {
|
||
'info': transaction,
|
||
'id': self.safe_string_2(transaction, 'id', 'withdrawalId'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'network': self.network_id_to_code(self.safe_string(transaction, 'chain')),
|
||
'address': address,
|
||
'addressTo': address,
|
||
'addressFrom': None,
|
||
'tag': tag,
|
||
'tagTo': tag,
|
||
'tagFrom': None,
|
||
'currency': code,
|
||
'amount': self.parse_number(amount),
|
||
'txid': txid,
|
||
'type': type,
|
||
'status': self.parse_transaction_status(rawStatus),
|
||
'comment': self.safe_string(transaction, 'remark'),
|
||
'internal': internal,
|
||
'fee': fee,
|
||
'updated': updated,
|
||
}
|
||
|
||
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
fetch all deposits made to an account
|
||
|
||
https://www.kucoin.com/docs/rest/funding/deposit/get-deposit-list
|
||
https://www.kucoin.com/docs/rest/funding/deposit/get-v1-historical-deposits-list
|
||
|
||
: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]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchDeposits', 'paginate')
|
||
if paginate:
|
||
return await self.fetch_paginated_call_dynamic('fetchDeposits', code, since, limit, params)
|
||
request: dict = {}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['currency'] = currency['id']
|
||
if limit is not None:
|
||
request['pageSize'] = limit
|
||
request, params = self.handle_until_option('endAt', request, params)
|
||
response = None
|
||
if since is not None and since < 1550448000000:
|
||
# if since is earlier than 2019-02-18T00:00:00Z
|
||
request['startAt'] = self.parse_to_int(since / 1000)
|
||
response = await self.privateGetHistDeposits(self.extend(request, params))
|
||
else:
|
||
if since is not None:
|
||
request['startAt'] = since
|
||
response = await self.privateGetDeposits(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "currentPage": 1,
|
||
# "pageSize": 5,
|
||
# "totalNum": 2,
|
||
# "totalPage": 1,
|
||
# "items": [
|
||
# #--------------------------------------------------
|
||
# # version 2 deposit response structure
|
||
# {
|
||
# "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989",
|
||
# "memo": "5c247c8a03aa677cea2a251d",
|
||
# "amount": 1,
|
||
# "fee": 0.0001,
|
||
# "currency": "KCS",
|
||
# "isInner": False,
|
||
# "walletTxId": "5bbb57386d99522d9f954c5a@test004",
|
||
# "status": "SUCCESS",
|
||
# "createdAt": 1544178843000,
|
||
# "updatedAt": 1544178891000
|
||
# "remark":"foobar"
|
||
# },
|
||
# #--------------------------------------------------
|
||
# # version 1(historical) deposit response structure
|
||
# {
|
||
# "currency": "BTC",
|
||
# "createAt": 1528536998,
|
||
# "amount": "0.03266638",
|
||
# "walletTxId": "55c643bc2c68d6f17266383ac1be9e454038864b929ae7cee0bc408cc5c869e8@12ffGWmMMD1zA1WbFm7Ho3JZ1w6NYXjpFk@234",
|
||
# "isInner": False,
|
||
# "status": "SUCCESS",
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
items = self.safe_list(data, 'items', [])
|
||
return self.parse_transactions(items, currency, since, limit, {'type': 'deposit'})
|
||
|
||
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
fetch all withdrawals made from an account
|
||
|
||
https://www.kucoin.com/docs/rest/funding/withdrawals/get-withdrawals-list
|
||
https://www.kucoin.com/docs/rest/funding/withdrawals/get-v1-historical-withdrawals-list
|
||
|
||
: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]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchWithdrawals', 'paginate')
|
||
if paginate:
|
||
return await self.fetch_paginated_call_dynamic('fetchWithdrawals', code, since, limit, params)
|
||
request: dict = {}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['currency'] = currency['id']
|
||
if limit is not None:
|
||
request['pageSize'] = limit
|
||
request, params = self.handle_until_option('endAt', request, params)
|
||
response = None
|
||
if since is not None and since < 1550448000000:
|
||
# if since is earlier than 2019-02-18T00:00:00Z
|
||
request['startAt'] = self.parse_to_int(since / 1000)
|
||
response = await self.privateGetHistWithdrawals(self.extend(request, params))
|
||
else:
|
||
if since is not None:
|
||
request['startAt'] = since
|
||
response = await self.privateGetWithdrawals(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "currentPage": 1,
|
||
# "pageSize": 5,
|
||
# "totalNum": 2,
|
||
# "totalPage": 1,
|
||
# "items": [
|
||
# #--------------------------------------------------
|
||
# # version 2 withdrawal response structure
|
||
# {
|
||
# "id": "5c2dc64e03aa675aa263f1ac",
|
||
# "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9",
|
||
# "memo": "",
|
||
# "currency": "ETH",
|
||
# "amount": 1.0000000,
|
||
# "fee": 0.0100000,
|
||
# "walletTxId": "3e2414d82acce78d38be7fe9",
|
||
# "isInner": False,
|
||
# "status": "FAILURE",
|
||
# "createdAt": 1546503758000,
|
||
# "updatedAt": 1546504603000
|
||
# },
|
||
# #--------------------------------------------------
|
||
# # version 1(historical) withdrawal response structure
|
||
# {
|
||
# "currency": "BTC",
|
||
# "createAt": 1526723468,
|
||
# "amount": "0.534",
|
||
# "address": "33xW37ZSW4tQvg443Pc7NLCAs167Yc2XUV",
|
||
# "walletTxId": "aeacea864c020acf58e51606169240e96774838dcd4f7ce48acf38e3651323f4",
|
||
# "isInner": False,
|
||
# "status": "SUCCESS"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
items = self.safe_list(data, 'items', [])
|
||
return self.parse_transactions(items, currency, since, limit, {'type': 'withdrawal'})
|
||
|
||
def parse_balance_helper(self, entry):
|
||
account = self.account()
|
||
account['used'] = self.safe_string_2(entry, 'holdBalance', 'hold')
|
||
account['free'] = self.safe_string_2(entry, 'availableBalance', 'available')
|
||
account['total'] = self.safe_string_2(entry, 'totalBalance', 'total')
|
||
debt = self.safe_string(entry, 'liability')
|
||
interest = self.safe_string(entry, 'interest')
|
||
account['debt'] = Precise.string_add(debt, interest)
|
||
return account
|
||
|
||
async def fetch_balance(self, params={}) -> Balances:
|
||
"""
|
||
query for balance and get the amount of funds available for trading or funds locked in orders
|
||
|
||
https://www.kucoin.com/docs/rest/account/basic-info/get-account-list-spot-margin-trade_hf
|
||
https://www.kucoin.com/docs/rest/funding/funding-overview/get-account-detail-margin
|
||
https://www.kucoin.com/docs/rest/funding/funding-overview/get-account-detail-isolated-margin
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param dict [params.marginMode]: 'cross' or 'isolated', margin type for fetching margin balance
|
||
:param dict [params.type]: extra parameters specific to the exchange API endpoint
|
||
:param dict [params.hf]: *default if False* if True, the result includes the balance of the high frequency account
|
||
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
code = self.safe_string(params, 'code')
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
|
||
requestedType = self.safe_string(params, 'type', defaultType)
|
||
accountsByType = self.safe_dict(self.options, 'accountsByType')
|
||
type = self.safe_string(accountsByType, requestedType, requestedType)
|
||
params = self.omit(params, 'type')
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
if hf and (type != 'main'):
|
||
type = 'trade_hf'
|
||
marginMode, query = self.handle_margin_mode_and_params('fetchBalance', params)
|
||
response = None
|
||
request: dict = {}
|
||
isolated = (marginMode == 'isolated') or (type == 'isolated')
|
||
cross = (marginMode == 'cross') or (type == 'margin')
|
||
if isolated:
|
||
if currency is not None:
|
||
request['balanceCurrency'] = currency['id']
|
||
response = await self.privateGetIsolatedAccounts(self.extend(request, query))
|
||
elif cross:
|
||
response = await self.privateGetMarginAccount(self.extend(request, query))
|
||
else:
|
||
if currency is not None:
|
||
request['currency'] = currency['id']
|
||
request['type'] = type
|
||
response = await self.privateGetAccounts(self.extend(request, query))
|
||
#
|
||
# Spot
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": [
|
||
# {
|
||
# "balance": "0.00009788",
|
||
# "available": "0.00009788",
|
||
# "holds": "0",
|
||
# "currency": "BTC",
|
||
# "id": "5c6a4fd399a1d81c4f9cc4d0",
|
||
# "type": "trade",
|
||
# },
|
||
# ]
|
||
# }
|
||
#
|
||
# Cross
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "debtRatio": "0",
|
||
# "accounts": [
|
||
# {
|
||
# "currency": "USDT",
|
||
# "totalBalance": "5",
|
||
# "availableBalance": "5",
|
||
# "holdBalance": "0",
|
||
# "liability": "0",
|
||
# "maxBorrowSize": "20"
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
# Isolated
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "totalAssetOfQuoteCurrency": "0",
|
||
# "totalLiabilityOfQuoteCurrency": "0",
|
||
# "timestamp": 1712085661155,
|
||
# "assets": [
|
||
# {
|
||
# "symbol": "MANA-USDT",
|
||
# "status": "EFFECTIVE",
|
||
# "debtRatio": "0",
|
||
# "baseAsset": {
|
||
# "currency": "MANA",
|
||
# "borrowEnabled": True,
|
||
# "transferInEnabled": True,
|
||
# "total": "0",
|
||
# "hold": "0",
|
||
# "available": "0",
|
||
# "liability": "0",
|
||
# "interest": "0",
|
||
# "maxBorrowSize": "0"
|
||
# },
|
||
# "quoteAsset": {
|
||
# "currency": "USDT",
|
||
# "borrowEnabled": True,
|
||
# "transferInEnabled": True,
|
||
# "total": "0",
|
||
# "hold": "0",
|
||
# "available": "0",
|
||
# "liability": "0",
|
||
# "interest": "0",
|
||
# "maxBorrowSize": "0"
|
||
# }
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = None
|
||
result: dict = {
|
||
'info': response,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
}
|
||
if isolated:
|
||
data = self.safe_dict(response, 'data', {})
|
||
assets = self.safe_value(data, 'assets', data)
|
||
for i in range(0, len(assets)):
|
||
entry = assets[i]
|
||
marketId = self.safe_string(entry, 'symbol')
|
||
symbol = self.safe_symbol(marketId, None, '_')
|
||
base = self.safe_dict(entry, 'baseAsset', {})
|
||
quote = self.safe_dict(entry, 'quoteAsset', {})
|
||
baseCode = self.safe_currency_code(self.safe_string(base, 'currency'))
|
||
quoteCode = self.safe_currency_code(self.safe_string(quote, 'currency'))
|
||
subResult: dict = {}
|
||
subResult[baseCode] = self.parse_balance_helper(base)
|
||
subResult[quoteCode] = self.parse_balance_helper(quote)
|
||
result[symbol] = self.safe_balance(subResult)
|
||
elif cross:
|
||
data = self.safe_dict(response, 'data', {})
|
||
accounts = self.safe_list(data, 'accounts', [])
|
||
for i in range(0, len(accounts)):
|
||
balance = accounts[i]
|
||
currencyId = self.safe_string(balance, 'currency')
|
||
codeInner = self.safe_currency_code(currencyId)
|
||
result[codeInner] = self.parse_balance_helper(balance)
|
||
else:
|
||
data = self.safe_list(response, 'data', [])
|
||
for i in range(0, len(data)):
|
||
balance = data[i]
|
||
balanceType = self.safe_string(balance, 'type')
|
||
if balanceType == type:
|
||
currencyId = self.safe_string(balance, 'currency')
|
||
codeInner2 = self.safe_currency_code(currencyId)
|
||
account = self.account()
|
||
account['total'] = self.safe_string(balance, 'balance')
|
||
account['free'] = self.safe_string(balance, 'available')
|
||
account['used'] = self.safe_string(balance, 'holds')
|
||
result[codeInner2] = account
|
||
returnType = result
|
||
if not isolated:
|
||
returnType = self.safe_balance(result)
|
||
return returnType
|
||
|
||
async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
||
"""
|
||
transfer currency internally between wallets on the same account
|
||
|
||
https://www.kucoin.com/docs/rest/funding/transfer/inner-transfer
|
||
https://docs.kucoin.com/futures/#transfer-funds-to-kucoin-main-account-2
|
||
https://docs.kucoin.com/spot-hf/#internal-funds-transfers-in-high-frequency-trading-accounts
|
||
|
||
:param str code: unified currency code
|
||
:param float amount: amount to transfer
|
||
:param str fromAccount: account to transfer from
|
||
:param str toAccount: account to transfer to
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
requestedAmount = self.currency_to_precision(code, amount)
|
||
fromId = self.convert_type_to_account(fromAccount)
|
||
toId = self.convert_type_to_account(toAccount)
|
||
fromIsolated = self.in_array(fromId, self.ids)
|
||
toIsolated = self.in_array(toId, self.ids)
|
||
if fromId == 'contract':
|
||
if toId != 'main':
|
||
raise ExchangeError(self.id + ' transfer() only supports transferring from futures account to main account')
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
'amount': requestedAmount,
|
||
}
|
||
if not ('bizNo' in params):
|
||
# it doesn't like more than 24 characters
|
||
request['bizNo'] = self.uuid22()
|
||
response = await self.futuresPrivatePostTransferOut(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "applyId": "605a87217dff1500063d485d",
|
||
# "bizNo": "bcd6e5e1291f4905af84dc",
|
||
# "payAccountType": "CONTRACT",
|
||
# "payTag": "DEFAULT",
|
||
# "remark": '',
|
||
# "recAccountType": "MAIN",
|
||
# "recTag": "DEFAULT",
|
||
# "recRemark": '',
|
||
# "recSystem": "KUCOIN",
|
||
# "status": "PROCESSING",
|
||
# "currency": "XBT",
|
||
# "amount": "0.00001",
|
||
# "fee": "0",
|
||
# "sn": "573688685663948",
|
||
# "reason": '',
|
||
# "createdAt": 1616545569000,
|
||
# "updatedAt": 1616545569000
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data')
|
||
return self.parse_transfer(data, currency)
|
||
else:
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
'amount': requestedAmount,
|
||
}
|
||
if fromIsolated or toIsolated:
|
||
if self.in_array(fromId, self.ids):
|
||
request['fromTag'] = fromId
|
||
fromId = 'isolated'
|
||
if self.in_array(toId, self.ids):
|
||
request['toTag'] = toId
|
||
toId = 'isolated'
|
||
request['from'] = fromId
|
||
request['to'] = toId
|
||
if not ('clientOid' in params):
|
||
request['clientOid'] = self.uuid()
|
||
response = await self.privatePostAccountsInnerTransfer(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "orderId": "605a6211e657f00006ad0ad6"
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data')
|
||
return self.parse_transfer(data, currency)
|
||
|
||
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
||
#
|
||
# transfer(spot)
|
||
#
|
||
# {
|
||
# "orderId": "605a6211e657f00006ad0ad6"
|
||
# }
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "msg": "Failed to transfer out. The amount exceeds the upper limit"
|
||
# }
|
||
#
|
||
# transfer(futures)
|
||
#
|
||
# {
|
||
# "applyId": "605a87217dff1500063d485d",
|
||
# "bizNo": "bcd6e5e1291f4905af84dc",
|
||
# "payAccountType": "CONTRACT",
|
||
# "payTag": "DEFAULT",
|
||
# "remark": '',
|
||
# "recAccountType": "MAIN",
|
||
# "recTag": "DEFAULT",
|
||
# "recRemark": '',
|
||
# "recSystem": "KUCOIN",
|
||
# "status": "PROCESSING",
|
||
# "currency": "XBT",
|
||
# "amount": "0.00001",
|
||
# "fee": "0",
|
||
# "sn": "573688685663948",
|
||
# "reason": '',
|
||
# "createdAt": 1616545569000,
|
||
# "updatedAt": 1616545569000
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(transfer, 'createdAt')
|
||
currencyId = self.safe_string(transfer, 'currency')
|
||
rawStatus = self.safe_string(transfer, 'status')
|
||
accountFromRaw = self.safe_string_lower(transfer, 'payAccountType')
|
||
accountToRaw = self.safe_string_lower(transfer, 'recAccountType')
|
||
accountsByType = self.safe_dict(self.options, 'accountsByType')
|
||
accountFrom = self.safe_string(accountsByType, accountFromRaw, accountFromRaw)
|
||
accountTo = self.safe_string(accountsByType, accountToRaw, accountToRaw)
|
||
return {
|
||
'id': self.safe_string_2(transfer, 'applyId', 'orderId'),
|
||
'currency': self.safe_currency_code(currencyId, currency),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'amount': self.safe_number(transfer, 'amount'),
|
||
'fromAccount': accountFrom,
|
||
'toAccount': accountTo,
|
||
'status': self.parse_transfer_status(rawStatus),
|
||
'info': transfer,
|
||
}
|
||
|
||
def parse_transfer_status(self, status: Str) -> Str:
|
||
statuses: dict = {
|
||
'PROCESSING': 'pending',
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_ledger_entry_type(self, type):
|
||
types: dict = {
|
||
'Assets Transferred in After Upgrading': 'transfer', # Assets Transferred in After V1 to V2 Upgrading
|
||
'Deposit': 'transaction', # Deposit
|
||
'Withdrawal': 'transaction', # Withdrawal
|
||
'Transfer': 'transfer', # Transfer
|
||
'Trade_Exchange': 'trade', # Trade
|
||
# 'Vote for Coin': 'Vote for Coin', # Vote for Coin
|
||
'KuCoin Bonus': 'bonus', # KuCoin Bonus
|
||
'Referral Bonus': 'referral', # Referral Bonus
|
||
'Rewards': 'bonus', # Activities Rewards
|
||
# 'Distribution': 'Distribution', # Distribution, such GAS by holding NEO
|
||
'Airdrop/Fork': 'airdrop', # Airdrop/Fork
|
||
'Other rewards': 'bonus', # Other rewards, except Vote, Airdrop, Fork
|
||
'Fee Rebate': 'rebate', # Fee Rebate
|
||
'Buy Crypto': 'trade', # Use credit card to buy crypto
|
||
'Sell Crypto': 'sell', # Use credit card to sell crypto
|
||
'Public Offering Purchase': 'trade', # Public Offering Purchase for Spotlight
|
||
# 'Send red envelope': 'Send red envelope', # Send red envelope
|
||
# 'Open red envelope': 'Open red envelope', # Open red envelope
|
||
# 'Staking': 'Staking', # Staking
|
||
# 'LockDrop Vesting': 'LockDrop Vesting', # LockDrop Vesting
|
||
# 'Staking Profits': 'Staking Profits', # Staking Profits
|
||
# 'Redemption': 'Redemption', # Redemption
|
||
'Refunded Fees': 'fee', # Refunded Fees
|
||
'KCS Pay Fees': 'fee', # KCS Pay Fees
|
||
'Margin Trade': 'trade', # Margin Trade
|
||
'Loans': 'Loans', # Loans
|
||
# 'Borrowings': 'Borrowings', # Borrowings
|
||
# 'Debt Repayment': 'Debt Repayment', # Debt Repayment
|
||
# 'Loans Repaid': 'Loans Repaid', # Loans Repaid
|
||
# 'Lendings': 'Lendings', # Lendings
|
||
# 'Pool transactions': 'Pool transactions', # Pool-X transactions
|
||
'Instant Exchange': 'trade', # Instant Exchange
|
||
'Sub-account transfer': 'transfer', # Sub-account transfer
|
||
'Liquidation Fees': 'fee', # Liquidation Fees
|
||
# 'Soft Staking Profits': 'Soft Staking Profits', # Soft Staking Profits
|
||
# 'Voting Earnings': 'Voting Earnings', # Voting Earnings on Pool-X
|
||
# 'Redemption of Voting': 'Redemption of Voting', # Redemption of Voting on Pool-X
|
||
# 'Voting': 'Voting', # Voting on Pool-X
|
||
# 'Convert to KCS': 'Convert to KCS', # Convert to KCS
|
||
}
|
||
return self.safe_string(types, type, type)
|
||
|
||
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
|
||
#
|
||
# {
|
||
# "id": "611a1e7c6a053300067a88d9", #unique key for each ledger entry
|
||
# "currency": "USDT", #Currency
|
||
# "amount": "10.00059547", #The total amount of assets(fees included) involved in assets changes such, withdrawal and bonus distribution.
|
||
# "fee": "0", #Deposit or withdrawal fee
|
||
# "balance": "0", #Total assets of a currency remaining funds after transaction
|
||
# "accountType": "MAIN", #Account Type
|
||
# "bizType": "Loans Repaid", #business type
|
||
# "direction": "in", #side, in or out
|
||
# "createdAt": 1629101692950, #Creation time
|
||
# "context": "{\"borrowerUserId\":\"601ad03e50dc810006d242ea\",\"loanRepayDetailNo\":\"611a1e7cc913d000066cf7ec\"}" #Business core parameters
|
||
# }
|
||
#
|
||
id = self.safe_string(item, 'id')
|
||
currencyId = self.safe_string(item, 'currency')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
currency = self.safe_currency(currencyId, currency)
|
||
amount = self.safe_number(item, 'amount')
|
||
balanceAfter = None
|
||
# balanceAfter = self.safe_number(item, 'balance'); only returns zero string
|
||
bizType = self.safe_string(item, 'bizType')
|
||
type = self.parse_ledger_entry_type(bizType)
|
||
direction = self.safe_string(item, 'direction')
|
||
timestamp = self.safe_integer(item, 'createdAt')
|
||
datetime = self.iso8601(timestamp)
|
||
account = self.safe_string(item, 'accountType') # MAIN, TRADE, MARGIN, or CONTRACT
|
||
context = self.safe_string(item, 'context') # contains other information about the ledger entry
|
||
#
|
||
# withdrawal transaction
|
||
#
|
||
# "{\"orderId\":\"617bb2d09e7b3b000196dac8\",\"txId\":\"0x79bb9855f86b351a45cab4dc69d78ca09586a94c45dde49475722b98f401b054\"}"
|
||
#
|
||
# deposit to MAIN, trade via MAIN
|
||
#
|
||
# "{\"orderId\":\"617ab9949e7b3b0001948081\",\"txId\":\"0x7a06b16bbd6b03dbc3d96df5683b15229fc35e7184fd7179a5f3a310bd67d1fa@default@0\"}"
|
||
#
|
||
# sell trade
|
||
#
|
||
# "{\"symbol\":\"ETH-USDT\",\"orderId\":\"617adcd1eb3fa20001dd29a1\",\"tradeId\":\"617adcd12e113d2b91222ff9\"}"
|
||
#
|
||
referenceId = None
|
||
if context is not None and context != '':
|
||
try:
|
||
parsed = json.loads(context)
|
||
orderId = self.safe_string(parsed, 'orderId')
|
||
tradeId = self.safe_string(parsed, 'tradeId')
|
||
# transactions only have an orderId but for trades we wish to use tradeId
|
||
if tradeId is not None:
|
||
referenceId = tradeId
|
||
else:
|
||
referenceId = orderId
|
||
except Exception as exc:
|
||
referenceId = context
|
||
fee = None
|
||
feeCost = self.safe_string(item, 'fee')
|
||
feeCurrency = None
|
||
if feeCost != '0':
|
||
feeCurrency = code
|
||
fee = {'cost': self.parse_number(feeCost), 'currency': feeCurrency}
|
||
return self.safe_ledger_entry({
|
||
'info': item,
|
||
'id': id,
|
||
'direction': direction,
|
||
'account': account,
|
||
'referenceId': referenceId,
|
||
'referenceAccount': account,
|
||
'type': type,
|
||
'currency': code,
|
||
'amount': amount,
|
||
'timestamp': timestamp,
|
||
'datetime': datetime,
|
||
'before': None,
|
||
'after': balanceAfter, # None
|
||
'status': None,
|
||
'fee': fee,
|
||
}, currency)
|
||
|
||
async 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://www.kucoin.com/docs/rest/account/basic-info/get-account-ledgers-spot-margin
|
||
https://www.kucoin.com/docs/rest/account/basic-info/get-account-ledgers-trade_hf
|
||
https://www.kucoin.com/docs/rest/account/basic-info/get-account-ledgers-margin_hf
|
||
|
||
:param str [code]: unified currency code, default is None
|
||
:param int [since]: timestamp in ms of the earliest ledger entry, default is None
|
||
:param int [limit]: max number of ledger entries to return, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.hf]: default False, when True will fetch ledger entries for the high frequency trading account
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
||
"""
|
||
await self.load_markets()
|
||
await self.load_accounts()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate')
|
||
hf = None
|
||
hf, params = self.handle_hf_and_params(params)
|
||
if paginate:
|
||
return await self.fetch_paginated_call_dynamic('fetchLedger', code, since, limit, params)
|
||
request: dict = {
|
||
# 'currency': currency['id'], # can choose up to 10, if not provided returns for all currencies by default
|
||
# 'direction': 'in', # 'out'
|
||
# 'bizType': 'DEPOSIT', # DEPOSIT, WITHDRAW, TRANSFER, SUB_TRANSFER,TRADE_EXCHANGE, MARGIN_EXCHANGE, KUCOIN_BONUS(optional)
|
||
# 'startAt': since,
|
||
# 'endAt': exchange.milliseconds(),
|
||
}
|
||
if since is not None:
|
||
request['startAt'] = since
|
||
# atm only single currency retrieval is supported
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['currency'] = currency['id']
|
||
request, params = self.handle_until_option('endAt', request, params)
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('fetchLedger', params)
|
||
response = None
|
||
if hf:
|
||
if marginMode is not None:
|
||
response = await self.privateGetHfMarginAccountLedgers(self.extend(request, params))
|
||
else:
|
||
response = await self.privateGetHfAccountsLedgers(self.extend(request, params))
|
||
else:
|
||
response = await self.privateGetAccountsLedgers(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code":"200000",
|
||
# "data":{
|
||
# "currentPage":1,
|
||
# "pageSize":50,
|
||
# "totalNum":1,
|
||
# "totalPage":1,
|
||
# "items":[
|
||
# {
|
||
# "id":"617cc528729f5f0001c03ceb",
|
||
# "currency":"GAS",
|
||
# "amount":"0.00000339",
|
||
# "fee":"0",
|
||
# "balance":"0",
|
||
# "accountType":"MAIN",
|
||
# "bizType":"Distribution",
|
||
# "direction":"in",
|
||
# "createdAt":1635566888183,
|
||
# "context":"{\"orderId\":\"617cc47a1c47ed0001ce3606\",\"description\":\"Holding NEO,distribute GAS(2021/10/30)\"}"
|
||
# }
|
||
# {
|
||
# "id": "611a1e7c6a053300067a88d9",//unique key
|
||
# "currency": "USDT", #Currency
|
||
# "amount": "10.00059547", #Change amount of the funds
|
||
# "fee": "0", #Deposit or withdrawal fee
|
||
# "balance": "0", #Total assets of a currency
|
||
# "accountType": "MAIN", #Account Type
|
||
# "bizType": "Loans Repaid", #business type
|
||
# "direction": "in", #side, in or out
|
||
# "createdAt": 1629101692950, #Creation time
|
||
# "context": "{\"borrowerUserId\":\"601ad03e50dc810006d242ea\",\"loanRepayDetailNo\":\"611a1e7cc913d000066cf7ec\"}"
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
dataList = self.safe_list(response, 'data')
|
||
if dataList is not None:
|
||
return self.parse_ledger(dataList, currency, since, limit)
|
||
data = self.safe_dict(response, 'data')
|
||
items = self.safe_list(data, 'items', [])
|
||
return self.parse_ledger(items, currency, since, limit)
|
||
|
||
def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
|
||
versions = self.safe_dict(self.options, 'versions', {})
|
||
apiVersions = self.safe_dict(versions, api, {})
|
||
methodVersions = self.safe_dict(apiVersions, method, {})
|
||
defaultVersion = self.safe_string(methodVersions, path, self.options['version'])
|
||
version = self.safe_string(params, 'version', defaultVersion)
|
||
if version == 'v3' and ('v3' in config):
|
||
return config['v3']
|
||
elif version == 'v2' and ('v2' in config):
|
||
return config['v2']
|
||
elif version == 'v1' and ('v1' in config):
|
||
return config['v1']
|
||
return self.safe_value(config, 'cost', 1)
|
||
|
||
def parse_borrow_rate(self, info, currency: Currency = None):
|
||
#
|
||
# {
|
||
# "tradeId": "62db2dcaff219600012b56cd",
|
||
# "currency": "USDT",
|
||
# "size": "10",
|
||
# "dailyIntRate": "0.00003",
|
||
# "term": 7,
|
||
# "timestamp": 1658531274508488480
|
||
# },
|
||
#
|
||
# {
|
||
# "createdAt": 1697783812257,
|
||
# "currency": "XMR",
|
||
# "interestAmount": "0.1",
|
||
# "dayRatio": "0.001"
|
||
# }
|
||
#
|
||
timestampId = self.safe_string_2(info, 'createdAt', 'timestamp')
|
||
timestamp = self.parse_to_int(timestampId[0:13])
|
||
currencyId = self.safe_string(info, 'currency')
|
||
return {
|
||
'currency': self.safe_currency_code(currencyId, currency),
|
||
'rate': self.safe_number_2(info, 'dailyIntRate', 'dayRatio'),
|
||
'period': 86400000,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'info': info,
|
||
}
|
||
|
||
async def fetch_borrow_interest(self, code: Str = None, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[BorrowInterest]:
|
||
"""
|
||
fetch the interest owed by the user for borrowing currency for margin trading
|
||
|
||
https://docs.kucoin.com/#get-repay-record
|
||
https://docs.kucoin.com/#query-isolated-margin-account-info
|
||
|
||
:param str [code]: unified currency code
|
||
:param str [symbol]: unified market symbol, required for isolated margin
|
||
:param int [since]: the earliest time in ms to fetch borrrow interest for
|
||
:param int [limit]: the maximum number of structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.marginMode]: 'cross' or 'isolated' default is 'cross'
|
||
:returns dict[]: a list of `borrow interest structures <https://docs.ccxt.com/#/?id=borrow-interest-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('fetchBorrowInterest', params, 'cross')
|
||
request: dict = {}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
if marginMode == 'isolated':
|
||
request['balanceCurrency'] = currency['id']
|
||
else:
|
||
request['quoteCurrency'] = currency['id']
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
response = None
|
||
if marginMode == 'isolated':
|
||
response = await self.privateGetIsolatedAccounts(self.extend(request, params))
|
||
else:
|
||
response = await self.privateGetMarginAccounts(self.extend(request, params))
|
||
#
|
||
# Cross
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "totalAssetOfQuoteCurrency": "0",
|
||
# "totalLiabilityOfQuoteCurrency": "0",
|
||
# "debtRatio": "0",
|
||
# "status": "EFFECTIVE",
|
||
# "accounts": [
|
||
# {
|
||
# "currency": "1INCH",
|
||
# "total": "0",
|
||
# "available": "0",
|
||
# "hold": "0",
|
||
# "liability": "0",
|
||
# "maxBorrowSize": "0",
|
||
# "borrowEnabled": True,
|
||
# "transferInEnabled": True
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
# Isolated
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "totalConversionBalance": "0.02138647",
|
||
# "liabilityConversionBalance": "0.01480001",
|
||
# "assets": [
|
||
# {
|
||
# "symbol": "MANA-USDT",
|
||
# "debtRatio": "0",
|
||
# "status": "BORROW",
|
||
# "baseAsset": {
|
||
# "currency": "MANA",
|
||
# "borrowEnabled": True,
|
||
# "repayEnabled": True,
|
||
# "transferEnabled": True,
|
||
# "borrowed": "0",
|
||
# "totalAsset": "0",
|
||
# "available": "0",
|
||
# "hold": "0",
|
||
# "maxBorrowSize": "1000"
|
||
# },
|
||
# "quoteAsset": {
|
||
# "currency": "USDT",
|
||
# "borrowEnabled": True,
|
||
# "repayEnabled": True,
|
||
# "transferEnabled": True,
|
||
# "borrowed": "0",
|
||
# "totalAsset": "0",
|
||
# "available": "0",
|
||
# "hold": "0",
|
||
# "maxBorrowSize": "50000"
|
||
# }
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
assets = self.safe_list(data, 'assets', []) if (marginMode == 'isolated') else self.safe_list(data, 'accounts', [])
|
||
interest = self.parse_borrow_interests(assets, market)
|
||
filteredByCurrency = self.filter_by_currency_since_limit(interest, code, since, limit)
|
||
return self.filter_by_symbol_since_limit(filteredByCurrency, symbol, since, limit)
|
||
|
||
def parse_borrow_interest(self, info: dict, market: Market = None) -> BorrowInterest:
|
||
#
|
||
# Cross
|
||
#
|
||
# {
|
||
# "currency": "1INCH",
|
||
# "total": "0",
|
||
# "available": "0",
|
||
# "hold": "0",
|
||
# "liability": "0",
|
||
# "maxBorrowSize": "0",
|
||
# "borrowEnabled": True,
|
||
# "transferInEnabled": True
|
||
# }
|
||
#
|
||
# Isolated
|
||
#
|
||
# {
|
||
# "symbol": "MANA-USDT",
|
||
# "debtRatio": "0",
|
||
# "status": "BORROW",
|
||
# "baseAsset": {
|
||
# "currency": "MANA",
|
||
# "borrowEnabled": True,
|
||
# "repayEnabled": True,
|
||
# "transferEnabled": True,
|
||
# "borrowed": "0",
|
||
# "totalAsset": "0",
|
||
# "available": "0",
|
||
# "hold": "0",
|
||
# "maxBorrowSize": "1000"
|
||
# },
|
||
# "quoteAsset": {
|
||
# "currency": "USDT",
|
||
# "borrowEnabled": True,
|
||
# "repayEnabled": True,
|
||
# "transferEnabled": True,
|
||
# "borrowed": "0",
|
||
# "totalAsset": "0",
|
||
# "available": "0",
|
||
# "hold": "0",
|
||
# "maxBorrowSize": "50000"
|
||
# }
|
||
# }
|
||
#
|
||
marketId = self.safe_string(info, 'symbol')
|
||
marginMode = 'cross' if (marketId is None) else 'isolated'
|
||
market = self.safe_market(marketId, market)
|
||
symbol = self.safe_string(market, 'symbol')
|
||
timestamp = self.safe_integer(info, 'createdAt')
|
||
isolatedBase = self.safe_dict(info, 'baseAsset', {})
|
||
amountBorrowed = None
|
||
interest = None
|
||
currencyId = None
|
||
if marginMode == 'isolated':
|
||
amountBorrowed = self.safe_number(isolatedBase, 'liability')
|
||
interest = self.safe_number(isolatedBase, 'interest')
|
||
currencyId = self.safe_string(isolatedBase, 'currency')
|
||
else:
|
||
amountBorrowed = self.safe_number(info, 'liability')
|
||
interest = self.safe_number(info, 'accruedInterest')
|
||
currencyId = self.safe_string(info, 'currency')
|
||
return {
|
||
'info': info,
|
||
'symbol': symbol,
|
||
'currency': self.safe_currency_code(currencyId),
|
||
'interest': interest,
|
||
'interestRate': self.safe_number(info, 'dailyIntRate'),
|
||
'amountBorrowed': amountBorrowed,
|
||
'marginMode': marginMode,
|
||
'timestamp': timestamp, # create time
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
|
||
async def fetch_borrow_rate_histories(self, codes=None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
retrieves a history of a multiple currencies borrow interest rate at specific time slots, returns all currencies if no symbols passed, default is None
|
||
|
||
https://www.kucoin.com/docs/rest/margin-trading/margin-trading-v3-/get-cross-isolated-margin-interest-records
|
||
|
||
:param str[]|None codes: list of unified currency codes, default is None
|
||
:param int [since]: timestamp in ms of the earliest borrowRate, default is None
|
||
:param int [limit]: max number of borrow rate prices to return, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.marginMode]: 'cross' or 'isolated' default is 'cross'
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:returns dict: a dictionary of `borrow rate structures <https://docs.ccxt.com/#/?id=borrow-rate-structure>` indexed by the market symbol
|
||
"""
|
||
await self.load_markets()
|
||
marginResult = self.handle_margin_mode_and_params('fetchBorrowRateHistories', params)
|
||
marginMode = self.safe_string(marginResult, 0, 'cross')
|
||
isIsolated = (marginMode == 'isolated') # True-isolated, False-cross
|
||
request: dict = {
|
||
'isIsolated': isIsolated,
|
||
}
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
if limit is not None:
|
||
request['pageSize'] = limit # default:50, min:10, max:500
|
||
response = await self.privateGetMarginInterest(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "timestamp": 1710829939673,
|
||
# "currentPage": 1,
|
||
# "pageSize": 50,
|
||
# "totalNum": 0,
|
||
# "totalPage": 0,
|
||
# "items": [
|
||
# {
|
||
# "createdAt": 1697783812257,
|
||
# "currency": "XMR",
|
||
# "interestAmount": "0.1",
|
||
# "dayRatio": "0.001"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data')
|
||
rows = self.safe_list(data, 'items', [])
|
||
return self.parse_borrow_rate_histories(rows, codes, since, limit)
|
||
|
||
async def fetch_borrow_rate_history(self, code: str, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
retrieves a history of a currencies borrow interest rate at specific time slots
|
||
|
||
https://www.kucoin.com/docs/rest/margin-trading/margin-trading-v3-/get-cross-isolated-margin-interest-records
|
||
|
||
:param str code: unified currency code
|
||
:param int [since]: timestamp for the earliest borrow rate
|
||
:param int [limit]: the maximum number of `borrow rate structures <https://docs.ccxt.com/#/?id=borrow-rate-structure>` to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.marginMode]: 'cross' or 'isolated' default is 'cross'
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:returns dict[]: an array of `borrow rate structures <https://docs.ccxt.com/#/?id=borrow-rate-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
marginResult = self.handle_margin_mode_and_params('fetchBorrowRateHistories', params)
|
||
marginMode = self.safe_string(marginResult, 0, 'cross')
|
||
isIsolated = (marginMode == 'isolated') # True-isolated, False-cross
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'isIsolated': isIsolated,
|
||
'currency': currency['id'],
|
||
}
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
if limit is not None:
|
||
request['pageSize'] = limit # default:50, min:10, max:500
|
||
response = await self.privateGetMarginInterest(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "timestamp": 1710829939673,
|
||
# "currentPage": 1,
|
||
# "pageSize": 50,
|
||
# "totalNum": 0,
|
||
# "totalPage": 0,
|
||
# "items": [
|
||
# {
|
||
# "createdAt": 1697783812257,
|
||
# "currency": "XMR",
|
||
# "interestAmount": "0.1",
|
||
# "dayRatio": "0.001"
|
||
# }
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data')
|
||
rows = self.safe_list(data, 'items', [])
|
||
return self.parse_borrow_rate_history(rows, code, since, limit)
|
||
|
||
def parse_borrow_rate_histories(self, response, codes, since, limit):
|
||
#
|
||
# [
|
||
# {
|
||
# "createdAt": 1697783812257,
|
||
# "currency": "XMR",
|
||
# "interestAmount": "0.1",
|
||
# "dayRatio": "0.001"
|
||
# }
|
||
# ]
|
||
#
|
||
borrowRateHistories: dict = {}
|
||
for i in range(0, len(response)):
|
||
item = response[i]
|
||
code = self.safe_currency_code(self.safe_string(item, 'currency'))
|
||
if codes is None or self.in_array(code, codes):
|
||
if not (code in borrowRateHistories):
|
||
borrowRateHistories[code] = []
|
||
borrowRateStructure = self.parse_borrow_rate(item)
|
||
borrowRateHistoriesCode = borrowRateHistories[code]
|
||
borrowRateHistoriesCode.append(borrowRateStructure)
|
||
keys = list(borrowRateHistories.keys())
|
||
for i in range(0, len(keys)):
|
||
code = keys[i]
|
||
borrowRateHistories[code] = self.filter_by_currency_since_limit(borrowRateHistories[code], code, since, limit)
|
||
return borrowRateHistories
|
||
|
||
async def borrow_cross_margin(self, code: str, amount: float, params={}):
|
||
"""
|
||
create a loan to borrow margin
|
||
|
||
https://docs.kucoin.com/#1-margin-borrowing
|
||
|
||
:param str code: unified currency code of the currency to borrow
|
||
:param float amount: the amount to borrow
|
||
:param dict [params]: extra parameters specific to the exchange API endpoints
|
||
:param str [params.timeInForce]: either IOC or FOK
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
'size': self.currency_to_precision(code, amount),
|
||
'timeInForce': 'FOK',
|
||
}
|
||
response = await self.privatePostMarginBorrow(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "success": True,
|
||
# "code": "200",
|
||
# "msg": "success",
|
||
# "retry": False,
|
||
# "data": {
|
||
# "orderNo": "5da6dba0f943c0c81f5d5db5",
|
||
# "actualSize": 10
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_margin_loan(data, currency)
|
||
|
||
async def borrow_isolated_margin(self, symbol: str, code: str, amount: float, params={}):
|
||
"""
|
||
create a loan to borrow margin
|
||
|
||
https://docs.kucoin.com/#1-margin-borrowing
|
||
|
||
:param str symbol: unified market symbol, required for isolated margin
|
||
:param str code: unified currency code of the currency to borrow
|
||
:param float amount: the amount to borrow
|
||
:param dict [params]: extra parameters specific to the exchange API endpoints
|
||
:param str [params.timeInForce]: either IOC or FOK
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
'size': self.currency_to_precision(code, amount),
|
||
'symbol': market['id'],
|
||
'timeInForce': 'FOK',
|
||
'isIsolated': True,
|
||
}
|
||
response = await self.privatePostMarginBorrow(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "success": True,
|
||
# "code": "200",
|
||
# "msg": "success",
|
||
# "retry": False,
|
||
# "data": {
|
||
# "orderNo": "5da6dba0f943c0c81f5d5db5",
|
||
# "actualSize": 10
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_margin_loan(data, currency)
|
||
|
||
async def repay_cross_margin(self, code: str, amount, params={}):
|
||
"""
|
||
repay borrowed margin and interest
|
||
|
||
https://docs.kucoin.com/#2-repayment
|
||
|
||
:param str code: unified currency code of the currency to repay
|
||
:param float amount: the amount to repay
|
||
:param dict [params]: extra parameters specific to the exchange API endpoints
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
'size': self.currency_to_precision(code, amount),
|
||
}
|
||
response = await self.privatePostMarginRepay(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "success": True,
|
||
# "code": "200",
|
||
# "msg": "success",
|
||
# "retry": False,
|
||
# "data": {
|
||
# "orderNo": "5da6dba0f943c0c81f5d5db5",
|
||
# "actualSize": 10
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_margin_loan(data, currency)
|
||
|
||
async def repay_isolated_margin(self, symbol: str, code: str, amount, params={}):
|
||
"""
|
||
repay borrowed margin and interest
|
||
|
||
https://docs.kucoin.com/#2-repayment
|
||
|
||
:param str symbol: unified market symbol
|
||
:param str code: unified currency code of the currency to repay
|
||
:param float amount: the amount to repay
|
||
:param dict [params]: extra parameters specific to the exchange API endpoints
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
'size': self.currency_to_precision(code, amount),
|
||
'symbol': market['id'],
|
||
'isIsolated': True,
|
||
}
|
||
response = await self.privatePostMarginRepay(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "success": True,
|
||
# "code": "200",
|
||
# "msg": "success",
|
||
# "retry": False,
|
||
# "data": {
|
||
# "orderNo": "5da6dba0f943c0c81f5d5db5",
|
||
# "actualSize": 10
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_margin_loan(data, currency)
|
||
|
||
def parse_margin_loan(self, info, currency: Currency = None):
|
||
#
|
||
# {
|
||
# "orderNo": "5da6dba0f943c0c81f5d5db5",
|
||
# "actualSize": 10
|
||
# }
|
||
#
|
||
timestamp = self.milliseconds()
|
||
currencyId = self.safe_string(info, 'currency')
|
||
return {
|
||
'id': self.safe_string(info, 'orderNo'),
|
||
'currency': self.safe_currency_code(currencyId, currency),
|
||
'amount': self.safe_number(info, 'actualSize'),
|
||
'symbol': None,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'info': info,
|
||
}
|
||
|
||
async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
||
"""
|
||
fetch deposit and withdraw fees - *IMPORTANT* use fetchDepositWithdrawFee to get more in-depth info
|
||
|
||
https://docs.kucoin.com/#get-currencies
|
||
|
||
: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>`
|
||
"""
|
||
await self.load_markets()
|
||
response = await self.publicGetCurrencies(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "currency": "CSP",
|
||
# "name": "CSP",
|
||
# "fullName": "Caspian",
|
||
# "precision": 8,
|
||
# "confirms": 12,
|
||
# "contractAddress": "0xa6446d655a0c34bc4f05042ee88170d056cbaf45",
|
||
# "withdrawalMinSize": "2000",
|
||
# "withdrawalMinFee": "1000",
|
||
# "isWithdrawEnabled": True,
|
||
# "isDepositEnabled": True,
|
||
# "isMarginEnabled": False,
|
||
# "isDebitEnabled": False
|
||
# },
|
||
# ]
|
||
#
|
||
data = self.safe_list(response, 'data', [])
|
||
return self.parse_deposit_withdraw_fees(data, codes, 'currency')
|
||
|
||
async def set_leverage(self, leverage: int, symbol: Str = None, params={}):
|
||
"""
|
||
set the level of leverage for a market
|
||
|
||
https://www.kucoin.com/docs/rest/margin-trading/margin-trading-v3-/modify-leverage-multiplier
|
||
|
||
:param int [leverage]: New leverage multiplier. Must be greater than 1 and up to two decimal places, and cannot be less than the user's current debt leverage or greater than the system's maximum leverage
|
||
:param str [symbol]: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: response from the exchange
|
||
"""
|
||
await self.load_markets()
|
||
market = None
|
||
marketType: Str = None
|
||
marketType, params = self.handle_market_type_and_params('setLeverage', None, params)
|
||
if (symbol is not None) or marketType != 'spot':
|
||
market = self.market(symbol)
|
||
if market['contract']:
|
||
raise NotSupported(self.id + ' setLeverage currently supports only spot margin')
|
||
marginMode: Str = None
|
||
marginMode, params = self.handle_margin_mode_and_params('setLeverage', params)
|
||
if marginMode is None:
|
||
raise ArgumentsRequired(self.id + ' setLeverage requires a marginMode parameter')
|
||
request: dict = {}
|
||
if marginMode == 'isolated' and symbol is None:
|
||
raise ArgumentsRequired(self.id + ' setLeverage requires a symbol parameter for isolated margin')
|
||
if symbol is not None:
|
||
request['symbol'] = market['id']
|
||
request['leverage'] = str(leverage)
|
||
request['isIsolated'] = (marginMode == 'isolated')
|
||
return await self.privatePostPositionUpdateUserLeverage(self.extend(request, params))
|
||
|
||
async def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
|
||
"""
|
||
fetch the current funding rate
|
||
|
||
https://www.kucoin.com/docs-new/rest/ua/get-current-funding-rate
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
||
"""
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = await self.utaGetMarketFundingRate(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "symbol": ".XBTUSDTMFPI8H",
|
||
# "nextFundingRate": 7.4E-5,
|
||
# "fundingTime": 1762444800000,
|
||
# "fundingRateCap": 0.003,
|
||
# "fundingRateFloor": -0.003
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
return self.parse_funding_rate(data, market)
|
||
|
||
def parse_funding_rate(self, data, market: Market = None) -> FundingRate:
|
||
#
|
||
# {
|
||
# "symbol": ".XBTUSDTMFPI8H",
|
||
# "nextFundingRate": 7.4E-5,
|
||
# "fundingTime": 1762444800000,
|
||
# "fundingRateCap": 0.003,
|
||
# "fundingRateFloor": -0.003
|
||
# }
|
||
#
|
||
fundingTimestamp = self.safe_integer(data, 'fundingTime')
|
||
marketId = self.safe_string(data, 'symbol')
|
||
return {
|
||
'info': data,
|
||
'symbol': self.safe_symbol(marketId, market, None, 'contract'),
|
||
'markPrice': None,
|
||
'indexPrice': None,
|
||
'interestRate': None,
|
||
'estimatedSettlePrice': None,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'fundingRate': self.safe_number(data, 'nextFundingRate'),
|
||
'fundingTimestamp': fundingTimestamp,
|
||
'fundingDatetime': self.iso8601(fundingTimestamp),
|
||
'nextFundingRate': None,
|
||
'nextFundingTimestamp': None,
|
||
'nextFundingDatetime': None,
|
||
'previousFundingRate': None,
|
||
'previousFundingTimestamp': None,
|
||
'previousFundingDatetime': None,
|
||
'interval': None,
|
||
}
|
||
|
||
async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches historical funding rate prices
|
||
|
||
https://www.kucoin.com/docs-new/rest/ua/get-history-funding-rate
|
||
|
||
:param str symbol: unified symbol of the market to fetch the funding rate history for
|
||
:param int [since]: not used by kucuoinfutures
|
||
:param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: end time in ms
|
||
: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')
|
||
if since is None:
|
||
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a since argument')
|
||
await self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
until = self.safe_integer(params, 'until')
|
||
params = self.omit(params, 'until')
|
||
if since is not None:
|
||
request['startAt'] = since
|
||
if until is None:
|
||
request['endAt'] = self.milliseconds()
|
||
if until is not None:
|
||
request['endAt'] = until
|
||
response = await self.utaGetMarketFundingRateHistory(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200000",
|
||
# "data": {
|
||
# "symbol": "XBTUSDTM",
|
||
# "list": [
|
||
# {
|
||
# "fundingRate": 7.6E-5,
|
||
# "ts": 1706097600000
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data', {})
|
||
result = self.safe_list(data, 'list', [])
|
||
return self.parse_funding_rate_histories(result, market, since, limit)
|
||
|
||
def parse_funding_rate_history(self, info, market: Market = None):
|
||
#
|
||
# {
|
||
# "fundingRate": 7.6E-5,
|
||
# "ts": 1706097600000
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(info, 'ts')
|
||
return {
|
||
'info': info,
|
||
'symbol': self.safe_symbol(None, market),
|
||
'fundingRate': self.safe_number(info, 'fundingRate'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
|
||
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
||
#
|
||
# the v2 URL is https://openapi-v2.kucoin.com/api/v1/endpoint
|
||
# ↑ ↑
|
||
# ↑ ↑
|
||
#
|
||
versions = self.safe_dict(self.options, 'versions', {})
|
||
apiVersions = self.safe_dict(versions, api, {})
|
||
methodVersions = self.safe_dict(apiVersions, method, {})
|
||
defaultVersion = self.safe_string(methodVersions, path, self.options['version'])
|
||
version = self.safe_string(params, 'version', defaultVersion)
|
||
params = self.omit(params, 'version')
|
||
endpoint = '/api/' + version + '/' + self.implode_params(path, params)
|
||
if api == 'webExchange':
|
||
endpoint = '/' + self.implode_params(path, params)
|
||
if api == 'earn':
|
||
endpoint = '/api/v1/' + self.implode_params(path, params)
|
||
isUtaPrivate = False
|
||
if api == 'uta':
|
||
endpoint = '/api/ua/v1/' + self.implode_params(path, params)
|
||
if path == 'market/orderbook':
|
||
isUtaPrivate = True
|
||
query = self.omit(params, self.extract_params(path))
|
||
endpart = ''
|
||
headers = headers if (headers is not None) else {}
|
||
url = self.urls['api'][api]
|
||
if not self.is_empty(query):
|
||
if ((method == 'GET') or (method == 'DELETE')) and (path != 'orders/multi-cancel'):
|
||
endpoint += '?' + self.rawencode(query)
|
||
else:
|
||
body = self.json(query)
|
||
endpart = body
|
||
headers['Content-Type'] = 'application/json'
|
||
url = url + endpoint
|
||
isFuturePrivate = (api == 'futuresPrivate')
|
||
isPrivate = (api == 'private')
|
||
isBroker = (api == 'broker')
|
||
isEarn = (api == 'earn')
|
||
if isPrivate or isFuturePrivate or isBroker or isEarn or isUtaPrivate:
|
||
self.check_required_credentials()
|
||
timestamp = str(self.nonce())
|
||
headers = self.extend({
|
||
'KC-API-KEY-VERSION': '2',
|
||
'KC-API-KEY': self.apiKey,
|
||
'KC-API-TIMESTAMP': timestamp,
|
||
}, headers)
|
||
apiKeyVersion = self.safe_string(headers, 'KC-API-KEY-VERSION')
|
||
if apiKeyVersion == '2':
|
||
passphrase = self.hmac(self.encode(self.password), self.encode(self.secret), hashlib.sha256, 'base64')
|
||
headers['KC-API-PASSPHRASE'] = passphrase
|
||
else:
|
||
headers['KC-API-PASSPHRASE'] = self.password
|
||
payload = timestamp + method + endpoint + endpart
|
||
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64')
|
||
headers['KC-API-SIGN'] = signature
|
||
partner = self.safe_dict(self.options, 'partner', {})
|
||
partner = self.safe_value(partner, 'future', partner) if isFuturePrivate else self.safe_value(partner, 'spot', partner)
|
||
partnerId = self.safe_string(partner, 'id')
|
||
partnerSecret = self.safe_string_2(partner, 'secret', 'key')
|
||
if (partnerId is not None) and (partnerSecret is not None):
|
||
partnerPayload = timestamp + partnerId + self.apiKey
|
||
partnerSignature = self.hmac(self.encode(partnerPayload), self.encode(partnerSecret), hashlib.sha256, 'base64')
|
||
headers['KC-API-PARTNER-SIGN'] = partnerSignature
|
||
headers['KC-API-PARTNER'] = partnerId
|
||
headers['KC-API-PARTNER-VERIFY'] = 'true'
|
||
if isBroker:
|
||
brokerName = self.safe_string(partner, 'name')
|
||
if brokerName is not None:
|
||
headers['KC-BROKER-NAME'] = brokerName
|
||
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):
|
||
if not response:
|
||
self.throw_broadly_matched_exception(self.exceptions['broad'], body, body)
|
||
return None
|
||
#
|
||
# bad
|
||
# {"code": "400100", "msg": "validation.createOrder.clientOidIsRequired"}
|
||
# good
|
||
# {code: '200000', data: {...}}
|
||
#
|
||
errorCode = self.safe_string(response, 'code')
|
||
message = self.safe_string_2(response, 'msg', 'data', '')
|
||
feedback = self.id + ' ' + body
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
||
self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
|
||
if errorCode != '200000' and errorCode != '200':
|
||
raise ExchangeError(feedback)
|
||
return None
|