/python/ccxt/kucoin.py
https://github.com/kroitor/ccxt · Python · 1729 lines · 1046 code · 43 blank · 640 comment · 130 complexity · 8baba9fe9caddce84265d1f560f020f6 MD5 · raw file
- # -*- coding: utf-8 -*-
- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
- # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
- from ccxt.base.exchange import Exchange
- import hashlib
- import math
- 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 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
- class kucoin(Exchange):
- def describe(self):
- return self.deep_extend(super(kucoin, self).describe(), {
- 'id': 'kucoin',
- 'name': 'KuCoin',
- 'countries': ['SC'],
- 'rateLimit': 334,
- 'version': 'v2',
- 'certified': False,
- 'pro': True,
- 'comment': 'Platform 2.0',
- 'has': {
- 'CORS': False,
- 'fetchStatus': True,
- 'fetchTime': True,
- 'fetchMarkets': True,
- 'fetchCurrencies': True,
- 'fetchTicker': True,
- 'fetchTickers': True,
- 'fetchOrderBook': True,
- 'fetchOrder': True,
- 'fetchClosedOrders': True,
- 'fetchOpenOrders': True,
- 'fetchDepositAddress': True,
- 'createDepositAddress': True,
- 'withdraw': True,
- 'fetchDeposits': True,
- 'fetchWithdrawals': True,
- 'fetchBalance': True,
- 'fetchTrades': True,
- 'fetchMyTrades': True,
- 'createOrder': True,
- 'cancelOrder': True,
- 'fetchAccounts': True,
- 'fetchFundingFee': True,
- 'fetchOHLCV': True,
- 'fetchLedger': True,
- },
- 'urls': {
- 'logo': 'https://user-images.githubusercontent.com/51840849/87295558-132aaf80-c50e-11ea-9801-a2fb0c57c799.jpg',
- 'referral': 'https://www.kucoin.com/?rcode=E5wkqe',
- 'api': {
- 'public': 'https://openapi-v2.kucoin.com',
- 'private': 'https://openapi-v2.kucoin.com',
- },
- 'test': {
- 'public': 'https://openapi-sandbox.kucoin.com',
- 'private': 'https://openapi-sandbox.kucoin.com',
- },
- 'www': 'https://www.kucoin.com',
- 'doc': [
- 'https://docs.kucoin.com',
- ],
- },
- 'requiredCredentials': {
- 'apiKey': True,
- 'secret': True,
- 'password': True,
- },
- 'api': {
- 'public': {
- 'get': [
- 'timestamp',
- 'status',
- 'symbols',
- 'markets',
- 'market/allTickers',
- 'market/orderbook/level{level}',
- 'market/orderbook/level2',
- 'market/orderbook/level2_20',
- 'market/orderbook/level2_100',
- 'market/orderbook/level3',
- 'market/histories',
- 'market/candles',
- 'market/stats',
- 'currencies',
- 'currencies/{currency}',
- 'prices',
- 'mark-price/{symbol}/current',
- 'margin/config',
- ],
- 'post': [
- 'bullet-public',
- ],
- },
- 'private': {
- 'get': [
- 'accounts',
- 'accounts/{accountId}',
- 'accounts/{accountId}/ledgers',
- 'accounts/{accountId}/holds',
- 'accounts/transferable',
- 'sub/user',
- 'sub-accounts',
- 'sub-accounts/{subUserId}',
- 'deposit-addresses',
- 'deposits',
- 'hist-deposits',
- 'hist-orders',
- 'hist-withdrawals',
- 'withdrawals',
- 'withdrawals/quotas',
- 'orders',
- 'orders/{orderId}',
- 'limit/orders',
- 'fills',
- 'limit/fills',
- 'margin/account',
- 'margin/borrow',
- 'margin/borrow/outstanding',
- 'margin/borrow/borrow/repaid',
- 'margin/lend/active',
- 'margin/lend/done',
- 'margin/lend/trade/unsettled',
- 'margin/lend/trade/settled',
- 'margin/lend/assets',
- 'margin/market',
- 'margin/margin/trade/last',
- ],
- 'post': [
- 'accounts',
- 'accounts/inner-transfer',
- 'accounts/sub-transfer',
- 'deposit-addresses',
- 'withdrawals',
- 'orders',
- 'orders/multi',
- 'margin/borrow',
- 'margin/repay/all',
- 'margin/repay/single',
- 'margin/lend',
- 'margin/toggle-auto-lend',
- 'bullet-private',
- ],
- 'delete': [
- 'withdrawals/{withdrawalId}',
- 'orders',
- 'orders/{orderId}',
- 'margin/lend/{orderId}',
- ],
- },
- },
- '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',
- },
- 'exceptions': {
- 'exact': {
- '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."}
- 'The withdrawal amount is below the minimum requirement.': ExchangeError, # {"code":"400100","msg":"The withdrawal amount is below the minimum requirement."}
- '400': BadRequest,
- '401': AuthenticationError,
- '403': NotSupported,
- '404': NotSupported,
- '405': NotSupported,
- '429': RateLimitExceeded,
- '500': ExchangeNotAvailable, # Internal Server Error -- We had a problem with our server. Try again later.
- '503': ExchangeNotAvailable,
- '200004': InsufficientFunds,
- '230003': InsufficientFunds, # {"code":"230003","msg":"Balance insufficient!"}
- '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': BadRequest,
- '411100': AccountSuspended,
- '415000': BadRequest, # {"code":"415000","msg":"Unsupported Media Type"}
- '500000': ExchangeError,
- },
- 'broad': {
- 'Exceeded the access frequency': RateLimitExceeded,
- 'require more permission': PermissionDenied,
- },
- },
- 'fees': {
- 'trading': {
- 'tierBased': False,
- 'percentage': True,
- 'taker': 0.001,
- 'maker': 0.001,
- },
- 'funding': {
- 'tierBased': False,
- 'percentage': False,
- 'withdraw': {},
- 'deposit': {},
- },
- },
- 'commonCurrencies': {
- 'HOT': 'HOTNOW',
- 'EDGE': 'DADI', # https://github.com/ccxt/ccxt/issues/5756
- 'WAX': 'WAXP',
- 'TRY': 'Trias',
- },
- 'options': {
- 'version': 'v1',
- 'symbolSeparator': '-',
- 'fetchMyTradesMethod': 'private_get_fills',
- 'fetchBalance': {
- 'type': 'trade', # or 'main'
- },
- # endpoint versions
- 'versions': {
- 'public': {
- 'GET': {
- 'status': 'v1',
- 'market/orderbook/level{level}': 'v1',
- 'market/orderbook/level2': 'v2',
- 'market/orderbook/level2_20': 'v1',
- 'market/orderbook/level2_100': 'v1',
- },
- },
- 'private': {
- 'POST': {
- 'accounts/inner-transfer': 'v2',
- 'accounts/sub-transfer': 'v2',
- },
- },
- },
- },
- })
- def nonce(self):
- return self.milliseconds()
- def load_time_difference(self, params={}):
- response = self.publicGetTimestamp(params)
- after = self.milliseconds()
- kucoinTime = self.safe_integer(response, 'data')
- self.options['timeDifference'] = int(after - kucoinTime)
- return self.options['timeDifference']
- def fetch_time(self, params={}):
- response = self.publicGetTimestamp(params)
- #
- # {
- # "code":"200000",
- # "msg":"success",
- # "data":1546837113087
- # }
- #
- return self.safe_integer(response, 'data')
- def fetch_status(self, params={}):
- response = self.publicGetStatus(params)
- #
- # {
- # "code":"200000",
- # "data":{
- # "msg":"",
- # "status":"open"
- # }
- # }
- #
- data = self.safe_value(response, 'data', {})
- status = self.safe_value(data, 'status')
- if status is not None:
- status = 'ok' if (status == 'open') else 'maintenance'
- self.status = self.extend(self.status, {
- 'status': status,
- 'updated': self.milliseconds(),
- })
- return self.status
- def fetch_markets(self, params={}):
- response = self.publicGetSymbols(params)
- #
- # {
- # quoteCurrency: 'BTC',
- # symbol: 'KCS-BTC',
- # quoteMaxSize: '9999999',
- # quoteIncrement: '0.000001',
- # baseMinSize: '0.01',
- # quoteMinSize: '0.00001',
- # enableTrading: True,
- # priceIncrement: '0.00000001',
- # name: 'KCS-BTC',
- # baseIncrement: '0.01',
- # baseMaxSize: '9999999',
- # baseCurrency: 'KCS'
- # }
- #
- data = response['data']
- result = []
- for i in range(0, len(data)):
- market = data[i]
- id = self.safe_string(market, 'symbol')
- baseId, quoteId = id.split('-')
- base = self.safe_currency_code(baseId)
- quote = self.safe_currency_code(quoteId)
- symbol = base + '/' + quote
- active = self.safe_value(market, 'enableTrading')
- baseMaxSize = self.safe_float(market, 'baseMaxSize')
- baseMinSize = self.safe_float(market, 'baseMinSize')
- quoteMaxSize = self.safe_float(market, 'quoteMaxSize')
- quoteMinSize = self.safe_float(market, 'quoteMinSize')
- # quoteIncrement = self.safe_float(market, 'quoteIncrement')
- precision = {
- 'amount': self.precision_from_string(self.safe_string(market, 'baseIncrement')),
- 'price': self.precision_from_string(self.safe_string(market, 'priceIncrement')),
- }
- limits = {
- 'amount': {
- 'min': baseMinSize,
- 'max': baseMaxSize,
- },
- 'price': {
- 'min': self.safe_float(market, 'priceIncrement'),
- 'max': quoteMaxSize / baseMinSize,
- },
- 'cost': {
- 'min': quoteMinSize,
- 'max': quoteMaxSize,
- },
- }
- result.append({
- 'id': id,
- 'symbol': symbol,
- 'baseId': baseId,
- 'quoteId': quoteId,
- 'base': base,
- 'quote': quote,
- 'active': active,
- 'precision': precision,
- 'limits': limits,
- 'info': market,
- })
- return result
- def fetch_currencies(self, params={}):
- response = self.publicGetCurrencies(params)
- #
- # {
- # precision: 10,
- # name: 'KCS',
- # fullName: 'KCS shares',
- # currency: 'KCS'
- # }
- #
- responseData = response['data']
- result = {}
- for i in range(0, len(responseData)):
- entry = responseData[i]
- id = self.safe_string(entry, 'currency')
- name = self.safe_string(entry, 'fullName')
- code = self.safe_currency_code(id)
- precision = self.safe_integer(entry, 'precision')
- result[code] = {
- 'id': id,
- 'name': name,
- 'code': code,
- 'precision': precision,
- 'info': entry,
- 'active': None,
- 'fee': None,
- 'limits': self.limits,
- }
- return result
- def fetch_accounts(self, params={}):
- response = 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_value(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,
- 'info': account,
- })
- return result
- def fetch_funding_fee(self, code, params={}):
- currencyId = self.currency_id(code)
- request = {
- 'currency': currencyId,
- }
- response = self.privateGetWithdrawalsQuotas(self.extend(request, params))
- data = response['data']
- withdrawFees = {}
- withdrawFees[code] = self.safe_float(data, 'withdrawMinFee')
- return {
- 'info': response,
- 'withdraw': withdrawFees,
- 'deposit': {},
- }
- def parse_ticker(self, ticker, market=None):
- #
- # {
- # symbol: "ETH-BTC",
- # high: "0.019518",
- # vol: "7997.82836194",
- # last: "0.019329",
- # low: "0.019",
- # buy: "0.019329",
- # sell: "0.01933",
- # changePrice: "-0.000139",
- # time: 1580553706304,
- # averagePrice: "0.01926386",
- # changeRate: "-0.0071",
- # volValue: "154.40791568183474"
- # }
- #
- # {
- # "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
- # }
- #
- percentage = self.safe_float(ticker, 'changeRate')
- if percentage is not None:
- percentage = percentage * 100
- last = self.safe_float_2(ticker, 'last', 'lastTradedPrice')
- symbol = None
- marketId = self.safe_string(ticker, 'symbol')
- if marketId is not None:
- if marketId in self.markets_by_id:
- market = self.markets_by_id[marketId]
- symbol = market['symbol']
- else:
- baseId, quoteId = marketId.split('-')
- base = self.safe_currency_code(baseId)
- quote = self.safe_currency_code(quoteId)
- symbol = base + '/' + quote
- if symbol is None:
- if market is not None:
- symbol = market['symbol']
- baseVolume = self.safe_float(ticker, 'vol')
- quoteVolume = self.safe_float(ticker, 'volValue')
- vwap = self.vwap(baseVolume, quoteVolume)
- timestamp = self.safe_integer_2(ticker, 'time', 'datetime')
- return {
- 'symbol': symbol,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'high': self.safe_float(ticker, 'high'),
- 'low': self.safe_float(ticker, 'low'),
- 'bid': self.safe_float(ticker, 'buy'),
- 'bidVolume': None,
- 'ask': self.safe_float(ticker, 'sell'),
- 'askVolume': None,
- 'vwap': vwap,
- 'open': self.safe_float(ticker, 'open'),
- 'close': last,
- 'last': last,
- 'previousClose': None,
- 'change': self.safe_float(ticker, 'changePrice'),
- 'percentage': percentage,
- 'average': self.safe_float(ticker, 'averagePrice'),
- 'baseVolume': baseVolume,
- 'quoteVolume': quoteVolume,
- 'info': ticker,
- }
- def fetch_tickers(self, symbols=None, params={}):
- self.load_markets()
- response = self.publicGetMarketAllTickers(params)
- #
- # {
- # "code": "200000",
- # "data": {
- # "date": 1550661940645,
- # "ticker": [
- # 'buy': '0.00001168',
- # 'changePrice': '-0.00000018',
- # 'changeRate': '-0.0151',
- # 'datetime': 1550661146316,
- # 'high': '0.0000123',
- # 'last': '0.00001169',
- # 'low': '0.00001159',
- # 'sell': '0.00001182',
- # 'symbol': 'LOOM-BTC',
- # 'vol': '44399.5669'
- # },
- # ]
- # }
- #
- data = self.safe_value(response, 'data', {})
- tickers = self.safe_value(data, 'ticker', [])
- result = {}
- for i in range(0, len(tickers)):
- 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(result, 'symbol', symbols)
- def fetch_ticker(self, symbol, params={}):
- self.load_markets()
- market = self.market(symbol)
- request = {
- 'symbol': market['id'],
- }
- response = self.publicGetMarketStats(self.extend(request, params))
- #
- # {
- # "code": "200000",
- # "data": {
- # 'buy': '0.00001168',
- # 'changePrice': '-0.00000018',
- # 'changeRate': '-0.0151',
- # 'datetime': 1550661146316,
- # 'high': '0.0000123',
- # 'last': '0.00001169',
- # 'low': '0.00001159',
- # 'sell': '0.00001182',
- # 'symbol': 'LOOM-BTC',
- # 'vol': '44399.5669'
- # },
- # }
- #
- return self.parse_ticker(response['data'], market)
- def parse_ohlcv(self, ohlcv, market=None):
- #
- # [
- # "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_float(ohlcv, 1),
- self.safe_float(ohlcv, 3),
- self.safe_float(ohlcv, 4),
- self.safe_float(ohlcv, 2),
- self.safe_float(ohlcv, 5),
- ]
- def fetch_ohlcv(self, symbol, timeframe='15m', since=None, limit=None, params={}):
- self.load_markets()
- market = self.market(symbol)
- marketId = market['id']
- request = {
- 'symbol': marketId,
- 'type': self.timeframes[timeframe],
- }
- duration = self.parse_timeframe(timeframe) * 1000
- endAt = self.milliseconds() # required param
- if since is not None:
- request['startAt'] = 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'] = int(int(math.floor(since / 1000)))
- request['endAt'] = int(int(math.floor(endAt / 1000)))
- response = 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"],
- # ]
- # }
- #
- data = self.safe_value(response, 'data', [])
- return self.parse_ohlcvs(data, market, timeframe, since, limit)
- def create_deposit_address(self, code, params={}):
- self.load_markets()
- currencyId = self.currency_id(code)
- request = {'currency': currencyId}
- response = self.privatePostDepositAddresses(self.extend(request, params))
- # BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}}
- # BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}}
- data = self.safe_value(response, 'data', {})
- address = self.safe_string(data, '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:', '')
- tag = self.safe_string(data, 'memo')
- self.check_address(address)
- return {
- 'info': response,
- 'currency': code,
- 'address': address,
- 'tag': tag,
- }
- def fetch_deposit_address(self, code, params={}):
- self.load_markets()
- currencyId = self.currency_id(code)
- request = {'currency': currencyId}
- response = self.privateGetDepositAddresses(self.extend(request, params))
- # BCH {"code":"200000","data":{"address":"bitcoincash:qza3m4nj9rx7l9r0cdadfqxts6f92shvhvr5ls4q7z","memo":""}}
- # BTC {"code":"200000","data":{"address":"36SjucKqQpQSvsak9A7h6qzFjrVXpRNZhE","memo":""}}
- data = self.safe_value(response, 'data', {})
- address = self.safe_string(data, '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:', '')
- tag = self.safe_string(data, 'memo')
- self.check_address(address)
- return {
- 'info': response,
- 'currency': code,
- 'address': address,
- 'tag': tag,
- }
- def fetch_l3_order_book(self, symbol, limit=None, params={}):
- return self.fetch_order_book(symbol, limit, {'level': 3})
- def fetch_order_book(self, symbol, limit=None, params={}):
- level = self.safe_integer(params, 'level', 2)
- levelLimit = str(level)
- if levelLimit == '2':
- if limit is not None:
- if (limit != 20) and (limit != 100):
- raise ExchangeError(self.id + ' fetchOrderBook limit argument must be None, 20 or 100')
- levelLimit += '_' + str(limit)
- self.load_markets()
- marketId = self.market_id(symbol)
- request = {'symbol': marketId, 'level': levelLimit}
- response = self.publicGetMarketOrderbookLevelLevel(self.extend(request, params))
- #
- # 'market/orderbook/level2'
- # 'market/orderbook/level2_20'
- # 'market/orderbook/level2_100'
- #
- # {
- # "code":"200000",
- # "data":{
- # "sequence":"1583235112106",
- # "asks":[
- # # ...
- # ["0.023197","12.5067468"],
- # ["0.023194","1.8"],
- # ["0.023191","8.1069672"]
- # ],
- # "bids":[
- # ["0.02319","1.6000002"],
- # ["0.023189","2.2842325"],
- # ],
- # "time":1586584067274
- # }
- # }
- #
- # 'market/orderbook/level3'
- #
- # {
- # "code":"200000",
- # "data":{
- # "sequence":"1583731857120",
- # "asks":[
- # # id, price, size, timestamp in nanoseconds
- # ["5e915f8acd26670009675300","6925.7","0.2","1586585482194286069"],
- # ["5e915f8ace35a200090bba48","6925.7","0.001","1586585482229569826"],
- # ["5e915f8a8857740009ca7d33","6926","0.00001819","1586585482149148621"],
- # ],
- # "bids":[
- # ["5e915f8acca406000ac88194","6925.6","0.05","1586585482384384842"],
- # ["5e915f93cd26670009676075","6925.6","0.08","1586585491334914600"],
- # ["5e915f906aa6e200099b49f6","6925.4","0.2","1586585488941126340"],
- # ],
- # "time":1586585492487
- # }
- # }
- #
- data = self.safe_value(response, 'data', {})
- timestamp = self.safe_integer(data, 'time')
- orderbook = self.parse_order_book(data, timestamp, 'bids', 'asks', level - 2, level - 1)
- orderbook['nonce'] = self.safe_integer(data, 'sequence')
- return orderbook
- def create_order(self, symbol, type, side, amount, price=None, params={}):
- self.load_markets()
- marketId = self.market_id(symbol)
- # required param, cannot be used twice
- clientOrderId = self.safe_string_2(params, 'clientOid', 'clientOrderId', self.uuid())
- params = self.omit(params, ['clientOid', 'clientOrderId'])
- request = {
- 'clientOid': clientOrderId,
- 'side': side,
- 'symbol': marketId,
- 'type': type,
- }
- if type != 'market':
- request['price'] = self.price_to_precision(symbol, price)
- request['size'] = self.amount_to_precision(symbol, amount)
- else:
- if self.safe_value(params, 'quoteAmount'):
- # used to create market order by quote amount - https://github.com/ccxt/ccxt/issues/4876
- request['funds'] = self.amount_to_precision(symbol, amount)
- else:
- request['size'] = self.amount_to_precision(symbol, amount)
- response = self.privatePostOrders(self.extend(request, params))
- #
- # {
- # code: '200000',
- # data: {
- # "orderId": "5bd6e9286d99522a52e458de"
- # }
- # }
- #
- data = self.safe_value(response, 'data', {})
- timestamp = self.milliseconds()
- id = self.safe_string(data, 'orderId')
- order = {
- 'id': id,
- 'clientOrderId': clientOrderId,
- 'info': data,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'lastTradeTimestamp': None,
- 'symbol': symbol,
- 'type': type,
- 'side': side,
- 'price': price,
- 'amount': None,
- 'cost': None,
- 'average': None,
- 'filled': None,
- 'remaining': None,
- 'status': None,
- 'fee': None,
- 'trades': None,
- }
- if not self.safe_value(params, 'quoteAmount'):
- order['amount'] = amount
- return order
- def cancel_order(self, id, symbol=None, params={}):
- request = {'orderId': id}
- response = self.privateDeleteOrdersOrderId(self.extend(request, params))
- return response
- def fetch_orders_by_status(self, status, symbol=None, since=None, limit=None, params={}):
- self.load_markets()
- request = {
- 'status': status,
- }
- 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
- response = self.privateGetOrders(self.extend(request, params))
- #
- # {
- # 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
- # },
- # ]
- # }
- # }
- responseData = self.safe_value(response, 'data', {})
- orders = self.safe_value(responseData, 'items', [])
- return self.parse_orders(orders, market, since, limit)
- def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
- return self.fetch_orders_by_status('done', symbol, since, limit, params)
- def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
- return self.fetch_orders_by_status('active', symbol, since, limit, params)
- def fetch_order(self, id, symbol=None, params={}):
- self.load_markets()
- # 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,
- }
- market = None
- if symbol is not None:
- market = self.market(symbol)
- response = self.privateGetOrdersOrderId(self.extend(request, params))
- responseData = self.safe_value(response, 'data')
- return self.parse_order(responseData, market)
- def parse_order(self, order, market=None):
- #
- # fetchOpenOrders, fetchClosedOrders
- #
- # {
- # "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
- # }
- #
- symbol = None
- marketId = self.safe_string(order, 'symbol')
- if marketId is not None:
- if marketId in self.markets_by_id:
- market = self.markets_by_id[marketId]
- symbol = market['symbol']
- else:
- baseId, quoteId = marketId.split('-')
- base = self.safe_currency_code(baseId)
- quote = self.safe_currency_code(quoteId)
- symbol = base + '/' + quote
- market = self.safe_value(self.markets_by_id, marketId)
- if symbol is None:
- if market is not None:
- symbol = market['symbol']
- orderId = self.safe_string(order, 'id')
- type = self.safe_string(order, 'type')
- timestamp = self.safe_integer(order, 'createdAt')
- datetime = self.iso8601(timestamp)
- price = self.safe_float(order, 'price')
- side = self.safe_string(order, 'side')
- feeCurrencyId = self.safe_string(order, 'feeCurrency')
- feeCurrency = self.safe_currency_code(feeCurrencyId)
- feeCost = self.safe_float(order, 'fee')
- amount = self.safe_float(order, 'size')
- filled = self.safe_float(order, 'dealSize')
- cost = self.safe_float(order, 'dealFunds')
- remaining = amount - filled
- # bool
- isActive = self.safe_value(order, 'isActive', False)
- cancelExist = self.safe_value(order, 'cancelExist', False)
- status = 'open' if isActive else 'closed'
- status = 'canceled' if cancelExist else status
- fee = {
- 'currency': feeCurrency,
- 'cost': feeCost,
- }
- if type == 'market':
- if price == 0.0:
- if (cost is not None) and (filled is not None):
- if (cost > 0) and (filled > 0):
- price = cost / filled
- clientOrderId = self.safe_string(order, 'clientOid')
- return {
- 'id': orderId,
- 'clientOrderId': clientOrderId,
- 'symbol': symbol,
- 'type': type,
- 'side': side,
- 'amount': amount,
- 'price': price,
- 'cost': cost,
- 'filled': filled,
- 'remaining': remaining,
- 'timestamp': timestamp,
- 'datetime': datetime,
- 'fee': fee,
- 'status': status,
- 'info': order,
- 'lastTradeTimestamp': None,
- 'average': None,
- 'trades': None,
- }
- def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
- self.load_markets()
- request = {}
- market = None
- if symbol is not None:
- market = self.market(symbol)
- request['symbol'] = market['id']
- if limit is not None:
- request['pageSize'] = limit
- method = self.options['fetchMyTradesMethod']
- parseResponseData = False
- if 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
- 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" as stated in the docs)
- parseResponseData = True
- elif method == 'private_get_hist_orders':
- # despite that self endpoint is called `HistOrders`
- # it returns historical trades instead of orders
- # returns trades earlier than 2019-02-18T00:00:00Z only
- if since is not None:
- request['startAt'] = int(since / 1000)
- else:
- raise ExchangeError(self.id + ' invalid fetchClosedOrder method')
- response = getattr(self, method)(self.extend(request, params))
- #
- # {
- # "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_value(response, 'data', {})
- trades = None
- if parseResponseData:
- trades = data
- else:
- trades = self.safe_value(data, 'items', [])
- return self.parse_trades(trades, market, since, limit)
- def fetch_trades(self, symbol, since=None, limit=None, params={}):
- self.load_markets()
- market = self.market(symbol)
- request = {
- 'symbol': market['id'],
- }
- if since is not None:
- request['startAt'] = int(math.floor(since / 1000))
- if limit is not None:
- request['pageSize'] = limit
- response = self.publicGetMarketHistories(self.extend(request, params))
- #
- # {
- # "code": "200000",
- # "data": [
- # {
- # "sequence": "1548764654235",
- # "side": "sell",
- # "size":"0.6841354",
- # "price":"0.03202",
- # "time":1548848575203567174
- # }
- # ]
- # }
- #
- trades = self.safe_value(response, 'data', [])
- return self.parse_trades(trades, market, since, limit)
- def parse_trade(self, trade, market=None):
- #
- # 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",
- # }
- #
- symbol = None
- marketId = self.safe_string(trade, 'symbol')
- if marketId is not None:
- if marketId in self.markets_by_id:
- market = self.markets_by_id[marketId]
- symbol = market['symbol']
- else:
- baseId, quoteId = marketId.split('-')
- base = self.safe_currency_code(baseId)
- quote = self.safe_currency_code(quoteId)
- symbol = base + '/' + quote
- if symbol is None:
- if market is not None:
- symbol = market['symbol']
- id = self.safe_string_2(trade, 'tradeId', 'id')
- orderId = self.safe_string(trade, 'orderId')
- takerOrMaker = self.safe_string(trade, 'liquidity')
- amount = self.safe_float_2(trade, 'size', 'amount')
- timestamp = self.safe_integer(trade, 'time')
- if timestamp is not None:
- timestamp = 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
- price = self.safe_float_2(trade, 'price', 'dealPrice')
- side = self.safe_string(trade, 'side')
- fee = None
- feeCost = self.safe_float(trade, 'fee')
- if feeCost is not None:
- feeCurrencyId = self.safe_string(trade, 'feeCurrency')
- feeCurrency = self.safe_currency_code(feeCurrencyId)
- if feeCurrency is None:
- if market is not None:
- feeCurrency = market['quote'] if (side == 'sell') else market['base']
- fee = {
- 'cost': feeCost,
- 'currency': feeCurrency,
- 'rate': self.safe_float(trade, 'feeRate'),
- }
- type = self.safe_string(trade, 'type')
- if type == 'match':
- type = None
- cost = self.safe_float_2(trade, 'funds', 'dealValue')
- if cost is None:
- if amount is not None:
- if price is not None:
- cost = amount * price
- return {
- 'info': trade,
- 'id': id,
- 'order': orderId,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'symbol': symbol,
- 'type': type,
- 'takerOrMaker': takerOrMaker,
- 'side': side,
- 'price': price,
- 'amount': amount,
- 'cost': cost,
- 'fee': fee,
- }
- def withdraw(self, code, amount, address, tag=None, params={}):
- self.load_markets()
- self.check_address(address)
- currency = self.currency_id(code)
- request = {
- 'currency': currency,
- 'address': address,
- 'amount': amount,
- }
- if tag is not None:
- request['memo'] = tag
- response = self.privatePostWithdrawals(self.extend(request, params))
- #
- # https://github.com/ccxt/ccxt/issues/5558
- #
- # {
- # "code": 200000,
- # "data": {
- # "withdrawalId": "abcdefghijklmnopqrstuvwxyz"
- # }
- # }
- #
- data = self.safe_value(response, 'data', {})
- return {
- 'id': self.safe_string(data, 'withdrawalId'),
- 'info': response,
- }
- def parse_transaction_status(self, status):
- statuses = {
- 'SUCCESS': 'ok',
- 'PROCESSING': 'ok',
- 'FAILURE': 'failed',
- }
- return self.safe_string(statuses, status)
- def parse_transaction(self, transaction, currency=None):
- #
- # fetchDeposits
- #
- # {
- # "address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989",
- # "memo": "5c247c8a03aa677cea2a251d",
- # "amount": 1,
- # "fee": 0.0001,
- # "currency": "KCS",
- # "isInner": False,
- # "walletTxId": "5bbb57386d99522d9f954c5a@test004",
- # "status": "SUCCESS",
- # "createdAt": 1544178843000,
- # "updatedAt": 1544178891000
- # }
- #
- # fetchWithdrawals
- #
- # {
- # "id": "5c2dc64e03aa675aa263f1ac",
- # "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9",
- # "memo": "",
- # "currency": "ETH",
- # "amount": 1.0000000,
- # "fee": 0.0100000,
- # "walletTxId": "3e2414d82acce78d38be7fe9",
- # "isInner": False,
- # "status": "FAILURE",
- # "createdAt": 1546503758000,
- # "updatedAt": 1546504603000
- # }
- #
- currencyId = self.safe_string(transaction, 'currency')
- code = self.safe_currency_code(currencyId, currency)
- address = self.safe_string(transaction, 'address')
- amount = self.safe_float(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')
- status = self.parse_transaction_status(rawStatus)
- fee = None
- feeCost = self.safe_float(transaction, 'fee')
- if feeCost is not None:
- rate = None
- if amount is not None:
- rate = feeCost / amount
- fee = {
- 'cost': feeCost,
- 'rate': rate,
- 'currency': code,
- }
- tag = self.safe_string(transaction, 'memo')
- timestamp = self.safe_integer_2(transaction, 'createdAt', 'createAt')
- id = self.safe_string(transaction, 'id')
- 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
- return {
- 'id': id,
- 'address': address,
- 'tag': tag,
- 'currency': code,
- 'amount': amount,
- 'txid': txid,
- 'type': type,
- 'status': status,
- 'fee': fee,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'updated': updated,
- 'info': transaction,
- }
- def fetch_deposits(self, code=None, since=None, limit=None, params={}):
- self.load_markets()
- request = {}
- currency = None
- if code is not None:
- currency = self.currency(code)
- request['currency'] = currency['id']
- if limit is not None:
- request['pageSize'] = limit
- method = 'privateGetDeposits'
- if since is not None:
- # if since is earlier than 2019-02-18T00:00:00Z
- if since < 1550448000000:
- request['startAt'] = int(since / 1000)
- method = 'privateGetHistDeposits'
- else:
- request['startAt'] = since
- response = getattr(self, method)(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
- # },
- # #--------------------------------------------------
- # # version 1(historical) deposit response structure
- # {
- # "currency": "BTC",
- # "createAt": 1528536998,
- # "amount": "0.03266638",
- # "walletTxId": "55c643bc2c68d6f17266383ac1be9e454038864b929ae7cee0bc408cc5c869e8@12ffGWmMMD1zA1WbFm7Ho3JZ1w6NYXjpFk@234",
- # "isInner": False,
- # "status": "SUCCESS",
- # }
- # ]
- # }
- # }
- #
- responseData = response['data']['items']
- return self.parse_transactions(responseData, currency, since, limit, {'type': 'deposit'})
- def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
- self.load_markets()
- request = {}
- currency = None
- if code is not None:
- currency = self.currency(code)
- request['currency'] = currency['id']
- if limit is not None:
- request['pageSize'] = limit
- method = 'privateGetWithdrawals'
- if since is not None:
- # if since is earlier than 2019-02-18T00:00:00Z
- if since < 1550448000000:
- request['startAt'] = int(since / 1000)
- method = 'privateGetHistWithdrawals'
- else:
- request['startAt'] = since
- response = getattr(self, method)(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"
- # }
- # ]
- # }
- # }
- #
- responseData = response['data']['items']
- return self.parse_transactions(responseData, currency, since, limit, {'type': 'withdrawal'})
- def fetch_balance(self, params={}):
- self.load_markets()
- type = None
- request = {}
- if 'type' in params:
- type = params['type']
- if type is not None:
- request['type'] = type
- params = self.omit(params, 'type')
- else:
- options = self.safe_value(self.options, 'fetchBalance', {})
- type = self.safe_string(options, 'type', 'trade')
- response = self.privateGetAccounts(self.extend(request, params))
- #
- # {
- # "code":"200000",
- # "data":[
- # {"balance":"0.00009788","available":"0.00009788","holds":"0","currency":"BTC","id":"5c6a4fd399a1d81c4f9cc4d0","type":"trade"},
- # {"balance":"3.41060034","available":"3.41060034","holds":"0","currency":"SOUL","id":"5c6a4d5d99a1d8182d37046d","type":"trade"},
- # {"balance":"0.01562641","available":"0.01562641","holds":"0","currency":"NEO","id":"5c6a4f1199a1d8165a99edb1","type":"trade"},
- # ]
- # }
- #
- data = self.safe_value(response, 'data', [])
- result = {'info': response}
- 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')
- code = self.safe_currency_code(currencyId)
- account = self.account()
- account['total'] = self.safe_float(balance, 'balance')
- account['free'] = self.safe_float(balance, 'available')
- account['used'] = self.safe_float(balance, 'holds')
- result[code] = account
- return self.parse_balance(result)
- def fetch_ledger(self, code=None, since=None, limit=None, params={}):
- if code is None:
- raise ArgumentsRequired(self.id + ' fetchLedger requires a code param')
- self.load_markets()
- self.load_accounts()
- currency = self.currency(code)
- accountId = self.safe_string(params, 'accountId')
- if accountId is None:
- for i in range(0, len(self.accounts)):
- account = self.accounts[i]
- if account['currency'] == code and account['type'] == 'main':
- accountId = account['id']
- break
- if accountId is None:
- raise ExchangeError(self.id + ' ' + code + 'main account is not loaded in loadAccounts')
- request = {
- 'accountId': accountId,
- }
- if since is not None:
- request['startAt'] = int(math.floor(since / 1000))
- response = self.privateGetAccountsAccountIdLedgers(self.extend(request, params))
- #
- # {
- # code: '200000',
- # data: {
- # totalNum: 1,
- # totalPage: 1,
- # pageSize: 50,
- # currentPage: 1,
- # items: [
- # {
- # createdAt: 1561897880000,
- # amount: '0.0111123',
- # bizType: 'Exchange',
- # balance: '0.13224427',
- # fee: '0.0000111',
- # context: '{"symbol":"KCS-ETH","orderId":"5d18ab98c788c6426188296f","tradeId":"5d18ab9818996813f539a806"}',
- # currency: 'ETH',
- # direction: 'out'
- # }
- # ]
- # }
- # }
- #
- items = response['data']['items']
- return self.parse_ledger(items, currency, since, limit)
- def parse_ledger_entry(self, item, currency=None):
- #
- # trade
- #
- # {
- # createdAt: 1561897880000,
- # amount: '0.0111123',
- # bizType: 'Exchange',
- # balance: '0.13224427',
- # fee: '0.0000111',
- # context: '{"symbol":"KCS-ETH","orderId":"5d18ab98c788c6426188296f","tradeId":"5d18ab9818996813f539a806"}',
- # currency: 'ETH',
- # direction: 'out'
- # }
- #
- # withdrawal
- #
- # {
- # createdAt: 1561900264000,
- # amount: '0.14333217',
- # bizType: 'Withdrawal',
- # balance: '0',
- # fee: '0.01',
- # context: '{"orderId":"5d18b4e687111437cf1c48b9","txId":"0x1d136ee065c5c4c5caa293faa90d43e213c953d7cdd575c89ed0b54eb87228b8"}',
- # currency: 'ETH',
- # direction: 'out'
- # }
- #
- currencyId = self.safe_string(item, 'currency')
- code = self.safe_currency_code(currencyId, currency)
- fee = {
- 'cost': self.safe_float(item, 'fee'),
- 'code': code,
- }
- amount = self.safe_float(item, 'amount')
- after = self.safe_float(item, 'balance')
- direction = self.safe_string(item, 'direction')
- before = None
- if after is not None and amount is not None:
- difference = amount if (direction == 'out') else -amount
- before = self.sum(after, difference)
- timestamp = self.safe_integer(item, 'createdAt')
- type = self.parse_ledger_entry_type(self.safe_string(item, 'bizType'))
- contextString = self.safe_string(item, 'context')
- id = None
- referenceId = None
- if self.is_json_encoded_object(contextString):
- context = self.parse_json(contextString)
- id = self.safe_string(context, 'orderId')
- if type == 'trade':
- referenceId = self.safe_string(context, 'tradeId')
- elif type == 'transaction':
- referenceId = self.safe_string(context, 'txId')
- return {
- 'id': id,
- 'currency': code,
- 'account': None,
- 'referenceAccount': None,
- 'referenceId': referenceId,
- 'status': None,
- 'amount': amount,
- 'before': before,
- 'after': after,
- 'fee': fee,
- 'direction': direction,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'type': type,
- 'info': item,
- }
- def parse_ledger_entry_type(self, type):
- types = {
- 'Exchange': 'trade',
- 'Withdrawal': 'transaction',
- 'Deposit': 'transaction',
- 'Transfer': 'transfer',
- }
- return self.safe_string(types, type, type)
- 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_value(self.options, 'versions', {})
- apiVersions = self.safe_value(versions, api)
- methodVersions = self.safe_value(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)
- query = self.omit(params, self.extract_params(path))
- endpart = ''
- headers = headers if (headers is not None) else {}
- if query:
- if method != 'GET':
- body = self.json(query)
- endpart = body
- headers['Content-Type'] = 'application/json'
- else:
- endpoint += '?' + self.urlencode(query)
- url = self.urls['api'][api] + endpoint
- if api == 'private':
- self.check_required_credentials()
- timestamp = str(self.nonce())
- headers = self.extend({
- 'KC-API-KEY': self.apiKey,
- 'KC-API-TIMESTAMP': timestamp,
- 'KC-API-PASSPHRASE': self.password,
- }, headers)
- 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_value(self.options, 'partner', {})
- partnerId = self.safe_string(partner, 'id')
- partnerSecret = self.safe_string(partner, 'secret')
- 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
- return {'url': url, 'method': method, 'body': body, 'headers': headers}
- def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
- if not response:
- self.throw_broadly_matched_exception(self.exceptions['broad'], body, body)
- return
- #
- # bad
- # {"code": "400100", "msg": "validation.createOrder.clientOidIsRequired"}
- # good
- # {code: '200000', data: {...}}
- #
- errorCode = self.safe_string(response, 'code')
- message = self.safe_string(response, 'msg')
- self.throw_exactly_matched_exception(self.exceptions['exact'], message, message)
- self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, message)