PageRenderTime 5696ms CodeModel.GetById 26ms RepoModel.GetById 2ms app.codeStats 1ms

/python/ccxt/async_support/kraken.py

https://github.com/kroitor/ccxt
Python | 1567 lines | 1151 code | 55 blank | 361 comment | 132 complexity | 1471d24c3492bfc458d71fff186e5feb MD5 | raw file
Possible License(s): MIT
  1. # -*- coding: utf-8 -*-
  2. # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
  3. # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
  4. from ccxt.async_support.base.exchange import Exchange
  5. # -----------------------------------------------------------------------------
  6. try:
  7. basestring # Python 3
  8. except NameError:
  9. basestring = str # Python 2
  10. import hashlib
  11. import math
  12. from ccxt.base.errors import ExchangeError
  13. from ccxt.base.errors import AuthenticationError
  14. from ccxt.base.errors import PermissionDenied
  15. from ccxt.base.errors import ArgumentsRequired
  16. from ccxt.base.errors import BadSymbol
  17. from ccxt.base.errors import InsufficientFunds
  18. from ccxt.base.errors import InvalidAddress
  19. from ccxt.base.errors import InvalidOrder
  20. from ccxt.base.errors import OrderNotFound
  21. from ccxt.base.errors import CancelPending
  22. from ccxt.base.errors import DDoSProtection
  23. from ccxt.base.errors import ExchangeNotAvailable
  24. from ccxt.base.errors import InvalidNonce
  25. from ccxt.base.decimal_to_precision import TRUNCATE
  26. from ccxt.base.decimal_to_precision import DECIMAL_PLACES
  27. class kraken(Exchange):
  28. def describe(self):
  29. return self.deep_extend(super(kraken, self).describe(), {
  30. 'id': 'kraken',
  31. 'name': 'Kraken',
  32. 'countries': ['US'],
  33. 'version': '0',
  34. 'rateLimit': 3000,
  35. 'certified': True,
  36. 'pro': True,
  37. 'has': {
  38. 'cancelOrder': True,
  39. 'CORS': False,
  40. 'createDepositAddress': True,
  41. 'createOrder': True,
  42. 'fetchBalance': True,
  43. 'fetchClosedOrders': True,
  44. 'fetchCurrencies': True,
  45. 'fetchDepositAddress': True,
  46. 'fetchDeposits': True,
  47. 'fetchLedger': True,
  48. 'fetchLedgerEntry': True,
  49. 'fetchMarkets': True,
  50. 'fetchMyTrades': True,
  51. 'fetchOHLCV': True,
  52. 'fetchOpenOrders': True,
  53. 'fetchOrder': True,
  54. 'fetchOrderBook': True,
  55. 'fetchOrderTrades': 'emulated',
  56. 'fetchTicker': True,
  57. 'fetchTickers': True,
  58. 'fetchTime': True,
  59. 'fetchTrades': True,
  60. 'fetchTradingFee': True,
  61. 'fetchTradingFees': True,
  62. 'fetchWithdrawals': True,
  63. 'withdraw': True,
  64. },
  65. 'marketsByAltname': {},
  66. 'timeframes': {
  67. '1m': 1,
  68. '5m': 5,
  69. '15m': 15,
  70. '30m': 30,
  71. '1h': 60,
  72. '4h': 240,
  73. '1d': 1440,
  74. '1w': 10080,
  75. '2w': 21600,
  76. },
  77. 'urls': {
  78. 'logo': 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg',
  79. 'api': {
  80. 'public': 'https://api.kraken.com',
  81. 'private': 'https://api.kraken.com',
  82. '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
  83. },
  84. 'www': 'https://www.kraken.com',
  85. 'doc': 'https://www.kraken.com/features/api',
  86. 'fees': 'https://www.kraken.com/en-us/features/fee-schedule',
  87. },
  88. 'fees': {
  89. 'trading': {
  90. 'tierBased': True,
  91. 'percentage': True,
  92. 'taker': 0.26 / 100,
  93. 'maker': 0.16 / 100,
  94. 'tiers': {
  95. 'taker': [
  96. [0, 0.0026],
  97. [50000, 0.0024],
  98. [100000, 0.0022],
  99. [250000, 0.0020],
  100. [500000, 0.0018],
  101. [1000000, 0.0016],
  102. [2500000, 0.0014],
  103. [5000000, 0.0012],
  104. [10000000, 0.0001],
  105. ],
  106. 'maker': [
  107. [0, 0.0016],
  108. [50000, 0.0014],
  109. [100000, 0.0012],
  110. [250000, 0.0010],
  111. [500000, 0.0008],
  112. [1000000, 0.0006],
  113. [2500000, 0.0004],
  114. [5000000, 0.0002],
  115. [10000000, 0.0],
  116. ],
  117. },
  118. },
  119. # self is a bad way of hardcoding fees that change on daily basis
  120. # hardcoding is now considered obsolete, we will remove all of it eventually
  121. 'funding': {
  122. 'tierBased': False,
  123. 'percentage': False,
  124. 'withdraw': {
  125. 'BTC': 0.001,
  126. 'ETH': 0.005,
  127. 'XRP': 0.02,
  128. 'XLM': 0.00002,
  129. 'LTC': 0.02,
  130. 'DOGE': 2,
  131. 'ZEC': 0.00010,
  132. 'ICN': 0.02,
  133. 'REP': 0.01,
  134. 'ETC': 0.005,
  135. 'MLN': 0.003,
  136. 'XMR': 0.05,
  137. 'DASH': 0.005,
  138. 'GNO': 0.01,
  139. 'EOS': 0.5,
  140. 'BCH': 0.001,
  141. 'XTZ': 0.05,
  142. 'USD': 5, # if domestic wire
  143. 'EUR': 5, # if domestic wire
  144. 'CAD': 10, # CAD EFT Withdrawal
  145. 'JPY': 300, # if domestic wire
  146. },
  147. 'deposit': {
  148. 'BTC': 0,
  149. 'ETH': 0,
  150. 'XRP': 0,
  151. 'XLM': 0,
  152. 'LTC': 0,
  153. 'DOGE': 0,
  154. 'ZEC': 0,
  155. 'ICN': 0,
  156. 'REP': 0,
  157. 'ETC': 0,
  158. 'MLN': 0,
  159. 'XMR': 0,
  160. 'DASH': 0,
  161. 'GNO': 0,
  162. 'EOS': 0,
  163. 'BCH': 0,
  164. 'XTZ': 0.05,
  165. 'USD': 5, # if domestic wire
  166. 'EUR': 0, # free deposit if EUR SEPA Deposit
  167. 'CAD': 5, # if domestic wire
  168. 'JPY': 0, # Domestic Deposit(Free, ¥5,000 deposit minimum)
  169. },
  170. },
  171. },
  172. 'api': {
  173. 'zendesk': {
  174. 'get': [
  175. # we should really refrain from putting fixed fee numbers and stop hardcoding
  176. # we will be using their web APIs to scrape all numbers from these articles
  177. '360000292886', # -What-are-the-deposit-fees-
  178. '201893608', # -What-are-the-withdrawal-fees-
  179. ],
  180. },
  181. 'public': {
  182. 'get': [
  183. 'Assets',
  184. 'AssetPairs',
  185. 'Depth',
  186. 'OHLC',
  187. 'Spread',
  188. 'Ticker',
  189. 'Time',
  190. 'Trades',
  191. ],
  192. },
  193. 'private': {
  194. 'post': [
  195. 'AddOrder',
  196. 'AddExport',
  197. 'Balance',
  198. 'CancelOrder',
  199. 'ClosedOrders',
  200. 'DepositAddresses',
  201. 'DepositMethods',
  202. 'DepositStatus',
  203. 'ExportStatus',
  204. 'GetWebSocketsToken',
  205. 'Ledgers',
  206. 'OpenOrders',
  207. 'OpenPositions',
  208. 'QueryLedgers',
  209. 'QueryOrders',
  210. 'QueryTrades',
  211. 'RetrieveExport',
  212. 'RemoveExport',
  213. 'TradeBalance',
  214. 'TradesHistory',
  215. 'TradeVolume',
  216. 'Withdraw',
  217. 'WithdrawCancel',
  218. 'WithdrawInfo',
  219. 'WithdrawStatus',
  220. ],
  221. },
  222. },
  223. 'commonCurrencies': {
  224. 'XBT': 'BTC',
  225. 'XDG': 'DOGE',
  226. },
  227. 'options': {
  228. 'cacheDepositMethodsOnFetchDepositAddress': True, # will issue up to two calls in fetchDepositAddress
  229. 'depositMethods': {},
  230. 'delistedMarketsById': {},
  231. # cannot withdraw/deposit these
  232. 'inactiveCurrencies': ['CAD', 'USD', 'JPY', 'GBP'],
  233. },
  234. 'exceptions': {
  235. 'EQuery:Invalid asset pair': BadSymbol, # {"error":["EQuery:Invalid asset pair"]}
  236. 'EAPI:Invalid key': AuthenticationError,
  237. 'EFunding:Unknown withdraw key': ExchangeError,
  238. 'EFunding:Invalid amount': InsufficientFunds,
  239. 'EService:Unavailable': ExchangeNotAvailable,
  240. 'EDatabase:Internal error': ExchangeNotAvailable,
  241. 'EService:Busy': ExchangeNotAvailable,
  242. 'EQuery:Unknown asset': ExchangeError,
  243. 'EAPI:Rate limit exceeded': DDoSProtection,
  244. 'EOrder:Rate limit exceeded': DDoSProtection,
  245. 'EGeneral:Internal error': ExchangeNotAvailable,
  246. 'EGeneral:Temporary lockout': DDoSProtection,
  247. 'EGeneral:Permission denied': PermissionDenied,
  248. 'EOrder:Unknown order': InvalidOrder,
  249. 'EOrder:Order minimum not met': InvalidOrder,
  250. },
  251. })
  252. def cost_to_precision(self, symbol, cost):
  253. return self.decimal_to_precision(cost, TRUNCATE, self.markets[symbol]['precision']['price'], DECIMAL_PLACES)
  254. def fee_to_precision(self, symbol, fee):
  255. return self.decimal_to_precision(fee, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
  256. async def fetch_markets(self, params={}):
  257. response = await self.publicGetAssetPairs(params)
  258. #
  259. # {
  260. # "error":[],
  261. # "result":{
  262. # "ADAETH":{
  263. # "altname":"ADAETH",
  264. # "wsname":"ADA\/ETH",
  265. # "aclass_base":"currency",
  266. # "base":"ADA",
  267. # "aclass_quote":"currency",
  268. # "quote":"XETH",
  269. # "lot":"unit",
  270. # "pair_decimals":7,
  271. # "lot_decimals":8,
  272. # "lot_multiplier":1,
  273. # "leverage_buy":[],
  274. # "leverage_sell":[],
  275. # "fees":[
  276. # [0,0.26],
  277. # [50000,0.24],
  278. # [100000,0.22],
  279. # [250000,0.2],
  280. # [500000,0.18],
  281. # [1000000,0.16],
  282. # [2500000,0.14],
  283. # [5000000,0.12],
  284. # [10000000,0.1]
  285. # ],
  286. # "fees_maker":[
  287. # [0,0.16],
  288. # [50000,0.14],
  289. # [100000,0.12],
  290. # [250000,0.1],
  291. # [500000,0.08],
  292. # [1000000,0.06],
  293. # [2500000,0.04],
  294. # [5000000,0.02],
  295. # [10000000,0]
  296. # ],
  297. # "fee_volume_currency":"ZUSD",
  298. # "margin_call":80,
  299. # "margin_stop":40,
  300. # "ordermin": "1"
  301. # },
  302. # }
  303. # }
  304. #
  305. keys = list(response['result'].keys())
  306. result = []
  307. for i in range(0, len(keys)):
  308. id = keys[i]
  309. market = response['result'][id]
  310. baseId = market['base']
  311. quoteId = market['quote']
  312. base = self.safe_currency_code(baseId)
  313. quote = self.safe_currency_code(quoteId)
  314. darkpool = id.find('.d') >= 0
  315. symbol = market['altname'] if darkpool else (base + '/' + quote)
  316. maker = None
  317. if 'fees_maker' in market:
  318. maker = float(market['fees_maker'][0][1]) / 100
  319. precision = {
  320. 'amount': market['lot_decimals'],
  321. 'price': market['pair_decimals'],
  322. }
  323. minAmount = self.safe_float(market, 'ordermin')
  324. result.append({
  325. 'id': id,
  326. 'symbol': symbol,
  327. 'base': base,
  328. 'quote': quote,
  329. 'baseId': baseId,
  330. 'quoteId': quoteId,
  331. 'darkpool': darkpool,
  332. 'info': market,
  333. 'altname': market['altname'],
  334. 'maker': maker,
  335. 'taker': float(market['fees'][0][1]) / 100,
  336. 'active': True,
  337. 'precision': precision,
  338. 'limits': {
  339. 'amount': {
  340. 'min': minAmount,
  341. 'max': math.pow(10, precision['amount']),
  342. },
  343. 'price': {
  344. 'min': math.pow(10, -precision['price']),
  345. 'max': None,
  346. },
  347. 'cost': {
  348. 'min': 0,
  349. 'max': None,
  350. },
  351. },
  352. })
  353. result = self.append_inactive_markets(result)
  354. self.marketsByAltname = self.index_by(result, 'altname')
  355. return result
  356. def safe_currency_code(self, currencyId, currency=None):
  357. if len(currencyId) > 3:
  358. if (currencyId.find('X') == 0) or (currencyId.find('Z') == 0):
  359. currencyId = currencyId[1:]
  360. return super(kraken, self).safe_currency_code(currencyId, currency)
  361. def append_inactive_markets(self, result):
  362. # result should be an array to append to
  363. precision = {'amount': 8, 'price': 8}
  364. costLimits = {'min': 0, 'max': None}
  365. priceLimits = {'min': math.pow(10, -precision['price']), 'max': None}
  366. amountLimits = {'min': math.pow(10, -precision['amount']), 'max': math.pow(10, precision['amount'])}
  367. limits = {'amount': amountLimits, 'price': priceLimits, 'cost': costLimits}
  368. defaults = {
  369. 'darkpool': False,
  370. 'info': None,
  371. 'maker': None,
  372. 'taker': None,
  373. 'active': False,
  374. 'precision': precision,
  375. 'limits': limits,
  376. }
  377. markets = [
  378. # {'id': 'XXLMZEUR', 'symbol': 'XLM/EUR', 'base': 'XLM', 'quote': 'EUR', 'altname': 'XLMEUR'},
  379. ]
  380. for i in range(0, len(markets)):
  381. result.append(self.extend(defaults, markets[i]))
  382. return result
  383. async def fetch_currencies(self, params={}):
  384. response = await self.publicGetAssets(params)
  385. #
  386. # {
  387. # "error": [],
  388. # "result": {
  389. # "ADA": {"aclass": "currency", "altname": "ADA", "decimals": 8, "display_decimals": 6},
  390. # "BCH": {"aclass": "currency", "altname": "BCH", "decimals": 10, "display_decimals": 5},
  391. # ...
  392. # },
  393. # }
  394. #
  395. currencies = self.safe_value(response, 'result')
  396. ids = list(currencies.keys())
  397. result = {}
  398. for i in range(0, len(ids)):
  399. id = ids[i]
  400. currency = currencies[id]
  401. # todo: will need to rethink the fees
  402. # see: https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees-
  403. # to add support for multiple withdrawal/deposit methods and
  404. # differentiated fees for each particular method
  405. code = self.safe_currency_code(self.safe_string(currency, 'altname'))
  406. precision = self.safe_integer(currency, 'decimals')
  407. # assumes all currencies are active except those listed above
  408. active = not self.in_array(code, self.options['inactiveCurrencies'])
  409. result[code] = {
  410. 'id': id,
  411. 'code': code,
  412. 'info': currency,
  413. 'name': code,
  414. 'active': active,
  415. 'fee': None,
  416. 'precision': precision,
  417. 'limits': {
  418. 'amount': {
  419. 'min': math.pow(10, -precision),
  420. 'max': math.pow(10, precision),
  421. },
  422. 'price': {
  423. 'min': math.pow(10, -precision),
  424. 'max': math.pow(10, precision),
  425. },
  426. 'cost': {
  427. 'min': None,
  428. 'max': None,
  429. },
  430. 'withdraw': {
  431. 'min': None,
  432. 'max': math.pow(10, precision),
  433. },
  434. },
  435. }
  436. return result
  437. async def fetch_trading_fees(self, params={}):
  438. await self.load_markets()
  439. self.check_required_credentials()
  440. response = await self.privatePostTradeVolume(params)
  441. tradedVolume = self.safe_float(response['result'], 'volume')
  442. tiers = self.fees['trading']['tiers']
  443. taker = tiers['taker'][1]
  444. maker = tiers['maker'][1]
  445. for i in range(0, len(tiers['taker'])):
  446. if tradedVolume >= tiers['taker'][i][0]:
  447. taker = tiers['taker'][i][1]
  448. for i in range(0, len(tiers['maker'])):
  449. if tradedVolume >= tiers['maker'][i][0]:
  450. maker = tiers['maker'][i][1]
  451. return {
  452. 'info': response,
  453. 'maker': maker,
  454. 'taker': taker,
  455. }
  456. def parse_bid_ask(self, bidask, priceKey=0, amountKey=1):
  457. price = self.safe_float(bidask, priceKey)
  458. amount = self.safe_float(bidask, amountKey)
  459. timestamp = self.safe_integer(bidask, 2)
  460. return [price, amount, timestamp]
  461. async def fetch_order_book(self, symbol, limit=None, params={}):
  462. await self.load_markets()
  463. market = self.market(symbol)
  464. if market['darkpool']:
  465. raise ExchangeError(self.id + ' does not provide an order book for darkpool symbol ' + symbol)
  466. request = {
  467. 'pair': market['id'],
  468. }
  469. if limit is not None:
  470. request['count'] = limit # 100
  471. response = await self.publicGetDepth(self.extend(request, params))
  472. #
  473. # {
  474. # "error":[],
  475. # "result":{
  476. # "XETHXXBT":{
  477. # "asks":[
  478. # ["0.023480","4.000",1586321307],
  479. # ["0.023490","50.095",1586321306],
  480. # ["0.023500","28.535",1586321302],
  481. # ],
  482. # "bids":[
  483. # ["0.023470","59.580",1586321307],
  484. # ["0.023460","20.000",1586321301],
  485. # ["0.023440","67.832",1586321306],
  486. # ]
  487. # }
  488. # }
  489. # }
  490. #
  491. result = self.safe_value(response, 'result', {})
  492. orderbook = self.safe_value(result, market['id'])
  493. return self.parse_order_book(orderbook)
  494. def parse_ticker(self, ticker, market=None):
  495. timestamp = self.milliseconds()
  496. symbol = None
  497. if market:
  498. symbol = market['symbol']
  499. baseVolume = float(ticker['v'][1])
  500. vwap = float(ticker['p'][1])
  501. quoteVolume = None
  502. if baseVolume is not None and vwap is not None:
  503. quoteVolume = baseVolume * vwap
  504. last = float(ticker['c'][0])
  505. return {
  506. 'symbol': symbol,
  507. 'timestamp': timestamp,
  508. 'datetime': self.iso8601(timestamp),
  509. 'high': float(ticker['h'][1]),
  510. 'low': float(ticker['l'][1]),
  511. 'bid': float(ticker['b'][0]),
  512. 'bidVolume': None,
  513. 'ask': float(ticker['a'][0]),
  514. 'askVolume': None,
  515. 'vwap': vwap,
  516. 'open': self.safe_float(ticker, 'o'),
  517. 'close': last,
  518. 'last': last,
  519. 'previousClose': None,
  520. 'change': None,
  521. 'percentage': None,
  522. 'average': None,
  523. 'baseVolume': baseVolume,
  524. 'quoteVolume': quoteVolume,
  525. 'info': ticker,
  526. }
  527. async def fetch_tickers(self, symbols=None, params={}):
  528. await self.load_markets()
  529. symbols = self.symbols if (symbols is None) else symbols
  530. marketIds = []
  531. for i in range(0, len(symbols)):
  532. symbol = symbols[i]
  533. market = self.markets[symbol]
  534. if market['active'] and not market['darkpool']:
  535. marketIds.append(market['id'])
  536. request = {
  537. 'pair': ','.join(marketIds),
  538. }
  539. response = await self.publicGetTicker(self.extend(request, params))
  540. tickers = response['result']
  541. ids = list(tickers.keys())
  542. result = {}
  543. for i in range(0, len(ids)):
  544. id = ids[i]
  545. market = self.markets_by_id[id]
  546. symbol = market['symbol']
  547. ticker = tickers[id]
  548. result[symbol] = self.parse_ticker(ticker, market)
  549. return self.filter_by_array(result, 'symbol', symbols)
  550. async def fetch_ticker(self, symbol, params={}):
  551. await self.load_markets()
  552. darkpool = symbol.find('.d') >= 0
  553. if darkpool:
  554. raise ExchangeError(self.id + ' does not provide a ticker for darkpool symbol ' + symbol)
  555. market = self.market(symbol)
  556. request = {
  557. 'pair': market['id'],
  558. }
  559. response = await self.publicGetTicker(self.extend(request, params))
  560. ticker = response['result'][market['id']]
  561. return self.parse_ticker(ticker, market)
  562. def parse_ohlcv(self, ohlcv, market=None):
  563. #
  564. # [
  565. # 1591475640,
  566. # "0.02500",
  567. # "0.02500",
  568. # "0.02500",
  569. # "0.02500",
  570. # "0.02500",
  571. # "9.12201000",
  572. # 5
  573. # ]
  574. #
  575. return [
  576. self.safe_timestamp(ohlcv, 0),
  577. self.safe_float(ohlcv, 1),
  578. self.safe_float(ohlcv, 2),
  579. self.safe_float(ohlcv, 3),
  580. self.safe_float(ohlcv, 4),
  581. self.safe_float(ohlcv, 6),
  582. ]
  583. async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
  584. await self.load_markets()
  585. market = self.market(symbol)
  586. request = {
  587. 'pair': market['id'],
  588. 'interval': self.timeframes[timeframe],
  589. }
  590. if since is not None:
  591. request['since'] = int((since - 1) / 1000)
  592. response = await self.publicGetOHLC(self.extend(request, params))
  593. #
  594. # {
  595. # "error":[],
  596. # "result":{
  597. # "XETHXXBT":[
  598. # [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0],
  599. # [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5],
  600. # [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2],
  601. # [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1],
  602. # ],
  603. # "last":1591517580
  604. # }
  605. # }
  606. result = self.safe_value(response, 'result', {})
  607. ohlcvs = self.safe_value(result, market['id'], [])
  608. return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
  609. def parse_ledger_entry_type(self, type):
  610. types = {
  611. 'trade': 'trade',
  612. 'withdrawal': 'transaction',
  613. 'deposit': 'transaction',
  614. 'transfer': 'transfer',
  615. 'margin': 'margin',
  616. }
  617. return self.safe_string(types, type, type)
  618. def parse_ledger_entry(self, item, currency=None):
  619. #
  620. # {
  621. # 'LTFK7F-N2CUX-PNY4SX': {
  622. # refid: "TSJTGT-DT7WN-GPPQMJ",
  623. # time: 1520102320.555,
  624. # type: "trade",
  625. # aclass: "currency",
  626. # asset: "XETH",
  627. # amount: "0.1087194600",
  628. # fee: "0.0000000000",
  629. # balance: "0.2855851000"
  630. # },
  631. # ...
  632. # }
  633. #
  634. id = self.safe_string(item, 'id')
  635. direction = None
  636. account = None
  637. referenceId = self.safe_string(item, 'refid')
  638. referenceAccount = None
  639. type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
  640. code = self.safe_currency_code(self.safe_string(item, 'asset'), currency)
  641. amount = self.safe_float(item, 'amount')
  642. if amount < 0:
  643. direction = 'out'
  644. amount = abs(amount)
  645. else:
  646. direction = 'in'
  647. time = self.safe_float(item, 'time')
  648. timestamp = None
  649. if time is not None:
  650. timestamp = int(time * 1000)
  651. fee = {
  652. 'cost': self.safe_float(item, 'fee'),
  653. 'currency': code,
  654. }
  655. before = None
  656. after = self.safe_float(item, 'balance')
  657. status = 'ok'
  658. return {
  659. 'info': item,
  660. 'id': id,
  661. 'direction': direction,
  662. 'account': account,
  663. 'referenceId': referenceId,
  664. 'referenceAccount': referenceAccount,
  665. 'type': type,
  666. 'currency': code,
  667. 'amount': amount,
  668. 'before': before,
  669. 'after': after,
  670. 'status': status,
  671. 'timestamp': timestamp,
  672. 'datetime': self.iso8601(timestamp),
  673. 'fee': fee,
  674. }
  675. async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
  676. # https://www.kraken.com/features/api#get-ledgers-info
  677. await self.load_markets()
  678. request = {}
  679. currency = None
  680. if code is not None:
  681. currency = self.currency(code)
  682. request['asset'] = currency['id']
  683. if since is not None:
  684. request['start'] = int(since / 1000)
  685. response = await self.privatePostLedgers(self.extend(request, params))
  686. # { error: [],
  687. # result: {ledger: {'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N",
  688. # time: 1520103488.314,
  689. # type: "withdrawal",
  690. # aclass: "currency",
  691. # asset: "XETH",
  692. # amount: "-0.2805800000",
  693. # fee: "0.0050000000",
  694. # balance: "0.0000051000" },
  695. result = self.safe_value(response, 'result', {})
  696. ledger = self.safe_value(result, 'ledger', {})
  697. keys = list(ledger.keys())
  698. items = []
  699. for i in range(0, len(keys)):
  700. key = keys[i]
  701. value = ledger[key]
  702. value['id'] = key
  703. items.append(value)
  704. return self.parse_ledger(items, currency, since, limit)
  705. async def fetch_ledger_entries_by_ids(self, ids, code=None, params={}):
  706. # https://www.kraken.com/features/api#query-ledgers
  707. await self.load_markets()
  708. ids = ','.join(ids)
  709. request = self.extend({
  710. 'id': ids,
  711. }, params)
  712. response = await self.privatePostQueryLedgers(request)
  713. # { error: [],
  714. # result: {'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N",
  715. # time: 1520103488.314,
  716. # type: "withdrawal",
  717. # aclass: "currency",
  718. # asset: "XETH",
  719. # amount: "-0.2805800000",
  720. # fee: "0.0050000000",
  721. # balance: "0.0000051000" }}}
  722. result = response['result']
  723. keys = list(result.keys())
  724. items = []
  725. for i in range(0, len(keys)):
  726. key = keys[i]
  727. value = result[key]
  728. value['id'] = key
  729. items.append(value)
  730. return self.parse_ledger(items)
  731. async def fetch_ledger_entry(self, id, code=None, params={}):
  732. items = await self.fetch_ledger_entries_by_ids([id], code, params)
  733. return items[0]
  734. def parse_trade(self, trade, market=None):
  735. #
  736. # fetchTrades(public)
  737. #
  738. # [
  739. # "0.032310", # price
  740. # "4.28169434", # amount
  741. # 1541390792.763, # timestamp
  742. # "s", # sell or buy
  743. # "l", # limit or market
  744. # ""
  745. # ]
  746. #
  747. # fetchOrderTrades(private)
  748. #
  749. # {
  750. # id: 'TIMIRG-WUNNE-RRJ6GT', # injected from outside
  751. # ordertxid: 'OQRPN2-LRHFY-HIFA7D',
  752. # postxid: 'TKH2SE-M7IF5-CFI7LT',
  753. # pair: 'USDCUSDT',
  754. # time: 1586340086.457,
  755. # type: 'sell',
  756. # ordertype: 'market',
  757. # price: '0.99860000',
  758. # cost: '22.16892001',
  759. # fee: '0.04433784',
  760. # vol: '22.20000000',
  761. # margin: '0.00000000',
  762. # misc: ''
  763. # }
  764. #
  765. timestamp = None
  766. side = None
  767. type = None
  768. price = None
  769. amount = None
  770. cost = None
  771. id = None
  772. order = None
  773. fee = None
  774. symbol = None
  775. if isinstance(trade, list):
  776. timestamp = self.safe_timestamp(trade, 2)
  777. side = 'sell' if (trade[3] == 's') else 'buy'
  778. type = 'limit' if (trade[4] == 'l') else 'market'
  779. price = self.safe_float(trade, 0)
  780. amount = self.safe_float(trade, 1)
  781. tradeLength = len(trade)
  782. if tradeLength > 6:
  783. id = self.safe_string(trade, 6) # artificially added as per #1794
  784. elif isinstance(trade, basestring):
  785. id = trade
  786. elif 'ordertxid' in trade:
  787. marketId = self.safe_string(trade, 'pair')
  788. foundMarket = self.find_market_by_altname_or_id(marketId)
  789. if foundMarket is not None:
  790. market = foundMarket
  791. elif marketId is not None:
  792. # delisted market ids go here
  793. market = self.get_delisted_market_by_id(marketId)
  794. order = trade['ordertxid']
  795. id = self.safe_string_2(trade, 'id', 'postxid')
  796. timestamp = self.safe_timestamp(trade, 'time')
  797. side = self.safe_string(trade, 'type')
  798. type = self.safe_string(trade, 'ordertype')
  799. price = self.safe_float(trade, 'price')
  800. amount = self.safe_float(trade, 'vol')
  801. if 'fee' in trade:
  802. currency = None
  803. if market is not None:
  804. currency = market['quote']
  805. fee = {
  806. 'cost': self.safe_float(trade, 'fee'),
  807. 'currency': currency,
  808. }
  809. if market is not None:
  810. symbol = market['symbol']
  811. if price is not None:
  812. if amount is not None:
  813. cost = price * amount
  814. return {
  815. 'id': id,
  816. 'order': order,
  817. 'info': trade,
  818. 'timestamp': timestamp,
  819. 'datetime': self.iso8601(timestamp),
  820. 'symbol': symbol,
  821. 'type': type,
  822. 'side': side,
  823. 'takerOrMaker': None,
  824. 'price': price,
  825. 'amount': amount,
  826. 'cost': cost,
  827. 'fee': fee,
  828. }
  829. async def fetch_trades(self, symbol, since=None, limit=None, params={}):
  830. await self.load_markets()
  831. market = self.market(symbol)
  832. id = market['id']
  833. request = {
  834. 'pair': id,
  835. }
  836. # https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API
  837. # https://github.com/ccxt/ccxt/issues/5677
  838. if since is not None:
  839. # php does not format it properly
  840. # therefore we use string concatenation here
  841. request['since'] = since * 1e6
  842. request['since'] = str(since) + '000000' # expected to be in nanoseconds
  843. # https://github.com/ccxt/ccxt/issues/5698
  844. if limit is not None and limit != 1000:
  845. fetchTradesWarning = self.safe_value(self.options, 'fetchTradesWarning', True)
  846. if fetchTradesWarning:
  847. 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.")
  848. response = await self.publicGetTrades(self.extend(request, params))
  849. #
  850. # {
  851. # "error": [],
  852. # "result": {
  853. # "XETHXXBT": [
  854. # ["0.032310","4.28169434",1541390792.763,"s","l",""]
  855. # ],
  856. # "last": "1541439421200678657"
  857. # }
  858. # }
  859. #
  860. result = response['result']
  861. trades = result[id]
  862. # trades is a sorted array: last(most recent trade) goes last
  863. length = len(trades)
  864. if length <= 0:
  865. return []
  866. lastTrade = trades[length - 1]
  867. lastTradeId = self.safe_string(result, 'last')
  868. lastTrade.append(lastTradeId)
  869. return self.parse_trades(trades, market, since, limit)
  870. async def fetch_balance(self, params={}):
  871. response = await self.privatePostBalance(params)
  872. balances = self.safe_value(response, 'result', {})
  873. result = {'info': balances}
  874. currencyIds = list(balances.keys())
  875. for i in range(0, len(currencyIds)):
  876. currencyId = currencyIds[i]
  877. code = self.safe_currency_code(currencyId)
  878. account = self.account()
  879. account['total'] = self.safe_float(balances, currencyId)
  880. result[code] = account
  881. return self.parse_balance(result)
  882. async def create_order(self, symbol, type, side, amount, price=None, params={}):
  883. await self.load_markets()
  884. market = self.market(symbol)
  885. request = {
  886. 'pair': market['id'],
  887. 'type': side,
  888. 'ordertype': type,
  889. 'volume': self.amount_to_precision(symbol, amount),
  890. }
  891. clientOrderId = self.safe_string_2(params, 'userref', 'clientOrderId')
  892. query = self.omit(params, ['userref', 'clientOrderId'])
  893. if clientOrderId is not None:
  894. request['userref'] = clientOrderId
  895. priceIsDefined = (price is not None)
  896. marketOrder = (type == 'market')
  897. limitOrder = (type == 'limit')
  898. shouldIncludePrice = limitOrder or (not marketOrder and priceIsDefined)
  899. if shouldIncludePrice:
  900. request['price'] = self.price_to_precision(symbol, price)
  901. response = await self.privatePostAddOrder(self.extend(request, query))
  902. id = self.safe_value(response['result'], 'txid')
  903. if id is not None:
  904. if isinstance(id, list):
  905. length = len(id)
  906. id = id if (length > 1) else id[0]
  907. return {
  908. 'id': id,
  909. 'clientOrderId': clientOrderId,
  910. 'info': response,
  911. 'timestamp': None,
  912. 'datetime': None,
  913. 'lastTradeTimestamp': None,
  914. 'symbol': symbol,
  915. 'type': type,
  916. 'side': side,
  917. 'price': None,
  918. 'amount': None,
  919. 'cost': None,
  920. 'average': None,
  921. 'filled': None,
  922. 'remaining': None,
  923. 'status': None,
  924. 'fee': None,
  925. 'trades': None,
  926. }
  927. def find_market_by_altname_or_id(self, id):
  928. if id in self.marketsByAltname:
  929. return self.marketsByAltname[id]
  930. elif id in self.markets_by_id:
  931. return self.markets_by_id[id]
  932. return None
  933. def get_delisted_market_by_id(self, id):
  934. if id is None:
  935. return id
  936. market = self.safe_value(self.options['delistedMarketsById'], id)
  937. if market is not None:
  938. return market
  939. baseIdStart = 0
  940. baseIdEnd = 3
  941. quoteIdStart = 3
  942. quoteIdEnd = 6
  943. if len(id) == 8:
  944. baseIdEnd = 4
  945. quoteIdStart = 4
  946. quoteIdEnd = 8
  947. elif len(id) == 7:
  948. baseIdEnd = 4
  949. quoteIdStart = 4
  950. quoteIdEnd = 7
  951. baseId = id[baseIdStart:baseIdEnd]
  952. quoteId = id[quoteIdStart:quoteIdEnd]
  953. base = self.safe_currency_code(baseId)
  954. quote = self.safe_currency_code(quoteId)
  955. symbol = base + '/' + quote
  956. market = {
  957. 'symbol': symbol,
  958. 'base': base,
  959. 'quote': quote,
  960. 'baseId': baseId,
  961. 'quoteId': quoteId,
  962. }
  963. self.options['delistedMarketsById'][id] = market
  964. return market
  965. def parse_order_status(self, status):
  966. statuses = {
  967. 'pending': 'open', # order pending book entry
  968. 'open': 'open',
  969. 'closed': 'closed',
  970. 'canceled': 'canceled',
  971. 'expired': 'expired',
  972. }
  973. return self.safe_string(statuses, status, status)
  974. def parse_order(self, order, market=None):
  975. description = self.safe_value(order, 'descr', {})
  976. side = self.safe_string(description, 'type')
  977. type = self.safe_string(description, 'ordertype')
  978. marketId = self.safe_string(description, 'pair')
  979. foundMarket = self.find_market_by_altname_or_id(marketId)
  980. symbol = None
  981. if foundMarket is not None:
  982. market = foundMarket
  983. elif marketId is not None:
  984. # delisted market ids go here
  985. market = self.get_delisted_market_by_id(marketId)
  986. timestamp = self.safe_timestamp(order, 'opentm')
  987. amount = self.safe_float(order, 'vol')
  988. filled = self.safe_float(order, 'vol_exec')
  989. remaining = amount - filled
  990. fee = None
  991. cost = self.safe_float(order, 'cost')
  992. price = self.safe_float(description, 'price')
  993. if (price is None) or (price == 0):
  994. price = self.safe_float(description, 'price2')
  995. if (price is None) or (price == 0):
  996. price = self.safe_float(order, 'price', price)
  997. average = self.safe_float(order, 'price')
  998. if market is not None:
  999. symbol = market['symbol']
  1000. if 'fee' in order:
  1001. flags = order['oflags']
  1002. feeCost = self.safe_float(order, 'fee')
  1003. fee = {
  1004. 'cost': feeCost,
  1005. 'rate': None,
  1006. }
  1007. if flags.find('fciq') >= 0:
  1008. fee['currency'] = market['quote']
  1009. elif flags.find('fcib') >= 0:
  1010. fee['currency'] = market['base']
  1011. status = self.parse_order_status(self.safe_string(order, 'status'))
  1012. id = self.safe_string(order, 'id')
  1013. clientOrderId = self.safe_string(order, 'userref')
  1014. rawTrades = self.safe_value(order, 'trades')
  1015. trades = None
  1016. if rawTrades is not None:
  1017. trades = self.parse_trades(rawTrades, market, None, None, {'order': id})
  1018. return {
  1019. 'id': id,
  1020. 'clientOrderId': clientOrderId,
  1021. 'info': order,
  1022. 'timestamp': timestamp,
  1023. 'datetime': self.iso8601(timestamp),
  1024. 'lastTradeTimestamp': None,
  1025. 'status': status,
  1026. 'symbol': symbol,
  1027. 'type': type,
  1028. 'side': side,
  1029. 'price': price,
  1030. 'cost': cost,
  1031. 'amount': amount,
  1032. 'filled': filled,
  1033. 'average': average,
  1034. 'remaining': remaining,
  1035. 'fee': fee,
  1036. 'trades': trades,
  1037. }
  1038. def parse_orders(self, orders, market=None, since=None, limit=None, params={}):
  1039. result = []
  1040. ids = list(orders.keys())
  1041. symbol = None
  1042. if market is not None:
  1043. symbol = market['symbol']
  1044. for i in range(0, len(ids)):
  1045. id = ids[i]
  1046. order = self.extend({'id': id}, orders[id])
  1047. result.append(self.extend(self.parse_order(order, market), params))
  1048. result = self.sort_by(result, 'timestamp')
  1049. return self.filter_by_symbol_since_limit(result, symbol, since, limit)
  1050. async def fetch_order(self, id, symbol=None, params={}):
  1051. await self.load_markets()
  1052. clientOrderId = self.safe_value_2(params, 'userref', 'clientOrderId')
  1053. request = {
  1054. 'trades': True, # whether or not to include trades in output(optional, default False)
  1055. # 'txid': id, # do not comma separate a list of ids - use fetchOrdersByIds instead
  1056. # 'userref': 'optional', # restrict results to given user reference id(optional)
  1057. }
  1058. query = params
  1059. if clientOrderId is not None:
  1060. request['userref'] = clientOrderId
  1061. query = self.omit(params, ['userref', 'clientOrderId'])
  1062. else:
  1063. request['txid'] = id
  1064. response = await self.privatePostQueryOrders(self.extend(request, query))
  1065. #
  1066. # {
  1067. # "error":[],
  1068. # "result":{
  1069. # "OTLAS3-RRHUF-NDWH5A":{
  1070. # "refid":null,
  1071. # "userref":null,
  1072. # "status":"closed",
  1073. # "reason":null,
  1074. # "opentm":1586822919.3342,
  1075. # "closetm":1586822919.365,
  1076. # "starttm":0,
  1077. # "expiretm":0,
  1078. # "descr":{
  1079. # "pair":"XBTUSDT",
  1080. # "type":"sell",
  1081. # "ordertype":"market",
  1082. # "price":"0",
  1083. # "price2":"0",
  1084. # "leverage":"none",
  1085. # "order":"sell 0.21804000 XBTUSDT @ market",
  1086. # "close":""
  1087. # },
  1088. # "vol":"0.21804000",
  1089. # "vol_exec":"0.21804000",
  1090. # "cost":"1493.9",
  1091. # "fee":"3.8",
  1092. # "price":"6851.5",
  1093. # "stopprice":"0.00000",
  1094. # "limitprice":"0.00000",
  1095. # "misc":"",
  1096. # "oflags":"fciq",
  1097. # "trades":["TT5UC3-GOIRW-6AZZ6R"]
  1098. # }
  1099. # }
  1100. # }
  1101. #
  1102. orders = self.safe_value(response, 'result', [])
  1103. order = self.parse_order(self.extend({'id': id}, orders[id]))
  1104. return self.extend({'info': response}, order)
  1105. async def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
  1106. orderTrades = self.safe_value(params, 'trades')
  1107. tradeIds = []
  1108. if orderTrades is None:
  1109. raise ArgumentsRequired(self.id + " fetchOrderTrades requires a unified order structure in the params argument or a 'trades' param(an array of trade id strings)")
  1110. else:
  1111. for i in range(0, len(orderTrades)):
  1112. orderTrade = orderTrades[i]
  1113. if isinstance(orderTrade, basestring):
  1114. tradeIds.append(orderTrade)
  1115. else:
  1116. tradeIds.append(orderTrade['id'])
  1117. await self.load_markets()
  1118. options = self.safe_value(self.options, 'fetchOrderTrades', {})
  1119. batchSize = self.safe_integer(options, 'batchSize', 20)
  1120. numBatches = int(tradeIds / batchSize)
  1121. numBatches = self.sum(numBatches, 1)
  1122. numTradeIds = len(tradeIds)
  1123. result = []
  1124. for j in range(0, numBatches):
  1125. requestIds = []
  1126. for k in range(0, batchSize):
  1127. index = self.sum(j * batchSize, k)
  1128. if index < numTradeIds:
  1129. requestIds.append(tradeIds[index])
  1130. request = {
  1131. 'txid': ','.join(requestIds),
  1132. }
  1133. response = await self.privatePostQueryTrades(request)
  1134. #
  1135. # {
  1136. # error: [],
  1137. # result: {
  1138. # 'TIMIRG-WUNNE-RRJ6GT': {
  1139. # ordertxid: 'OQRPN2-LRHFY-HIFA7D',
  1140. # postxid: 'TKH2SE-M7IF5-CFI7LT',
  1141. # pair: 'USDCUSDT',
  1142. # time: 1586340086.457,
  1143. # type: 'sell',
  1144. # ordertype: 'market',
  1145. # price: '0.99860000',
  1146. # cost: '22.16892001',
  1147. # fee: '0.04433784',
  1148. # vol: '22.20000000',
  1149. # margin: '0.00000000',
  1150. # misc: ''
  1151. # }
  1152. # }
  1153. # }
  1154. #
  1155. rawTrades = self.safe_value(response, 'result')
  1156. ids = list(rawTrades.keys())
  1157. for i in range(0, len(ids)):
  1158. rawTrades[ids[i]]['id'] = ids[i]
  1159. trades = self.parse_trades(rawTrades, None, since, limit)
  1160. tradesFilteredBySymbol = self.filter_by_symbol(trades, symbol)
  1161. result = self.array_concat(result, tradesFilteredBySymbol)
  1162. return result
  1163. async def fetch_orders_by_ids(self, ids, symbol=None, params={}):
  1164. await self.load_markets()
  1165. response = await self.privatePostQueryOrders(self.extend({
  1166. 'trades': True, # whether or not to include trades in output(optional, default False)
  1167. 'txid': ','.join(ids), # comma delimited list of transaction ids to query info about(20 maximum)
  1168. }, params))
  1169. result = self.safe_value(response, 'result', {})
  1170. orders = []
  1171. orderIds = list(result.keys())
  1172. for i in range(0, len(orderIds)):
  1173. id = orderIds[i]
  1174. item = result[id]
  1175. order = self.parse_order(self.extend({'id': id}, item))
  1176. orders.append(order)
  1177. return orders
  1178. async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
  1179. await self.load_markets()
  1180. request = {
  1181. # 'type': 'all', # any position, closed position, closing position, no position
  1182. # 'trades': False, # whether or not to include trades related to position in output
  1183. # 'start': 1234567890, # starting unix timestamp or trade tx id of results(exclusive)
  1184. # 'end': 1234567890, # ending unix timestamp or trade tx id of results(inclusive)
  1185. # 'ofs' = result offset
  1186. }
  1187. if since is not None:
  1188. request['start'] = int(since / 1000)
  1189. response = await self.privatePostTradesHistory(self.extend(request, params))
  1190. #
  1191. # {
  1192. # "error": [],
  1193. # "result": {
  1194. # "trades": {
  1195. # "GJ3NYQ-XJRTF-THZABF": {
  1196. # "ordertxid": "TKH2SE-ZIF5E-CFI7LT",
  1197. # "postxid": "OEN3VX-M7IF5-JNBJAM",
  1198. # "pair": "XICNXETH",
  1199. # "time": 1527213229.4491,
  1200. # "type": "sell",
  1201. # "ordertype": "limit",
  1202. # "price": "0.001612",
  1203. # "cost": "0.025792",
  1204. # "fee": "0.000026",
  1205. # "vol": "16.00000000",
  1206. # "margin": "0.000000",
  1207. # "misc": ""
  1208. # },
  1209. # ...
  1210. # },
  1211. # "count": 9760,
  1212. # },
  1213. # }
  1214. #
  1215. trades = response['result']['trades']
  1216. ids = list(trades.keys())
  1217. for i in range(0, len(ids)):
  1218. trades[ids[i]]['id'] = ids[i]
  1219. result = self.parse_trades(trades, None, since, limit)
  1220. if symbol is None:
  1221. return result
  1222. return self.filter_by_symbol(result, symbol)
  1223. async def cancel_order(self, id, symbol=None, params={}):
  1224. await self.load_markets()
  1225. response = None
  1226. try:
  1227. response = await self.privatePostCancelOrder(self.extend({
  1228. 'txid': id,
  1229. }, params))
  1230. except Exception as e:
  1231. if self.last_http_response:
  1232. if self.last_http_response.find('EOrder:Unknown order') >= 0:
  1233. raise OrderNotFound(self.id + ' cancelOrder() error ' + self.last_http_response)
  1234. raise e
  1235. return response
  1236. async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
  1237. await self.load_markets()
  1238. request = {}
  1239. if since is not None:
  1240. request['start'] = int(since / 1000)
  1241. response = await self.privatePostOpenOrders(self.extend(request, params))
  1242. orders = self.parse_orders(response['result']['open'], None, since, limit)
  1243. if symbol is None:
  1244. return orders
  1245. return self.filter_by_symbol(orders, symbol)
  1246. async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
  1247. await self.load_markets()
  1248. request = {}
  1249. if since is not None:
  1250. request['start'] = int(since / 1000)
  1251. response = await self.privatePostClosedOrders(self.extend(request, params))
  1252. orders = self.parse_orders(response['result']['closed'], None, since, limit)
  1253. if symbol is None:
  1254. return orders
  1255. return self.filter_by_symbol(orders, symbol)
  1256. async def fetch_deposit_methods(self, code, params={}):
  1257. await self.load_markets()
  1258. currency = self.currency(code)
  1259. request = {
  1260. 'asset': currency['id'],
  1261. }
  1262. response = await self.privatePostDepositMethods(self.extend(request, params))
  1263. return response['result']
  1264. def parse_transaction_status(self, status):
  1265. # IFEX transaction states
  1266. statuses = {
  1267. 'Initial': 'pending',
  1268. 'Pending': 'pending',
  1269. 'Success': 'ok',
  1270. 'Settled': 'pending',
  1271. 'Failure': 'failed',
  1272. 'Partial': 'ok',
  1273. }
  1274. return self.safe_string(statuses, status, status)
  1275. def parse_transaction(self, transaction, currency=None):
  1276. #
  1277. # fetchDeposits
  1278. #
  1279. # {method: "Ether(Hex)",
  1280. # aclass: "currency",
  1281. # asset: "XETH",
  1282. # refid: "Q2CANKL-LBFVEE-U4Y2WQ",
  1283. # txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
  1284. # info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
  1285. # amount: "7.9999257900",
  1286. # fee: "0.0000000000",
  1287. # time: 1529223212,
  1288. # status: "Success" }
  1289. #
  1290. # fetchWithdrawals
  1291. #
  1292. # {method: "Ether",
  1293. # aclass: "currency",
  1294. # asset: "XETH",
  1295. # refid: "A2BF34S-O7LBNQ-UE4Y4O",
  1296. # txid: "0x288b83c6b0904d8400ef44e1c9e2187b5c8f7ea3d838222d53f701a15b5c274d",
  1297. # info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
  1298. # amount: "9.9950000000",
  1299. # fee: "0.0050000000",
  1300. # time: 1530481750,
  1301. # status: "Success" }
  1302. #
  1303. id = self.safe_string(transaction, 'refid')
  1304. txid = self.safe_string(transaction, 'txid')
  1305. timestamp = self.safe_timestamp(transaction, 'time')
  1306. currencyId = self.safe_string(transaction, 'asset')
  1307. code = self.safe_currency_code(currencyId, currency)
  1308. address = self.safe_string(transaction, 'info')
  1309. amount = self.safe_float(transaction, 'amount')
  1310. status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
  1311. type = self.safe_string(transaction, 'type') # injected from the outside
  1312. feeCost = self.safe_float(transaction, 'fee')
  1313. if feeCost is None:
  1314. if type == 'deposit':
  1315. feeCost = 0
  1316. return {
  1317. 'info': transaction,
  1318. 'id': id,
  1319. 'currency': code,
  1320. 'amount': amount,
  1321. 'address': address,
  1322. 'tag': None,
  1323. 'status': status,
  1324. 'type': type,
  1325. 'updated': None,
  1326. 'txid': txid,
  1327. 'timestamp': timestamp,
  1328. 'datetime': self.iso8601(timestamp),
  1329. 'fee': {
  1330. 'currency': code,
  1331. 'cost': feeCost,
  1332. },
  1333. }
  1334. def parse_transactions_by_type(self, type, transactions, code=None, since=None, limit=None):
  1335. result = []
  1336. for i in range(0, len(transactions)):
  1337. transaction = self.parse_transaction(self.extend({
  1338. 'type': type,
  1339. }, transactions[i]))
  1340. result.append(transaction)
  1341. return self.filter_by_currency_since_limit(result, code, since, limit)
  1342. async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
  1343. # https://www.kraken.com/en-us/help/api#deposit-status
  1344. if code is None:
  1345. raise ArgumentsRequired(self.id + ' fetchDeposits requires a currency code argument')
  1346. await self.load_markets()
  1347. currency = self.currency(code)
  1348. request = {
  1349. 'asset': currency['id'],
  1350. }
  1351. response = await self.privatePostDepositStatus(self.extend(request, params))
  1352. #
  1353. # { error: [],
  1354. # result: [{method: "Ether(Hex)",
  1355. # aclass: "currency",
  1356. # asset: "XETH",
  1357. # refid: "Q2CANKL-LBFVEE-U4Y2WQ",
  1358. # txid: "0x57fd704dab1a73c20e24c8696099b695d596924b401b261513cfdab23…",
  1359. # info: "0x615f9ba7a9575b0ab4d571b2b36b1b324bd83290",
  1360. # amount: "7.9999257900",
  1361. # fee: "0.0000000000",
  1362. # time: 1529223212,
  1363. # status: "Success" }]}
  1364. #
  1365. return self.parse_transactions_by_type('deposit', response['result'], code, since, limit)
  1366. async def fetch_time(self, params={}):
  1367. # https://www.kraken.com/en-us/features/api#get-server-time
  1368. response = await self.publicGetTime(params)
  1369. #
  1370. # {
  1371. # "error": [],
  1372. # "result": {
  1373. # "unixtime": 1591502873,
  1374. # "rfc1123": "Sun, 7 Jun 20 04:07:53 +0000"
  1375. # }
  1376. # }
  1377. #
  1378. result = self.safe_value(response, 'result', {})
  1379. return self.safe_timestamp(result, 'unixtime')
  1380. async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
  1381. # https://www.kraken.com/en-us/help/api#withdraw-status
  1382. if code is None:
  1383. raise ArgumentsRequired(self.id + ' fetchWithdrawals requires a currency code argument')
  1384. await self.load_markets()
  1385. currency = self.currency(code)
  1386. request = {
  1387. 'asset': currency['id'],
  1388. }
  1389. response = await self.privatePostWithdrawStatus(self.extend(request, params))
  1390. #
  1391. # { error: [],
  1392. # result: [{method: "Ether",
  1393. # aclass: "currency",
  1394. # asset: "XETH",
  1395. # refid: "A2BF34S-O7LBNQ-UE4Y4O",
  1396. # txid: "0x298c83c7b0904d8400ef43e1c9e2287b518f7ea3d838822d53f704a1565c274d",
  1397. # info: "0x7cb275a5e07ba943fee972e165d80daa67cb2dd0",
  1398. # amount: "9.9950000000",
  1399. # fee: "0.0050000000",
  1400. # time: 1530481750,
  1401. # status: "Success" }]}
  1402. #
  1403. return self.parse_transactions_by_type('withdrawal', response['result'], code, since, limit)
  1404. async def create_deposit_address(self, code, params={}):
  1405. request = {
  1406. 'new': 'true',
  1407. }
  1408. response = await self.fetch_deposit_address(code, self.extend(request, params))
  1409. address = self.safe_string(response, 'address')
  1410. self.check_address(address)
  1411. return {
  1412. 'currency': code,
  1413. 'address': address,
  1414. 'info': response,
  1415. }
  1416. async def fetch_deposit_address(self, code, params={}):
  1417. await self.load_markets()
  1418. currency = self.currency(code)
  1419. # eslint-disable-next-line quotes
  1420. method = self.safe_string(params, 'method')
  1421. if method is None:
  1422. if self.options['cacheDepositMethodsOnFetchDepositAddress']:
  1423. # cache depositMethods
  1424. if not (code in self.options['depositMethods']):
  1425. self.options['depositMethods'][code] = await self.fetch_deposit_methods(code)
  1426. method = self.options['depositMethods'][code][0]['method']
  1427. else:
  1428. 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')
  1429. request = {
  1430. 'asset': currency['id'],
  1431. 'method': method,
  1432. }
  1433. response = await self.privatePostDepositAddresses(self.extend(request, params)) # overwrite methods
  1434. result = response['result']
  1435. numResults = len(result)
  1436. if numResults < 1:
  1437. raise InvalidAddress(self.id + ' privatePostDepositAddresses() returned no addresses')
  1438. address = self.safe_string(result[0], 'address')
  1439. tag = self.safe_string_2(result[0], 'tag', 'memo')
  1440. self.check_address(address)
  1441. return {
  1442. 'currency': code,
  1443. 'address': address,
  1444. 'tag': tag,
  1445. 'info': response,
  1446. }
  1447. async def withdraw(self, code, amount, address, tag=None, params={}):
  1448. self.check_address(address)
  1449. if 'key' in params:
  1450. await self.load_markets()
  1451. currency = self.currency(code)
  1452. request = {
  1453. 'asset': currency['id'],
  1454. 'amount': amount,
  1455. # 'address': address, # they don't allow withdrawals to direct addresses
  1456. }
  1457. response = await self.privatePostWithdraw(self.extend(request, params))
  1458. return {
  1459. 'info': response,
  1460. 'id': response['result'],
  1461. }
  1462. raise ExchangeError(self.id + " withdraw requires a 'key' parameter(withdrawal key name, as set up on your account)")
  1463. def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
  1464. url = '/' + self.version + '/' + api + '/' + path
  1465. if api == 'public':
  1466. if params:
  1467. url += '?' + self.urlencode(params)
  1468. elif api == 'private':
  1469. self.check_required_credentials()
  1470. nonce = str(self.nonce())
  1471. body = self.urlencode(self.extend({'nonce': nonce}, params))
  1472. auth = self.encode(nonce + body)
  1473. hash = self.hash(auth, 'sha256', 'binary')
  1474. binary = self.encode(url)
  1475. binhash = self.binary_concat(binary, hash)
  1476. secret = self.base64_to_binary(self.secret)
  1477. signature = self.hmac(binhash, secret, hashlib.sha512, 'base64')
  1478. headers = {
  1479. 'API-Key': self.apiKey,
  1480. 'API-Sign': signature,
  1481. 'Content-Type': 'application/x-www-form-urlencoded',
  1482. }
  1483. else:
  1484. url = '/' + path
  1485. url = self.urls['api'][api] + url
  1486. return {'url': url, 'method': method, 'body': body, 'headers': headers}
  1487. def nonce(self):
  1488. return self.milliseconds()
  1489. def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
  1490. if code == 520:
  1491. raise ExchangeNotAvailable(self.id + ' ' + str(code) + ' ' + reason)
  1492. # todo: rewrite self for "broad" exceptions matching
  1493. if body.find('Invalid order') >= 0:
  1494. raise InvalidOrder(self.id + ' ' + body)
  1495. if body.find('Invalid nonce') >= 0:
  1496. raise InvalidNonce(self.id + ' ' + body)
  1497. if body.find('Insufficient funds') >= 0:
  1498. raise InsufficientFunds(self.id + ' ' + body)
  1499. if body.find('Cancel pending') >= 0:
  1500. raise CancelPending(self.id + ' ' + body)
  1501. if body.find('Invalid arguments:volume') >= 0:
  1502. raise InvalidOrder(self.id + ' ' + body)
  1503. if body[0] == '{':
  1504. if not isinstance(response, basestring):
  1505. if 'error' in response:
  1506. numErrors = len(response['error'])
  1507. if numErrors:
  1508. message = self.id + ' ' + body
  1509. for i in range(0, len(response['error'])):
  1510. error = response['error'][i]
  1511. self.throw_exactly_matched_exception(self.exceptions, error, message)
  1512. raise ExchangeError(message)