/python/ccxt/kucoin.py
https://github.com/kroitor/ccxt · Python · 1729 lines · 1046 code · 43 blank · 640 comment · 130 complexity · 8baba9fe9caddce84265d1f560f020f6 MD5 · raw file
Large files are truncated click here to view the full 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, '…