/python/ccxt/async_support/kraken.py
Python | 1567 lines | 1151 code | 55 blank | 361 comment | 132 complexity | 1471d24c3492bfc458d71fff186e5feb MD5 | raw file
Possible License(s): MIT
- # -*- 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
- # -----------------------------------------------------------------------------
- try:
- basestring # Python 3
- except NameError:
- basestring = str # Python 2
- 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 ArgumentsRequired
- 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 CancelPending
- from ccxt.base.errors import DDoSProtection
- 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 DECIMAL_PLACES
- class kraken(Exchange):
- def describe(self):
- return self.deep_extend(super(kraken, self).describe(), {
- 'id': 'kraken',
- 'name': 'Kraken',
- 'countries': ['US'],
- 'version': '0',
- 'rateLimit': 3000,
- 'certified': True,
- 'pro': True,
- 'has': {
- 'cancelOrder': True,
- 'CORS': False,
- 'createDepositAddress': True,
- 'createOrder': True,
- 'fetchBalance': True,
- 'fetchClosedOrders': True,
- 'fetchCurrencies': True,
- 'fetchDepositAddress': True,
- 'fetchDeposits': True,
- 'fetchLedger': True,
- 'fetchLedgerEntry': True,
- 'fetchMarkets': True,
- 'fetchMyTrades': True,
- 'fetchOHLCV': True,
- 'fetchOpenOrders': True,
- 'fetchOrder': True,
- 'fetchOrderBook': True,
- 'fetchOrderTrades': 'emulated',
- 'fetchTicker': True,
- 'fetchTickers': True,
- 'fetchTime': True,
- 'fetchTrades': True,
- 'fetchTradingFee': True,
- 'fetchTradingFees': True,
- 'fetchWithdrawals': True,
- 'withdraw': True,
- },
- 'marketsByAltname': {},
- 'timeframes': {
- '1m': 1,
- '5m': 5,
- '15m': 15,
- '30m': 30,
- '1h': 60,
- '4h': 240,
- '1d': 1440,
- '1w': 10080,
- '2w': 21600,
- },
- 'urls': {
- 'logo': 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg',
- 'api': {
- 'public': 'https://api.kraken.com',
- 'private': 'https://api.kraken.com',
- 'zendesk': 'https://kraken.zendesk.com/api/v2/help_center/en-us/articles', # use the public zendesk api to receive article bodies and bypass new anti-spam protections
- },
- 'www': 'https://www.kraken.com',
- 'doc': 'https://www.kraken.com/features/api',
- 'fees': 'https://www.kraken.com/en-us/features/fee-schedule',
- },
- 'fees': {
- 'trading': {
- 'tierBased': True,
- 'percentage': True,
- 'taker': 0.26 / 100,
- 'maker': 0.16 / 100,
- 'tiers': {
- 'taker': [
- [0, 0.0026],
- [50000, 0.0024],
- [100000, 0.0022],
- [250000, 0.0020],
- [500000, 0.0018],
- [1000000, 0.0016],
- [2500000, 0.0014],
- [5000000, 0.0012],
- [10000000, 0.0001],
- ],
- 'maker': [
- [0, 0.0016],
- [50000, 0.0014],
- [100000, 0.0012],
- [250000, 0.0010],
- [500000, 0.0008],
- [1000000, 0.0006],
- [2500000, 0.0004],
- [5000000, 0.0002],
- [10000000, 0.0],
- ],
- },
- },
- # self is a bad way of hardcoding fees that change on daily basis
- # hardcoding is now considered obsolete, we will remove all of it eventually
- 'funding': {
- 'tierBased': False,
- 'percentage': False,
- 'withdraw': {
- 'BTC': 0.001,
- 'ETH': 0.005,
- 'XRP': 0.02,
- 'XLM': 0.00002,
- 'LTC': 0.02,
- 'DOGE': 2,
- 'ZEC': 0.00010,
- 'ICN': 0.02,
- 'REP': 0.01,
- 'ETC': 0.005,
- 'MLN': 0.003,
- 'XMR': 0.05,
- 'DASH': 0.005,
- 'GNO': 0.01,
- 'EOS': 0.5,
- 'BCH': 0.001,
- 'XTZ': 0.05,
- 'USD': 5, # if domestic wire
- 'EUR': 5, # if domestic wire
- 'CAD': 10, # CAD EFT Withdrawal
- 'JPY': 300, # if domestic wire
- },
- 'deposit': {
- 'BTC': 0,
- 'ETH': 0,
- 'XRP': 0,
- 'XLM': 0,
- 'LTC': 0,
- 'DOGE': 0,
- 'ZEC': 0,
- 'ICN': 0,
- 'REP': 0,
- 'ETC': 0,
- 'MLN': 0,
- 'XMR': 0,
- 'DASH': 0,
- 'GNO': 0,
- 'EOS': 0,
- 'BCH': 0,
- 'XTZ': 0.05,
- 'USD': 5, # if domestic wire
- 'EUR': 0, # free deposit if EUR SEPA Deposit
- 'CAD': 5, # if domestic wire
- 'JPY': 0, # Domestic Deposit(Free, ¥5,000 deposit minimum)
- },
- },
- },
- 'api': {
- 'zendesk': {
- 'get': [
- # we should really refrain from putting fixed fee numbers and stop hardcoding
- # we will be using their web APIs to scrape all numbers from these articles
- '360000292886', # -What-are-the-deposit-fees-
- '201893608', # -What-are-the-withdrawal-fees-
- ],
- },
- 'public': {
- 'get': [
- 'Assets',
- 'AssetPairs',
- 'Depth',
- 'OHLC',
- 'Spread',
- 'Ticker',
- 'Time',
- 'Trades',
- ],
- },
- 'private': {
- 'post': [
- 'AddOrder',
- 'AddExport',
- 'Balance',
- 'CancelOrder',
- 'ClosedOrders',
- 'DepositAddresses',
- 'DepositMethods',
- 'DepositStatus',
- 'ExportStatus',
- 'GetWebSocketsToken',
- 'Ledgers',
- 'OpenOrders',
- 'OpenPositions',
- 'QueryLedgers',
- 'QueryOrders',
- 'QueryTrades',
- 'RetrieveExport',
- 'RemoveExport',
- 'TradeBalance',
- 'TradesHistory',
- 'TradeVolume',
- 'Withdraw',
- 'WithdrawCancel',
- 'WithdrawInfo',
- 'WithdrawStatus',
- ],
- },
- },
- 'commonCurrencies': {
- 'XBT': 'BTC',
- 'XDG': 'DOGE',
- },
- 'options': {
- 'cacheDepositMethodsOnFetchDepositAddress': True, # will issue up to two calls in fetchDepositAddress
- 'depositMethods': {},
- 'delistedMarketsById': {},
- # cannot withdraw/deposit these
- 'inactiveCurrencies': ['CAD', 'USD', 'JPY', 'GBP'],
- },
- 'exceptions': {
- 'EQuery:Invalid asset pair': BadSymbol, # {"error":["EQuery:Invalid asset pair"]}
- 'EAPI:Invalid key': AuthenticationError,
- 'EFunding:Unknown withdraw key': ExchangeError,
- 'EFunding:Invalid amount': InsufficientFunds,
- 'EService:Unavailable': ExchangeNotAvailable,
- 'EDatabase:Internal error': ExchangeNotAvailable,
- 'EService:Busy': ExchangeNotAvailable,
- 'EQuery:Unknown asset': ExchangeError,
- 'EAPI:Rate limit exceeded': DDoSProtection,
- 'EOrder:Rate limit exceeded': DDoSProtection,
- 'EGeneral:Internal error': ExchangeNotAvailable,
- 'EGeneral:Temporary lockout': DDoSProtection,
- 'EGeneral:Permission denied': PermissionDenied,
- 'EOrder:Unknown order': InvalidOrder,
- 'EOrder:Order minimum not met': InvalidOrder,
- },
- })
- def cost_to_precision(self, symbol, cost):
- return self.decimal_to_precision(cost, TRUNCATE, self.markets[symbol]['precision']['price'], DECIMAL_PLACES)
- def fee_to_precision(self, symbol, fee):
- return self.decimal_to_precision(fee, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
- async def fetch_markets(self, params={}):
- response = await self.publicGetAssetPairs(params)
- #
- # {
- # "error":[],
- # "result":{
- # "ADAETH":{
- # "altname":"ADAETH",
- # "wsname":"ADA\/ETH",
- # "aclass_base":"currency",
- # "base":"ADA",
- # "aclass_quote":"currency",
- # "quote":"XETH",
- # "lot":"unit",
- # "pair_decimals":7,
- # "lot_decimals":8,
- # "lot_multiplier":1,
- # "leverage_buy":[],
- # "leverage_sell":[],
- # "fees":[
- # [0,0.26],
- # [50000,0.24],
- # [100000,0.22],
- # [250000,0.2],
- # [500000,0.18],
- # [1000000,0.16],
- # [2500000,0.14],
- # [5000000,0.12],
- # [10000000,0.1]
- # ],
- # "fees_maker":[
- # [0,0.16],
- # [50000,0.14],
- # [100000,0.12],
- # [250000,0.1],
- # [500000,0.08],
- # [1000000,0.06],
- # [2500000,0.04],
- # [5000000,0.02],
- # [10000000,0]
- # ],
- # "fee_volume_currency":"ZUSD",
- # "margin_call":80,
- # "margin_stop":40,
- # "ordermin": "1"
- # },
- # }
- # }
- #
- keys = list(response['result'].keys())
- result = []
- for i in range(0, len(keys)):
- id = keys[i]
- market = response['result'][id]
- baseId = market['base']
- quoteId = market['quote']
- base = self.safe_currency_code(baseId)
- quote = self.safe_currency_code(quoteId)
- darkpool = id.find('.d') >= 0
- symbol = market['altname'] if darkpool else (base + '/' + quote)
- maker = None
- if 'fees_maker' in market:
- maker = float(market['fees_maker'][0][1]) / 100
- precision = {
- 'amount': market['lot_decimals'],
- 'price': market['pair_decimals'],
- }
- minAmount = self.safe_float(market, 'ordermin')
- result.append({
- 'id': id,
- 'symbol': symbol,
- 'base': base,
- 'quote': quote,
- 'baseId': baseId,
- 'quoteId': quoteId,
- 'darkpool': darkpool,
- 'info': market,
- 'altname': market['altname'],
- 'maker': maker,
- 'taker': float(market['fees'][0][1]) / 100,
- 'active': True,
- 'precision': precision,
- 'limits': {
- 'amount': {
- 'min': minAmount,
- 'max': math.pow(10, precision['amount']),
- },
- 'price': {
- 'min': math.pow(10, -precision['price']),
- 'max': None,
- },
- 'cost': {
- 'min': 0,
- 'max': None,
- },
- },
- })
- result = self.append_inactive_markets(result)
- self.marketsByAltname = self.index_by(result, 'altname')
- return result
- def safe_currency_code(self, currencyId, currency=None):
- if len(currencyId) > 3:
- if (currencyId.find('X') == 0) or (currencyId.find('Z') == 0):
- currencyId = currencyId[1:]
- return super(kraken, self).safe_currency_code(currencyId, currency)
- def append_inactive_markets(self, result):
- # result should be an array to append to
- precision = {'amount': 8, 'price': 8}
- costLimits = {'min': 0, 'max': None}
- priceLimits = {'min': math.pow(10, -precision['price']), 'max': None}
- amountLimits = {'min': math.pow(10, -precision['amount']), 'max': math.pow(10, precision['amount'])}
- limits = {'amount': amountLimits, 'price': priceLimits, 'cost': costLimits}
- defaults = {
- 'darkpool': False,
- 'info': None,
- 'maker': None,
- 'taker': None,
- 'active': False,
- 'precision': precision,
- 'limits': limits,
- }
- markets = [
- # {'id': 'XXLMZEUR', 'symbol': 'XLM/EUR', 'base': 'XLM', 'quote': 'EUR', 'altname': 'XLMEUR'},
- ]
- for i in range(0, len(markets)):
- result.append(self.extend(defaults, markets[i]))
- return result
- async def fetch_currencies(self, params={}):
- response = await self.publicGetAssets(params)
- #
- # {
- # "error": [],
- # "result": {
- # "ADA": {"aclass": "currency", "altname": "ADA", "decimals": 8, "display_decimals": 6},
- # "BCH": {"aclass": "currency", "altname": "BCH", "decimals": 10, "display_decimals": 5},
- # ...
- # },
- # }
- #
- currencies = self.safe_value(response, 'result')
- ids = list(currencies.keys())
- result = {}
- for i in range(0, len(ids)):
- id = ids[i]
- currency = currencies[id]
- # todo: will need to rethink the fees
- # see: https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees-
- # to add support for multiple withdrawal/deposit methods and
- # differentiated fees for each particular method
- code = self.safe_currency_code(self.safe_string(currency, 'altname'))
- precision = self.safe_integer(currency, 'decimals')
- # assumes all currencies are active except those listed above
- active = not self.in_array(code, self.options['inactiveCurrencies'])
- result[code] = {
- 'id': id,
- 'code': code,
- 'info': currency,
- 'name': code,
- 'active': active,
- 'fee': None,
- 'precision': precision,
- 'limits': {
- 'amount': {
- 'min': math.pow(10, -precision),
- 'max': math.pow(10, precision),
- },
- 'price': {
- 'min': math.pow(10, -precision),
- 'max': math.pow(10, precision),
- },
- 'cost': {
- 'min': None,
- 'max': None,
- },
- 'withdraw': {
- 'min': None,
- 'max': math.pow(10, precision),
- },
- },
- }
- return result
- async def fetch_trading_fees(self, params={}):
- await self.load_markets()
- self.check_required_credentials()
- response = await self.privatePostTradeVolume(params)
- tradedVolume = self.safe_float(response['result'], 'volume')
- tiers = self.fees['trading']['tiers']
- taker = tiers['taker'][1]
- maker = tiers['maker'][1]
- for i in range(0, len(tiers['taker'])):
- if tradedVolume >= tiers['taker'][i][0]:
- taker = tiers['taker'][i][1]
- for i in range(0, len(tiers['maker'])):
- if tradedVolume >= tiers['maker'][i][0]:
- maker = tiers['maker'][i][1]
- return {
- 'info': response,
- 'maker': maker,
- 'taker': taker,
- }
- def parse_bid_ask(self, bidask, priceKey=0, amountKey=1):
- price = self.safe_float(bidask, priceKey)
- amount = self.safe_float(bidask, amountKey)
- timestamp = self.safe_integer(bidask, 2)
- return [price, amount, timestamp]
- async def fetch_order_book(self, symbol, limit=None, params={}):
- await self.load_markets()
- market = self.market(symbol)
- if market['darkpool']:
- raise ExchangeError(self.id + ' does not provide an order book for darkpool symbol ' + symbol)
- request = {
- 'pair': market['id'],
- }
- if limit is not None:
- request['count'] = limit # 100
- response = await self.publicGetDepth(self.extend(request, params))
- #
- # {
- # "error":[],
- # "result":{
- # "XETHXXBT":{
- # "asks":[
- # ["0.023480","4.000",1586321307],
- # ["0.023490","50.095",1586321306],
- # ["0.023500","28.535",1586321302],
- # ],
- # "bids":[
- # ["0.023470","59.580",1586321307],
- # ["0.023460","20.000",1586321301],
- # ["0.023440","67.832",1586321306],
- # ]
- # }
- # }
- # }
- #
- result = self.safe_value(response, 'result', {})
- orderbook = self.safe_value(result, market['id'])
- return self.parse_order_book(orderbook)
- def parse_ticker(self, ticker, market=None):
- timestamp = self.milliseconds()
- symbol = None
- if market:
- symbol = market['symbol']
- baseVolume = float(ticker['v'][1])
- vwap = float(ticker['p'][1])
- quoteVolume = None
- if baseVolume is not None and vwap is not None:
- quoteVolume = baseVolume * vwap
- last = float(ticker['c'][0])
- return {
- 'symbol': symbol,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'high': float(ticker['h'][1]),
- 'low': float(ticker['l'][1]),
- 'bid': float(ticker['b'][0]),
- 'bidVolume': None,
- 'ask': float(ticker['a'][0]),
- 'askVolume': None,
- 'vwap': vwap,
- 'open': self.safe_float(ticker, 'o'),
- 'close': last,
- 'last': last,
- 'previousClose': None,
- 'change': None,
- 'percentage': None,
- 'average': None,
- 'baseVolume': baseVolume,
- 'quoteVolume': quoteVolume,
- 'info': ticker,
- }
- async def fetch_tickers(self, symbols=None, params={}):
- await self.load_markets()
- symbols = self.symbols if (symbols is None) else symbols
- marketIds = []
- for i in range(0, len(symbols)):
- symbol = symbols[i]
- market = self.markets[symbol]
- if market['active'] and not market['darkpool']:
- marketIds.append(market['id'])
- request = {
- 'pair': ','.join(marketIds),
- }
- response = await self.publicGetTicker(self.extend(request, params))
- tickers = response['result']
- ids = list(tickers.keys())
- result = {}
- for i in range(0, len(ids)):
- id = ids[i]
- market = self.markets_by_id[id]
- symbol = market['symbol']
- ticker = tickers[id]
- result[symbol] = self.parse_ticker(ticker, market)
- return self.filter_by_array(result, 'symbol', symbols)
- async def fetch_ticker(self, symbol, params={}):
- await self.load_markets()
- darkpool = symbol.find('.d') >= 0
- if darkpool:
- raise ExchangeError(self.id + ' does not provide a ticker for darkpool symbol ' + symbol)
- market = self.market(symbol)
- request = {
- 'pair': market['id'],
- }
- response = await self.publicGetTicker(self.extend(request, params))
- ticker = response['result'][market['id']]
- return self.parse_ticker(ticker, market)
- def parse_ohlcv(self, ohlcv, market=None):
- #
- # [
- # 1591475640,
- # "0.02500",
- # "0.02500",
- # "0.02500",
- # "0.02500",
- # "0.02500",
- # "9.12201000",
- # 5
- # ]
- #
- return [
- self.safe_timestamp(ohlcv, 0),
- self.safe_float(ohlcv, 1),
- self.safe_float(ohlcv, 2),
- self.safe_float(ohlcv, 3),
- self.safe_float(ohlcv, 4),
- self.safe_float(ohlcv, 6),
- ]
- async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
- await self.load_markets()
- market = self.market(symbol)
- request = {
- 'pair': market['id'],
- 'interval': self.timeframes[timeframe],
- }
- if since is not None:
- request['since'] = int((since - 1) / 1000)
- response = await self.publicGetOHLC(self.extend(request, params))
- #
- # {
- # "error":[],
- # "result":{
- # "XETHXXBT":[
- # [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0],
- # [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5],
- # [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2],
- # [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1],
- # ],
- # "last":1591517580
- # }
- # }
- result = self.safe_value(response, 'result', {})
- ohlcvs = self.safe_value(result, market['id'], [])
- return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
- def parse_ledger_entry_type(self, type):
- types = {
- 'trade': 'trade',
- 'withdrawal': 'transaction',
- 'deposit': 'transaction',
- 'transfer': 'transfer',
- 'margin': 'margin',
- }
- return self.safe_string(types, type, type)
- def parse_ledger_entry(self, item, currency=None):
- #
- # {
- # 'LTFK7F-N2CUX-PNY4SX': {
- # refid: "TSJTGT-DT7WN-GPPQMJ",
- # time: 1520102320.555,
- # type: "trade",
- # aclass: "currency",
- # asset: "XETH",
- # amount: "0.1087194600",
- # fee: "0.0000000000",
- # balance: "0.2855851000"
- # },
- # ...
- # }
- #
- id = self.safe_string(item, 'id')
- direction = None
- account = None
- referenceId = self.safe_string(item, 'refid')
- referenceAccount = None
- type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
- code = self.safe_currency_code(self.safe_string(item, 'asset'), currency)
- amount = self.safe_float(item, 'amount')
- if amount < 0:
- direction = 'out'
- amount = abs(amount)
- else:
- direction = 'in'
- time = self.safe_float(item, 'time')
- timestamp = None
- if time is not None:
- timestamp = int(time * 1000)
- fee = {
- 'cost': self.safe_float(item, 'fee'),
- 'currency': code,
- }
- before = None
- after = self.safe_float(item, 'balance')
- status = 'ok'
- return {
- 'info': item,
- 'id': id,
- 'direction': direction,
- 'account': account,
- 'referenceId': referenceId,
- 'referenceAccount': referenceAccount,
- 'type': type,
- 'currency': code,
- 'amount': amount,
- 'before': before,
- 'after': after,
- 'status': status,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'fee': fee,
- }
- async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
- # https://www.kraken.com/features/api#get-ledgers-info
- await self.load_markets()
- request = {}
- currency = None
- if code is not None:
- currency = self.currency(code)
- request['asset'] = currency['id']
- if since is not None:
- request['start'] = int(since / 1000)
- response = await self.privatePostLedgers(self.extend(request, params))
- # { error: [],
- # result: {ledger: {'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N",
- # time: 1520103488.314,
- # type: "withdrawal",
- # aclass: "currency",
- # asset: "XETH",
- # amount: "-0.2805800000",
- # fee: "0.0050000000",
- # balance: "0.0000051000" },
- result = self.safe_value(response, 'result', {})
- ledger = self.safe_value(result, 'ledger', {})
- keys = list(ledger.keys())
- items = []
- for i in range(0, len(keys)):
- key = keys[i]
- value = ledger[key]
- value['id'] = key
- items.append(value)
- return self.parse_ledger(items, currency, since, limit)
- async def fetch_ledger_entries_by_ids(self, ids, code=None, params={}):
- # https://www.kraken.com/features/api#query-ledgers
- await self.load_markets()
- ids = ','.join(ids)
- request = self.extend({
- 'id': ids,
- }, params)
- response = await self.privatePostQueryLedgers(request)
- # { error: [],
- # result: {'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N",
- # time: 1520103488.314,
- # type: "withdrawal",
- # aclass: "currency",
- # asset: "XETH",
- # amount: "-0.2805800000",
- # fee: "0.0050000000",
- # balance: "0.0000051000" }}}
- result = response['result']
- keys = list(result.keys())
- items = []
- for i in range(0, len(keys)):
- key = keys[i]
- value = result[key]
- value['id'] = key
- items.append(value)
- return self.parse_ledger(items)
- async def fetch_ledger_entry(self, id, code=None, params={}):
- items = await self.fetch_ledger_entries_by_ids([id], code, params)
- return items[0]
- def parse_trade(self, trade, market=None):
- #
- # fetchTrades(public)
- #
- # [
- # "0.032310", # price
- # "4.28169434", # amount
- # 1541390792.763, # timestamp
- # "s", # sell or buy
- # "l", # limit or market
- # ""
- # ]
- #
- # fetchOrderTrades(private)
- #
- # {
- # id: 'TIMIRG-WUNNE-RRJ6GT', # injected from outside
- # ordertxid: 'OQRPN2-LRHFY-HIFA7D',
- # postxid: 'TKH2SE-M7IF5-CFI7LT',
- # pair: 'USDCUSDT',
- # time: 1586340086.457,
- # type: 'sell',
- # ordertype: 'market',
- # price: '0.99860000',
- # cost: '22.16892001',
- # fee: '0.04433784',
- # vol: '22.20000000',
- # margin: '0.00000000',
- # misc: ''
- # }
- #
- timestamp = None
- side = None
- type = None
- price = None
- amount = None
- cost = None
- id = None
- order = None
- fee = None
- symbol = None
- if isinstance(trade, list):
- timestamp = self.safe_timestamp(trade, 2)
- side = 'sell' if (trade[3] == 's') else 'buy'
- type = 'limit' if (trade[4] == 'l') else 'market'
- price = self.safe_float(trade, 0)
- amount = self.safe_float(trade, 1)
- tradeLength = len(trade)
- if tradeLength > 6:
- id = self.safe_string(trade, 6) # artificially added as per #1794
- elif isinstance(trade, basestring):
- id = trade
- elif 'ordertxid' in trade:
- marketId = self.safe_string(trade, 'pair')
- foundMarket = self.find_market_by_altname_or_id(marketId)
- if foundMarket is not None:
- market = foundMarket
- elif marketId is not None:
- # delisted market ids go here
- market = self.get_delisted_market_by_id(marketId)
- order = trade['ordertxid']
- id = self.safe_string_2(trade, 'id', 'postxid')
- timestamp = self.safe_timestamp(trade, 'time')
- side = self.safe_string(trade, 'type')
- type = self.safe_string(trade, 'ordertype')
- price = self.safe_float(trade, 'price')
- amount = self.safe_float(trade, 'vol')
- if 'fee' in trade:
- currency = None
- if market is not None:
- currency = market['quote']
- fee = {
- 'cost': self.safe_float(trade, 'fee'),
- 'currency': currency,
- }
- if market is not None:
- symbol = market['symbol']
- if price is not None:
- if amount is not None:
- cost = price * amount
- return {
- 'id': id,
- 'order': order,
- 'info': trade,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'symbol': symbol,
- 'type': type,
- 'side': side,
- 'takerOrMaker': None,
- 'price': price,
- 'amount': amount,
- 'cost': cost,
- 'fee': fee,
- }
- async def fetch_trades(self, symbol, since=None, limit=None, params={}):
- await self.load_markets()
- market = self.market(symbol)
- id = market['id']
- request = {
- 'pair': id,
- }
- # https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API
- # https://github.com/ccxt/ccxt/issues/5677
- if since is not None:
- # php does not format it properly
- # therefore we use string concatenation here
- request['since'] = since * 1e6
- request['since'] = str(since) + '000000' # expected to be in nanoseconds
- # https://github.com/ccxt/ccxt/issues/5698
- if limit is not None and limit != 1000:
- fetchTradesWarning = self.safe_value(self.options, 'fetchTradesWarning', True)
- if fetchTradesWarning:
- raise ExchangeError(self.id + ' fetchTrades() cannot serve ' + str(limit) + " trades without breaking the pagination, see https://github.com/ccxt/ccxt/issues/5698 for more details. Set exchange.options['fetchTradesWarning'] to acknowledge self warning and silence it.")
- response = await self.publicGetTrades(self.extend(request, params))
- #
- # {
- # "error": [],
- # "result": {
- # "XETHXXBT": [
- # ["0.032310","4.28169434",1541390792.763,"s","l",""]
- # ],
- # "last": "1541439421200678657"
- # }
- # }
- #
- result = response['result']
- trades = result[id]
- # trades is a sorted array: last(most recent trade) goes last
- length = len(trades)
- if length <= 0:
- return []
- lastTrade = trades[length - 1]
- lastTradeId = self.safe_string(result, 'last')
- lastTrade.append(lastTradeId)
- return self.parse_trades(trades, market, since, limit)
- async def fetch_balance(self, params={}):
- response = await self.privatePostBalance(params)
- balances = self.safe_value(response, 'result', {})
- result = {'info': balances}
- currencyIds = list(balances.keys())
- for i in range(0, len(currencyIds)):
- currencyId = currencyIds[i]
- code = self.safe_currency_code(currencyId)
- account = self.account()
- account['total'] = self.safe_float(balances, currencyId)
- result[code] = account
- return self.parse_balance(result)
- async def create_order(self, symbol, type, side, amount, price=None, params={}):
- await self.load_markets()
- market = self.market(symbol)
- request = {
- 'pair': market['id'],
- 'type': side,
- 'ordertype': type,
- 'volume': self.amount_to_precision(symbol, amount),
- }
- clientOrderId = self.safe_string_2(params, 'userref', 'clientOrderId')
- query = self.omit(params, ['userref', 'clientOrderId'])
- if clientOrderId is not None:
- request['userref'] = clientOrderId
- priceIsDefined = (price is not None)
- marketOrder = (type == 'market')
- limitOrder = (type == 'limit')
- shouldIncludePrice = limitOrder or (not marketOrder and priceIsDefined)
- if shouldIncludePrice:
- request['price'] = self.price_to_precision(symbol, price)
- response = await self.privatePostAddOrder(self.extend(request, query))
- id = self.safe_value(response['result'], 'txid')
- if id is not None:
- if isinstance(id, list):
- length = len(id)
- id = id if (length > 1) else id[0]
- return {
- 'id': id,
- 'clientOrderId': clientOrderId,
- 'info': response,
- 'timestamp': None,
- 'datetime': None,
- 'lastTradeTimestamp': None,
- 'symbol': symbol,
- 'type': type,
- 'side': side,
- 'price': None,
- 'amount': None,
- 'cost': None,
- 'average': None,
- 'filled': None,
- 'remaining': None,
- 'status': None,
- 'fee': None,
- 'trades': None,
- }
- def find_market_by_altname_or_id(self, id):
- if id in self.marketsByAltname:
- return self.marketsByAltname[id]
- elif id in self.markets_by_id:
- return self.markets_by_id[id]
- return None
- def get_delisted_market_by_id(self, id):
- if id is None:
- return id
- market = self.safe_value(self.options['delistedMarketsById'], id)
- if market is not None:
- return market
- baseIdStart = 0
- baseIdEnd = 3
- quoteIdStart = 3
- quoteIdEnd = 6
- if len(id) == 8:
- baseIdEnd = 4
- quoteIdStart = 4
- quoteIdEnd = 8
- elif len(id) == 7:
- baseIdEnd = 4
- quoteIdStart = 4
- quoteIdEnd = 7
- baseId = id[baseIdStart:baseIdEnd]
- quoteId = id[quoteIdStart:quoteIdEnd]
- base = self.safe_currency_code(baseId)
- quote = self.safe_currency_code(quoteId)
- symbol = base + '/' + quote
- market = {
- 'symbol': symbol,
- 'base': base,
- 'quote': quote,
- 'baseId': baseId,
- 'quoteId': quoteId,
- }
- self.options['delistedMarketsById'][id] = market
- return market
- def parse_order_status(self, status):
- statuses = {
- 'pending': 'open', # order pending book entry
- 'open': 'open',
- 'closed': 'closed',
- 'canceled': 'canceled',
- 'expired': 'expired',
- }
- return self.safe_string(statuses, status, status)
- def parse_order(self, order, market=None):
- description = self.safe_value(order, 'descr', {})
- side = self.safe_string(description, 'type')
- type = self.safe_string(description, 'ordertype')
- marketId = self.safe_string(description, 'pair')
- foundMarket = self.find_market_by_altname_or_id(marketId)
- symbol = None
- if foundMarket is not None:
- market = foundMarket
- elif marketId is not None:
- # delisted market ids go here
- market = self.get_delisted_market_by_id(marketId)
- timestamp = self.safe_timestamp(order, 'opentm')
- amount = self.safe_float(order, 'vol')
- filled = self.safe_float(order, 'vol_exec')
- remaining = amount - filled
- fee = None
- cost = self.safe_float(order, 'cost')
- price = self.safe_float(description, 'price')
- if (price is None) or (price == 0):
- price = self.safe_float(description, 'price2')
- if (price is None) or (price == 0):
- price = self.safe_float(order, 'price', price)
- average = self.safe_float(order, 'price')
- if market is not None:
- symbol = market['symbol']
- if 'fee' in order:
- flags = order['oflags']
- feeCost = self.safe_float(order, 'fee')
- fee = {
- 'cost': feeCost,
- 'rate': None,
- }
- if flags.find('fciq') >= 0:
- fee['currency'] = market['quote']
- elif flags.find('fcib') >= 0:
- fee['currency'] = market['base']
- status = self.parse_order_status(self.safe_string(order, 'status'))
- id = self.safe_string(order, 'id')
- clientOrderId = self.safe_string(order, 'userref')
- rawTrades = self.safe_value(order, 'trades')
- trades = None
- if rawTrades is not None:
- trades = self.parse_trades(rawTrades, market, None, None, {'order': id})
- return {
- 'id': id,
- 'clientOrderId': clientOrderId,
- 'info': order,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'lastTradeTimestamp': None,
- 'status': status,
- 'symbol': symbol,
- 'type': type,
- 'side': side,
- 'price': price,
- 'cost': cost,
- 'amount': amount,
- 'filled': filled,
- 'average': average,
- 'remaining': remaining,
- 'fee': fee,
- 'trades': trades,
- }
- def parse_orders(self, orders, market=None, since=None, limit=None, params={}):
- result = []
- ids = list(orders.keys())
- symbol = None
- if market is not None:
- symbol = market['symbol']
- for i in range(0, len(ids)):
- id = ids[i]
- order = self.extend({'id': id}, orders[id])
- result.append(self.extend(self.parse_order(order, market), params))
- result = self.sort_by(result, 'timestamp')
- return self.filter_by_symbol_since_limit(result, symbol, since, limit)
- async def fetch_order(self, id, symbol=None, params={}):
- await self.load_markets()
- clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
- request = {
- 'trades': True, # whether or not to include trades in output(optional, default False)
- # 'txid': id, # do not comma separate a list of ids - use fetchOrdersByIds instead
- # 'userref': 'optional', # restrict results to given user reference id(optional)
- }
- query = params
- if clientOrderId is not None:
- request['userref'] = clientOrderId
- query = self.omit(params, ['userref', 'clientOrderId'])
- else:
- request['txid'] = id
- response = await self.privatePostQueryOrders(self.extend(request, query))
- #
- # {
- # "error":[],
- # "result":{
- # "OTLAS3-RRHUF-NDWH5A":{
- # "refid":null,
- # "userref":null,
- # "status":"closed",
- # "reason":null,
- # "opentm":1586822919.3342,
- # "closetm":1586822919.365,
- # "starttm":0,
- # "expiretm":0,
- # "descr":{
- # "pair":"XBTUSDT",
- # "type":"sell",
- # "ordertype":"market",
- # "price":"0",
- # "price2":"0",
- # "leverage":"none",
- # "order":"sell 0.21804000 XBTUSDT @ market",
- # "close":""
- # },
- # "vol":"0.21804000",
- # "vol_exec":"0.21804000",
- # "cost":"1493.9",
- # "fee":"3.8",
- # "price":"6851.5",
- # "stopprice":"0.00000",
- # "limitprice":"0.00000",
- # "misc":"",
- # "oflags":"fciq",
- # "trades":["TT5UC3-GOIRW-6AZZ6R"]
- # }
- # }
- # }
- #
- orders = self.safe_value(response, 'result', [])
- order = self.parse_order(self.extend({'id': id}, orders[id]))
- return self.extend({'info': response}, order)
- async def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
- orderTrades = self.safe_value(params, 'trades')
- tradeIds = []
- if orderTrades is None:
- raise ArgumentsRequired(self.id + " fetchOrderTrades requires a unified order structure in the params argument or a 'trades' param(an array of trade id strings)")
- else:
- for i in range(0, len(orderTrades)):
- orderTrade = orderTrades[i]
- if isinstance(orderTrade, basestring):
- tradeIds.append(orderTrade)
- else:
- tradeIds.append(orderTrade['id'])
- await self.load_markets()
- options = self.safe_value(self.options, 'fetchOrderTrades', {})
- batchSize = self.safe_integer(options, 'batchSize', 20)
- numBatches = int(tradeIds / batchSize)
- numBatches = self.sum(numBatches, 1)
- numTradeIds = len(tradeIds)
- result = []
- for j in range(0, numBatches):
- requestIds = []
- for k in range(0, batchSize):
- index = self.sum(j * batchSize, k)
- if index < numTradeIds:
- requestIds.append(tradeIds[index])
- request = {
- 'txid': ','.join(requestIds),
- }
- response = await self.privatePostQueryTrades(request)
- #
- # {
- # error: [],
- # result: {
- # 'TIMIRG-WUNNE-RRJ6GT': {
- # ordertxid: 'OQRPN2-LRHFY-HIFA7D',
- # postxid: 'TKH2SE-M7IF5-CFI7LT',
- # pair: 'USDCUSDT',
- # time: 1586340086.457,
- # type: 'sell',
- # ordertype: 'market',
- # price: '0.99860000',
- # cost: '22.16892001',
- # fee: '0.04433784',
- # vol: '22.20000000',
- # margin: '0.00000000',
- # misc: ''
- # }
- # }
- # }
- #
- rawTrades = self.safe_value(response, 'result')
- ids = list(rawTrades.keys())
- for i in range(0, len(ids)):
- rawTrades[ids[i]]['id'] = ids[i]
- trades = self.parse_trades(rawTrades, None, since, limit)
- tradesFilteredBySymbol = self.filter_by_symbol(trades, symbol)
- result = self.array_concat(result, tradesFilteredBySymbol)
- return result
- async def fetch_orders_by_ids(self, ids, symbol=None, params={}):
- await self.load_markets()
- response = await self.privatePostQueryOrders(self.extend({
- 'trades': True, # whether or not to include trades in output(optional, default False)
- 'txid': ','.join(ids), # comma delimited list of transaction ids to query info about(20 maximum)
- }, params))
- result = self.safe_value(response, 'result', {})
- orders = []
- orderIds = list(result.keys())
- for i in range(0, len(orderIds)):
- id = orderIds[i]
- item = result[id]
- order = self.parse_order(self.extend({'id': id}, item))
- orders.append(order)
- return orders
- async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
- await self.load_markets()
- request = {
- # 'type': 'all', # any position, closed position, closing position, no position
- # 'trades': False, # whether or not to include trades related to position in output
- # 'start': 1234567890, # starting unix timestamp or trade tx id of results(exclusive)
- # 'end': 1234567890, # ending unix timestamp or trade tx id of results(inclusive)
- # 'ofs' = result offset
- }
- if since is not None:
- request['start'] = int(since / 1000)
- response = await self.privatePostTradesHistory(self.extend(request, params))
- #
- # {
- # "error": [],
- # "result": {
- # "trades": {
- # "GJ3NYQ-XJRTF-THZABF": {
- # "ordertxid": "TKH2SE-ZIF5E-CFI7LT",
- # "postxid": "OEN3VX-M7IF5-JNBJAM",
- # "pair": "XICNXETH",
- # "time": 1527213229.4491,
- # "type": "sell",
- # "ordertype": "limit",
- # "price": "0.001612",
- # "cost": "0.025792",
- # "fee": "0.000026",
- # "vol": "16.00000000",
- # "margin": "0.000000",
- # "misc": ""
- # },
- # ...
- # },
- # "count": 9760,
- # },
- # }
- #
- trades = response['result']['trades']
- ids = list(trades.keys())
- for i in range(0, len(ids)):
- trades[ids[i]]['id'] = ids[i]
- result = self.parse_trades(trades, None, since, limit)
- if symbol is None:
- return result
- return self.filter_by_symbol(result, symbol)
- async def cancel_order(self, id, symbol=None, params={}):
- await self.load_markets()
- response = None
- try:
- response = await self.privatePostCancelOrder(self.extend({
- 'txid': id,
- }, params))
- except Exception as e:
- if self.last_http_response:
- if self.last_http_response.find('EOrder:Unknown order') >= 0:
- raise OrderNotFound(self.id + ' cancelOrder() error ' + self.last_http_response)
- raise e
- return response
- async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
- await self.load_markets()
- request = {}
- if since is not None:
- request['start'] = int(since / 1000)
- response = await self.privatePostOpenOrders(self.extend(request, params))
- orders = self.parse_orders(response['result']['open'], None, since, limit)
- if symbol is None:
- return orders
- return self.filter_by_symbol(orders, symbol)
- async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
- await self.load_markets()
- request = {}
- if since is not None:
- request['start'] = int(since / 1000)
- response = await self.privatePostClosedOrders(self.extend(request, params))
- orders = self.parse_orders(response['result']['closed'], None, since, limit)
- if symbol is None:
- return orders
- return self.filter_by_symbol(orders, symbol)
- async def fetch_deposit_methods(self, code, params={}):
- await self.load_markets()
- currency = self.currency(code)
- request = {
- 'asset': currency['id'],
- }
- response = await self.privatePostDepositMethods(self.extend(request, params))
- return response['result']
- def parse_transaction_status(self, status):
- # IFEX transaction states
- statuses = {
- 'Initial': 'pending',
- 'Pending': 'pending',
- 'Success': 'ok',
- 'Settled': 'pending',
- 'Failure': 'failed',
- 'Partial': 'ok',
- }
- return self.safe_string(statuses, status, status)
- def parse_transaction(self, transaction, currency=None):
- #
- # fetchDeposits
- #
- # {method: "Ether(Hex)",
- # aclass: "currency",
- # asset: "XETH",
- # refid: "Q2CANKL-LBFVEE-U4Y2WQ",
- # txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
- # info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
- # amount: "7.9999257900",
- # fee: "0.0000000000",
- # time: 1529223212,
- # status: "Success" }
- #
- # fetchWithdrawals
- #
- # {method: "Ether",
- # aclass: "currency",
- # asset: "XETH",
- # refid: "A2BF34S-O7LBNQ-UE4Y4O",
- # txid: "0x288b83c6b0904d8400ef44e1c9e2187b5c8f7ea3d838222d53f701a15b5c274d",
- # info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
- # amount: "9.9950000000",
- # fee: "0.0050000000",
- # time: 1530481750,
- # status: "Success" }
- #
- id = self.safe_string(transaction, 'refid')
- txid = self.safe_string(transaction, 'txid')
- timestamp = self.safe_timestamp(transaction, 'time')
- currencyId = self.safe_string(transaction, 'asset')
- code = self.safe_currency_code(currencyId, currency)
- address = self.safe_string(transaction, 'info')
- amount = self.safe_float(transaction, 'amount')
- status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
- type = self.safe_string(transaction, 'type') # injected from the outside
- feeCost = self.safe_float(transaction, 'fee')
- if feeCost is None:
- if type == 'deposit':
- feeCost = 0
- return {
- 'info': transaction,
- 'id': id,
- 'currency': code,
- 'amount': amount,
- 'address': address,
- 'tag': None,
- 'status': status,
- 'type': type,
- 'updated': None,
- 'txid': txid,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'fee': {
- 'currency': code,
- 'cost': feeCost,
- },
- }
- def parse_transactions_by_type(self, type, transactions, code=None, since=None, limit=None):
- result = []
- for i in range(0, len(transactions)):
- transaction = self.parse_transaction(self.extend({
- 'type': type,
- }, transactions[i]))
- result.append(transaction)
- return self.filter_by_currency_since_limit(result, code, since, limit)
- async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
- # https://www.kraken.com/en-us/help/api#deposit-status
- if code is None:
- raise ArgumentsRequired(self.id + ' fetchDeposits requires a currency code argument')
- await self.load_markets()
- currency = self.currency(code)
- request = {
- 'asset': currency['id'],
- }
- response = await self.privatePostDepositStatus(self.extend(request, params))
- #
- # { error: [],
- # result: [{method: "Ether(Hex)",
- # aclass: "currency",
- # asset: "XETH",
- # refid: "Q2CANKL-LBFVEE-U4Y2WQ",
- # txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
- # info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
- # amount: "7.9999257900",
- # fee: "0.0000000000",
- # time: 1529223212,
- # status: "Success" }]}
- #
- return self.parse_transactions_by_type('deposit', response['result'], code, since, limit)
- async def fetch_time(self, params={}):
- # https://www.kraken.com/en-us/features/api#get-server-time
- response = await self.publicGetTime(params)
- #
- # {
- # "error": [],
- # "result": {
- # "unixtime": 1591502873,
- # "rfc1123": "Sun, 7 Jun 20 04:07:53 +0000"
- # }
- # }
- #
- result = self.safe_value(response, 'result', {})
- return self.safe_timestamp(result, 'unixtime')
- async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
- # https://www.kraken.com/en-us/help/api#withdraw-status
- if code is None:
- raise ArgumentsRequired(self.id + ' fetchWithdrawals requires a currency code argument')
- await self.load_markets()
- currency = self.currency(code)
- request = {
- 'asset': currency['id'],
- }
- response = await self.privatePostWithdrawStatus(self.extend(request, params))
- #
- # { error: [],
- # result: [{method: "Ether",
- # aclass: "currency",
- # asset: "XETH",
- # refid: "A2BF34S-O7LBNQ-UE4Y4O",
- # txid: "0x298c83c7b0904d8400ef43e1c9e2287b518f7ea3d838822d53f704a1565c274d",
- # info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
- # amount: "9.9950000000",
- # fee: "0.0050000000",
- # time: 1530481750,
- # status: "Success" }]}
- #
- return self.parse_transactions_by_type('withdrawal', response['result'], code, since, limit)
- async def create_deposit_address(self, code, params={}):
- request = {
- 'new': 'true',
- }
- response = await self.fetch_deposit_address(code, self.extend(request, params))
- address = self.safe_string(response, 'address')
- self.check_address(address)
- return {
- 'currency': code,
- 'address': address,
- 'info': response,
- }
- async def fetch_deposit_address(self, code, params={}):
- await self.load_markets()
- currency = self.currency(code)
- # eslint-disable-next-line quotes
- method = self.safe_string(params, 'method')
- if method is None:
- if self.options['cacheDepositMethodsOnFetchDepositAddress']:
- # cache depositMethods
- if not (code in self.options['depositMethods']):
- self.options['depositMethods'][code] = await self.fetch_deposit_methods(code)
- method = self.options['depositMethods'][code][0]['method']
- else:
- raise ArgumentsRequired(self.id + ' fetchDepositAddress() requires an extra `method` parameter. Use fetchDepositMethods("' + code + '") to get a list of available deposit methods or enable the exchange property .options["cacheDepositMethodsOnFetchDepositAddress"] = True')
- request = {
- 'asset': currency['id'],
- 'method': method,
- }
- response = await self.privatePostDepositAddresses(self.extend(request, params)) # overwrite methods
- result = response['result']
- numResults = len(result)
- if numResults < 1:
- raise InvalidAddress(self.id + ' privatePostDepositAddresses() returned no addresses')
- address = self.safe_string(result[0], 'address')
- tag = self.safe_string_2(result[0], 'tag', 'memo')
- self.check_address(address)
- return {
- 'currency': code,
- 'address': address,
- 'tag': tag,
- 'info': response,
- }
- async def withdraw(self, code, amount, address, tag=None, params={}):
- self.check_address(address)
- if 'key' in params:
- await self.load_markets()
- currency = self.currency(code)
- request = {
- 'asset': currency['id'],
- 'amount': amount,
- # 'address': address, # they don't allow withdrawals to direct addresses
- }
- response = await self.privatePostWithdraw(self.extend(request, params))
- return {
- 'info': response,
- 'id': response['result'],
- }
- raise ExchangeError(self.id + " withdraw requires a 'key' parameter(withdrawal key name, as set up on your account)")
- def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
- url = '/' + self.version + '/' + api + '/' + path
- if api == 'public':
- if params:
- url += '?' + self.urlencode(params)
- elif api == 'private':
- self.check_required_credentials()
- nonce = str(self.nonce())
- body = self.urlencode(self.extend({'nonce': nonce}, params))
- auth = self.encode(nonce + body)
- hash = self.hash(auth, 'sha256', 'binary')
- binary = self.encode(url)
- binhash = self.binary_concat(binary, hash)
- secret = self.base64_to_binary(self.secret)
- signature = self.hmac(binhash, secret, hashlib.sha512, 'base64')
- headers = {
- 'API-Key': self.apiKey,
- 'API-Sign': signature,
- 'Content-Type': 'application/x-www-form-urlencoded',
- }
- else:
- url = '/' + path
- url = self.urls['api'][api] + url
- return {'url': url, 'method': method, 'body': body, 'headers': headers}
- def nonce(self):
- return self.milliseconds()
- def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
- if code == 520:
- raise ExchangeNotAvailable(self.id + ' ' + str(code) + ' ' + reason)
- # todo: rewrite self for "broad" exceptions matching
- if body.find('Invalid order') >= 0:
- raise InvalidOrder(self.id + ' ' + body)
- if body.find('Invalid nonce') >= 0:
- raise InvalidNonce(self.id + ' ' + body)
- if body.find('Insufficient funds') >= 0:
- raise InsufficientFunds(self.id + ' ' + body)
- if body.find('Cancel pending') >= 0:
- raise CancelPending(self.id + ' ' + body)
- if body.find('Invalid arguments:volume') >= 0:
- raise InvalidOrder(self.id + ' ' + body)
- if body[0] == '{':
- if not isinstance(response, basestring):
- if 'error' in response:
- numErrors = len(response['error'])
- if numErrors:
- message = self.id + ' ' + body
- for i in range(0, len(response['error'])):
- error = response['error'][i]
- self.throw_exactly_matched_exception(self.exceptions, error, message)
- raise ExchangeError(message)