PageRenderTime 1683ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 1ms

/python/ccxt/qtrade.py

https://github.com/kroitor/ccxt
Python | 1504 lines | 759 code | 37 blank | 708 comment | 88 complexity | 64aaed11c81197a9600e3da5cc4d02f0 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.base.exchange import Exchange
  5. # -----------------------------------------------------------------------------
  6. try:
  7. basestring # Python 3
  8. except NameError:
  9. basestring = str # Python 2
  10. from ccxt.base.errors import ExchangeError
  11. from ccxt.base.errors import AuthenticationError
  12. from ccxt.base.errors import InsufficientFunds
  13. from ccxt.base.errors import InvalidOrder
  14. class qtrade(Exchange):
  15. def describe(self):
  16. return self.deep_extend(super(qtrade, self).describe(), {
  17. 'id': 'qtrade',
  18. 'name': 'qTrade',
  19. 'countries': ['US'],
  20. 'rateLimit': 1000,
  21. 'version': 'v1',
  22. 'urls': {
  23. 'logo': 'https://user-images.githubusercontent.com/51840849/80491487-74a99c00-896b-11ea-821e-d307e832f13e.jpg',
  24. 'api': 'https://api.qtrade.io',
  25. 'www': 'https://qtrade.io',
  26. 'doc': 'https://qtrade-exchange.github.io/qtrade-docs',
  27. 'referral': 'https://qtrade.io/?ref=BKOQWVFGRH2C',
  28. },
  29. 'has': {
  30. 'CORS': False,
  31. 'fetchTrades': True,
  32. 'fetchTicker': True,
  33. 'fetchTickers': True,
  34. 'fetchMarkets': True,
  35. 'fetchCurrencies': True,
  36. 'fetchBalance': True,
  37. 'fetchOrderBook': True,
  38. 'fetchOrder': True,
  39. 'fetchOrders': True,
  40. 'fetchMyTrades': True,
  41. 'fetchClosedOrders': True,
  42. 'fetchOpenOrders': True,
  43. 'fetchOHLCV': True,
  44. 'createOrder': True,
  45. 'cancelOrder': True,
  46. 'createMarketOrder': False,
  47. 'withdraw': True,
  48. 'fetchDepositAddress': True,
  49. 'fetchTransactions': False,
  50. 'fetchDeposits': True,
  51. 'fetchWithdrawals': True,
  52. 'fetchDeposit': True,
  53. 'fetchWithdrawal': True,
  54. },
  55. 'timeframes': {
  56. '5m': 'fivemin',
  57. '15m': 'fifteenmin',
  58. '30m': 'thirtymin',
  59. '1h': 'onehour',
  60. '2h': 'twohour',
  61. '4h': 'fourhour',
  62. '1d': 'oneday',
  63. },
  64. 'api': {
  65. 'public': {
  66. 'get': [
  67. 'ticker/{market_string}',
  68. 'tickers',
  69. 'currency/{code}',
  70. 'currencies',
  71. 'common',
  72. 'market/{market_string}',
  73. 'markets',
  74. 'market/{market_string}/trades',
  75. 'orderbook/{market_string}',
  76. 'market/{market_string}/ohlcv/{interval}',
  77. ],
  78. },
  79. 'private': {
  80. 'get': [
  81. 'me',
  82. 'balances',
  83. 'balances_all', # undocumented
  84. 'market/{market_string}',
  85. 'orders',
  86. 'order/{order_id}',
  87. 'trades',
  88. 'withdraw/{withdraw_id}',
  89. 'withdraws',
  90. 'deposit/{deposit_id}',
  91. 'deposits',
  92. 'transfers',
  93. ],
  94. 'post': [
  95. 'cancel_order',
  96. 'withdraw',
  97. 'deposit_address/{currency}',
  98. 'sell_limit',
  99. 'buy_limit',
  100. ],
  101. },
  102. },
  103. 'fees': {
  104. 'trading': {
  105. 'tierBased': True,
  106. 'percentage': True,
  107. 'taker': 0.0025,
  108. 'maker': 0.0,
  109. },
  110. 'funding': {
  111. 'withdraw': {},
  112. },
  113. },
  114. 'exceptions': {
  115. 'exact': {
  116. 'invalid_auth': AuthenticationError,
  117. 'insuff_funds': InsufficientFunds,
  118. },
  119. },
  120. })
  121. def fetch_markets(self, params={}):
  122. response = self.publicGetMarkets(params)
  123. #
  124. # {
  125. # "data":{
  126. # "markets":[
  127. # {
  128. # "id":5,
  129. # "market_currency":"BAC",
  130. # "base_currency":"BTC",
  131. # "maker_fee":"0.0025",
  132. # "taker_fee":"0.0025",
  133. # "metadata":{
  134. # "delisting_date":"7/15/2018",
  135. # "market_notices":[
  136. # {
  137. # "message":"Delisting Notice: This market has been delisted due to low volume. Please cancel your orders and withdraw your funds by 7/15/2018.",
  138. # "type":"warning"
  139. # }
  140. # ]
  141. # },
  142. # "can_trade":false,
  143. # "can_cancel":true,
  144. # "can_view":false,
  145. # "market_string":"BAC_BTC",
  146. # "minimum_sell_amount":"0.0001",
  147. # "minimum_buy_value":"0.0001",
  148. # "market_precision":8,
  149. # "base_precision":8
  150. # },
  151. # ],
  152. # }
  153. # }
  154. #
  155. data = self.safe_value(response, 'data', {})
  156. markets = self.safe_value(data, 'markets', [])
  157. result = []
  158. for i in range(0, len(markets)):
  159. market = markets[i]
  160. marketId = self.safe_string(market, 'market_string')
  161. numericId = self.safe_integer(market, 'id')
  162. baseId = self.safe_string(market, 'market_currency')
  163. quoteId = self.safe_string(market, 'base_currency')
  164. base = self.safe_currency_code(baseId)
  165. quote = self.safe_currency_code(quoteId)
  166. symbol = base + '/' + quote
  167. precision = {
  168. 'amount': self.safe_integer(market, 'market_precision'),
  169. 'price': self.safe_integer(market, 'base_precision'),
  170. }
  171. canView = self.safe_value(market, 'can_view', False)
  172. canTrade = self.safe_value(market, 'can_trade', False)
  173. active = canTrade and canView
  174. result.append({
  175. 'symbol': symbol,
  176. 'id': marketId,
  177. 'numericId': numericId,
  178. 'baseId': baseId,
  179. 'quoteId': quoteId,
  180. 'base': base,
  181. 'quote': quote,
  182. 'active': active,
  183. 'precision': precision,
  184. 'taker': self.safe_float(market, 'taker_fee'),
  185. 'maker': self.safe_float(market, 'maker_fee'),
  186. 'limits': {
  187. 'amount': {
  188. 'min': self.safe_float(market, 'minimum_buy_value'),
  189. 'max': None,
  190. },
  191. 'price': {
  192. 'min': None,
  193. 'max': None,
  194. },
  195. 'cost': {
  196. 'min': None,
  197. 'max': None,
  198. },
  199. },
  200. 'info': market,
  201. })
  202. return result
  203. def fetch_currencies(self, params={}):
  204. response = self.publicGetCurrencies(params)
  205. #
  206. # {
  207. # "data":{
  208. # "currencies":[
  209. # {
  210. # "code":"DGB",
  211. # "long_name":"Digibyte",
  212. # "type":"bitcoin_like",
  213. # "precision":8,
  214. # "config":{
  215. # "price":0.0035,
  216. # "withdraw_fee":"10",
  217. # "deposit_types":[
  218. # {
  219. # "label":"Address",
  220. # "lookup_mode":"address",
  221. # "render_type":"address",
  222. # "deposit_type":"address",
  223. # "lookup_config":{}
  224. # }
  225. # ],
  226. # "default_signer":103,
  227. # "address_version":30,
  228. # "satoshi_per_byte":300,
  229. # "required_confirmations":200,
  230. # "required_generate_confirmations":300
  231. # },
  232. # "metadata":{},
  233. # "minimum_order":"0.0001",
  234. # "status":"ok",
  235. # "can_withdraw":true,
  236. # "delisted":false,
  237. # "deposit_disabled":false,
  238. # "withdraw_disabled":false,
  239. # "deposit_warn_codes":[],
  240. # "withdraw_warn_codes":[]
  241. # },
  242. # ],
  243. # }
  244. # }
  245. #
  246. data = self.safe_value(response, 'data', {})
  247. currencies = self.safe_value(data, 'currencies', [])
  248. result = {}
  249. for i in range(0, len(currencies)):
  250. currency = currencies[i]
  251. id = self.safe_string(currency, 'code')
  252. code = self.safe_currency_code(id)
  253. name = self.safe_string(currency, 'long_name')
  254. type = self.safe_string(currency, 'type')
  255. canWithdraw = self.safe_value(currency, 'can_withdraw', True)
  256. depositDisabled = self.safe_value(currency, 'deposit_disabled', False)
  257. config = self.safe_value(currency, 'config', {})
  258. status = self.safe_string(currency, 'status')
  259. active = canWithdraw and (status == 'ok') and not depositDisabled
  260. result[code] = {
  261. 'id': id,
  262. 'code': code,
  263. 'info': currency,
  264. 'type': type,
  265. 'name': name,
  266. 'fee': self.safe_float(config, 'withdraw_fee'),
  267. 'precision': self.safe_integer(currency, 'precision'),
  268. 'active': active,
  269. 'limits': {
  270. 'amount': {
  271. 'min': self.safe_float(currency, 'minimum_order'),
  272. 'max': None,
  273. },
  274. 'price': {
  275. 'min': None,
  276. 'max': None,
  277. },
  278. 'cost': {
  279. 'min': None,
  280. 'max': None,
  281. },
  282. 'withdraw': {
  283. 'min': None,
  284. 'max': None,
  285. },
  286. },
  287. }
  288. return result
  289. def parse_ohlcv(self, ohlcv, market=None):
  290. #
  291. # {
  292. # "time":"2019-12-07T22:55:00Z",
  293. # "open":"0.00197",
  294. # "high":"0.00197",
  295. # "low":"0.00197",
  296. # "close":"0.00197",
  297. # "volume":"0.00016676",
  298. # "market_volume":"0.08465047"
  299. # }
  300. #
  301. return [
  302. self.parse8601(self.safe_string(ohlcv, 'time')),
  303. self.safe_float(ohlcv, 'open'),
  304. self.safe_float(ohlcv, 'high'),
  305. self.safe_float(ohlcv, 'low'),
  306. self.safe_float(ohlcv, 'close'),
  307. self.safe_float(ohlcv, 'market_volume'),
  308. ]
  309. def fetch_ohlcv(self, symbol, timeframe='5m', since=None, limit=None, params={}):
  310. self.load_markets()
  311. market = self.market(symbol)
  312. request = {
  313. 'market_string': market['id'],
  314. 'interval': self.timeframes[timeframe],
  315. }
  316. response = self.publicGetMarketMarketStringOhlcvInterval(self.extend(request, params))
  317. #
  318. # {
  319. # "data":{
  320. # "slices":[
  321. # {"time":"2019-12-07T22:55:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0.00016676","market_volume":"0.08465047"},
  322. # {"time":"2019-12-07T23:00:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"},
  323. # {"time":"2019-12-07T23:05:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"},
  324. # ]
  325. # }
  326. # }
  327. #
  328. data = self.safe_value(response, 'data', {})
  329. ohlcvs = self.safe_value(data, 'slices', [])
  330. return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
  331. def fetch_order_book(self, symbol, limit=None, params={}):
  332. self.load_markets()
  333. marketId = self.market_id(symbol)
  334. request = {'market_string': marketId}
  335. response = self.publicGetOrderbookMarketString(self.extend(request, params))
  336. #
  337. # {
  338. # "data":{
  339. # "buy":{
  340. # "0.00700015":"4.76196367",
  341. # "0.00700017":"1.89755391",
  342. # "0.00700018":"2.13214088",
  343. # },
  344. # "last_change":1588539869958811,
  345. # "sell":{
  346. # "0.02418662":"0.19513696",
  347. # "0.02465627":"0.2439212",
  348. # "0.02530277":"0.663475931274359255",
  349. # }
  350. # }
  351. # }
  352. #
  353. data = self.safe_value(response, 'data', {})
  354. orderbook = {}
  355. sides = {'buy': 'bids', 'sell': 'asks'}
  356. keys = list(sides.keys())
  357. for i in range(0, len(keys)):
  358. key = keys[i]
  359. side = sides[key]
  360. bidasks = self.safe_value(data, key, {})
  361. prices = list(bidasks.keys())
  362. result = []
  363. for j in range(0, len(prices)):
  364. priceAsString = prices[j]
  365. price = self.safe_float(prices, j)
  366. amount = self.safe_float(bidasks, priceAsString)
  367. result.append([price, amount])
  368. orderbook[side] = result
  369. timestamp = self.safe_integer_product(data, 'last_change', 0.001)
  370. return self.parse_order_book(orderbook, timestamp)
  371. def parse_ticker(self, ticker, market=None):
  372. #
  373. # fetchTicker, fetchTickers
  374. #
  375. # {
  376. # "ask":"0.02423119",
  377. # "bid":"0.0230939",
  378. # "day_avg_price":"0.0247031874349301",
  379. # "day_change":"-0.0237543162270376",
  380. # "day_high":"0.02470552",
  381. # "day_low":"0.02470172",
  382. # "day_open":"0.02530277",
  383. # "day_volume_base":"0.00268074",
  384. # "day_volume_market":"0.10851798",
  385. # "id":41,
  386. # "id_hr":"ETH_BTC",
  387. # "last":"0.02470172",
  388. # "last_change":1588533365354609
  389. # }
  390. #
  391. symbol = None
  392. marketId = self.safe_string(ticker, 'id_hr')
  393. if marketId is not None:
  394. if marketId in self.markets_by_id:
  395. market = self.markets_by_id[marketId]
  396. else:
  397. baseId, quoteId = marketId.split('_')
  398. base = self.safe_currency_code(baseId)
  399. quote = self.safe_currency_code(quoteId)
  400. symbol = quote + '/' + base
  401. if (symbol is None) and (market is not None):
  402. symbol = market['symbol']
  403. timestamp = self.safe_integer_product(ticker, 'last_change', 0.001)
  404. previous = self.safe_float(ticker, 'day_open')
  405. last = self.safe_float(ticker, 'last')
  406. day_change = self.safe_float(ticker, 'day_change')
  407. percentage = None
  408. change = None
  409. average = self.safe_float(ticker, 'day_avg_price')
  410. if day_change is not None:
  411. percentage = day_change * 100
  412. if previous is not None:
  413. change = day_change * previous
  414. if (average is None) and (last is not None) and (previous is not None):
  415. average = self.sum(last, previous) / 2
  416. baseVolume = self.safe_float(ticker, 'day_volume_market')
  417. quoteVolume = self.safe_float(ticker, 'day_volume_base')
  418. vwap = self.vwap(baseVolume, quoteVolume)
  419. return {
  420. 'symbol': symbol,
  421. 'timestamp': timestamp,
  422. 'datetime': self.iso8601(timestamp),
  423. 'high': self.safe_float(ticker, 'day_high'),
  424. 'low': self.safe_float(ticker, 'day_low'),
  425. 'bid': self.safe_float(ticker, 'bid'),
  426. 'bidVolume': None,
  427. 'ask': self.safe_float(ticker, 'ask'),
  428. 'askVolume': None,
  429. 'vwap': vwap,
  430. 'open': previous,
  431. 'close': last,
  432. 'last': last,
  433. 'previousClose': None,
  434. 'change': change,
  435. 'percentage': percentage,
  436. 'average': average,
  437. 'baseVolume': baseVolume,
  438. 'quoteVolume': quoteVolume,
  439. 'info': ticker,
  440. }
  441. def fetch_tickers(self, symbols=None, params={}):
  442. self.load_markets()
  443. response = self.publicGetTickers(params)
  444. #
  445. # {
  446. # "data":{
  447. # "markets":[
  448. # {
  449. # "ask":"0.0000003",
  450. # "bid":"0.00000029",
  451. # "day_avg_price":"0.0000002999979728",
  452. # "day_change":"0.0344827586206897",
  453. # "day_high":"0.0000003",
  454. # "day_low":"0.0000003",
  455. # "day_open":"0.00000029",
  456. # "day_volume_base":"0.00591958",
  457. # "day_volume_market":"19732.06666665",
  458. # "id":36,
  459. # "id_hr":"DOGE_BTC",
  460. # "last":"0.0000003",
  461. # "last_change":1588534202130778
  462. # },
  463. # ]
  464. # }
  465. # }
  466. #
  467. data = self.safe_value(response, 'data', {})
  468. tickers = self.safe_value(data, 'markets', [])
  469. result = {}
  470. for i in range(0, len(tickers)):
  471. ticker = self.parse_ticker(tickers[i])
  472. symbol = ticker['symbol']
  473. result[symbol] = ticker
  474. return self.filter_by_array(result, 'symbol', symbols)
  475. def fetch_ticker(self, symbol, params={}):
  476. self.load_markets()
  477. market = self.market(symbol)
  478. request = {
  479. 'market_string': market['id'],
  480. }
  481. response = self.publicGetTickerMarketString(self.extend(request, params))
  482. #
  483. # {
  484. # "data":{
  485. # "ask":"0.02423119",
  486. # "bid":"0.0230939",
  487. # "day_avg_price":"0.0247031874349301",
  488. # "day_change":"-0.0237543162270376",
  489. # "day_high":"0.02470552",
  490. # "day_low":"0.02470172",
  491. # "day_open":"0.02530277",
  492. # "day_volume_base":"0.00268074",
  493. # "day_volume_market":"0.10851798",
  494. # "id":41,
  495. # "id_hr":"ETH_BTC",
  496. # "last":"0.02470172",
  497. # "last_change":1588533365354609
  498. # }
  499. # }
  500. #
  501. data = self.safe_value(response, 'data', {})
  502. return self.parse_ticker(data, market)
  503. def fetch_trades(self, symbol, since=None, limit=None, params={}):
  504. self.load_markets()
  505. market = self.market(symbol)
  506. request = {
  507. 'market_string': market['id'],
  508. # 'older_than': 123, # returns trades with id < older_than
  509. # 'newer_than': 123, # returns trades with id > newer_than
  510. }
  511. response = self.publicGetMarketMarketStringTrades(self.extend(request, params))
  512. #
  513. # {
  514. # "data":{
  515. # "trades":[
  516. # {
  517. # "id":85507,
  518. # "amount":"0.09390502",
  519. # "price":"0.02556325",
  520. # "base_volume":"0.00240051",
  521. # "seller_taker":true,
  522. # "side":"sell",
  523. # "created_at":"0001-01-01T00:00:00Z",
  524. # "created_at_ts":1581560391338718
  525. # },
  526. # ]
  527. # }
  528. # }
  529. #
  530. data = self.safe_value(response, 'data', {})
  531. trades = self.safe_value(data, 'trades', [])
  532. return self.parse_trades(trades, market, since, limit)
  533. def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
  534. self.load_markets()
  535. request = {
  536. 'desc': True, # Returns newest trades first when True
  537. # 'older_than': 123, # returns trades with id < older_than
  538. # 'newer_than': 123, # returns trades with id > newer_than
  539. }
  540. market = None
  541. numericId = self.safe_value(params, 'market_id')
  542. if numericId is not None:
  543. request['market_id'] = numericId # mutually exclusive with market_string
  544. elif symbol is not None:
  545. market = self.market(symbol)
  546. request['market_string'] = market['id']
  547. response = self.privateGetTrades(self.extend(request, params))
  548. #
  549. # {
  550. # "data":{
  551. # "trades":[
  552. # {
  553. # "id":107331,
  554. # "market_amount":"0.1082536946986",
  555. # "price":"0.0230939",
  556. # "base_amount":"0.00249999",
  557. # "order_id":13790596,
  558. # "market_id":41,
  559. # "market_string":"ETH_BTC",
  560. # "taker":true,
  561. # "base_fee":"0.00001249",
  562. # "side":"sell",
  563. # "created_at":"2020-05-04T06:08:18.513413Z"
  564. # }
  565. # ]
  566. # }
  567. # }
  568. #
  569. data = self.safe_value(response, 'data', {})
  570. trades = self.safe_value(data, 'trades', [])
  571. return self.parse_trades(trades, market, since, limit)
  572. def parse_trade(self, trade, market=None):
  573. #
  574. # fetchTrades(public)
  575. #
  576. # {
  577. # "id":85507,
  578. # "amount":"0.09390502",
  579. # "price":"0.02556325",
  580. # "base_volume":"0.00240051",
  581. # "seller_taker":true,
  582. # "side":"sell",
  583. # "created_at":"0001-01-01T00:00:00Z",
  584. # "created_at_ts":1581560391338718
  585. # }
  586. #
  587. # fetchMyTrades(private)
  588. #
  589. # {
  590. # "id":107331,
  591. # "market_amount":"0.1082536946986",
  592. # "price":"0.0230939",
  593. # "base_amount":"0.00249999",
  594. # "order_id":13790596,
  595. # "market_id":41,
  596. # "market_string":"ETH_BTC",
  597. # "taker":true,
  598. # "base_fee":"0.00001249",
  599. # "side":"sell",
  600. # "created_at":"2020-05-04T06:08:18.513413Z"
  601. # }
  602. #
  603. # createOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders
  604. #
  605. # {
  606. # "base_amount": "9.58970687",
  607. # "base_fee": "0.02397426",
  608. # "created_at": "0001-01-01T00:00:00Z",
  609. # "id": 0,
  610. # "market_amount": "0.97179355",
  611. # "price": "9.86804952",
  612. # "taker": True
  613. # }
  614. #
  615. id = self.safe_string(trade, 'id')
  616. timestamp = self.safe_integer_product(trade, 'created_at_ts', 0.001)
  617. if timestamp is None:
  618. timestamp = self.parse8601(self.safe_string(trade, 'created_at'))
  619. side = self.safe_string(trade, 'side')
  620. symbol = None
  621. marketId = self.safe_string(trade, 'market_string')
  622. if marketId is not None:
  623. if marketId in self.markets_by_id:
  624. market = self.markets_by_id[marketId]
  625. else:
  626. baseId, quoteId = marketId.split('_')
  627. base = self.safe_currency_code(baseId)
  628. quote = self.safe_currency_code(quoteId)
  629. symbol = quote + '/' + base
  630. if (symbol is None) and (market is not None):
  631. symbol = market['symbol']
  632. cost = self.safe_float_2(trade, 'base_volume', 'base_amount')
  633. price = self.safe_float(trade, 'price')
  634. amount = self.safe_float_2(trade, 'market_amount', 'amount')
  635. if (cost is None) and (amount is not None) and (price is not None):
  636. if price is not None:
  637. cost = price * amount
  638. fee = None
  639. feeCost = self.safe_float(trade, 'base_fee')
  640. if feeCost is not None:
  641. feeCurrencyCode = None if (market is None) else market['quote']
  642. fee = {
  643. 'currency': feeCurrencyCode,
  644. 'cost': feeCost,
  645. }
  646. taker = self.safe_value(trade, 'taker', True)
  647. takerOrMaker = 'taker' if taker else 'maker'
  648. orderId = self.safe_string(trade, 'order_id')
  649. result = {
  650. 'id': id,
  651. 'info': trade,
  652. 'timestamp': timestamp,
  653. 'datetime': self.iso8601(timestamp),
  654. 'symbol': symbol,
  655. 'order': orderId,
  656. 'type': None,
  657. 'side': side,
  658. 'takerOrMaker': takerOrMaker,
  659. 'price': price,
  660. 'amount': amount,
  661. 'cost': cost,
  662. 'fee': fee,
  663. }
  664. return result
  665. def fetch_balance(self, params={}):
  666. self.load_markets()
  667. response = self.privateGetBalancesAll(params)
  668. #
  669. # {
  670. # "data":{
  671. # "balances": [
  672. # {"balance": "100000000", "currency": "BCH"},
  673. # {"balance": "99992435.78253015", "currency": "LTC"},
  674. # {"balance": "99927153.76074182", "currency": "BTC"},
  675. # ],
  676. # "order_balances":[],
  677. # "limit_used":0,
  678. # "limit_remaining":4000,
  679. # "limit":4000
  680. # }
  681. # }
  682. #
  683. data = self.safe_value(response, 'data', {})
  684. balances = self.safe_value(data, 'balances', [])
  685. result = {
  686. 'info': response,
  687. }
  688. for i in range(0, len(balances)):
  689. balance = balances[i]
  690. currencyId = self.safe_string(balance, 'currency')
  691. code = self.safe_currency_code(currencyId)
  692. account = result[code] if (code in result) else self.account()
  693. account['free'] = self.safe_float(balance, 'balance')
  694. account['used'] = 0
  695. result[code] = account
  696. balances = self.safe_value(data, 'order_balances', [])
  697. for i in range(0, len(balances)):
  698. balance = balances[i]
  699. currencyId = self.safe_string(balance, 'currency')
  700. code = self.safe_currency_code(currencyId)
  701. account = result[code] if (code in result) else self.account()
  702. account['used'] = self.safe_float(balance, 'balance')
  703. result[code] = account
  704. return self.parse_balance(result)
  705. def create_order(self, symbol, type, side, amount, price=None, params={}):
  706. if type != 'limit':
  707. raise InvalidOrder(self.id + ' createOrder() allows limit orders only')
  708. self.load_markets()
  709. market = self.market(symbol)
  710. request = {
  711. 'amount': self.amount_to_precision(symbol, amount),
  712. 'market_id': market['numericId'],
  713. 'price': self.price_to_precision(symbol, price),
  714. }
  715. method = 'privatePostSellLimit' if (side == 'sell') else 'privatePostBuyLimit'
  716. response = getattr(self, method)(self.extend(request, params))
  717. #
  718. # {
  719. # "data": {
  720. # "order": {
  721. # "created_at": "2018-04-06T20:46:52.899248Z",
  722. # "id": 13253,
  723. # "market_amount": "1",
  724. # "market_amount_remaining": "0",
  725. # "market_id": 1,
  726. # "open": False,
  727. # "order_type": "sell_limit",
  728. # "price": "0.01",
  729. # "trades": [
  730. # {
  731. # "base_amount": "0.27834267",
  732. # "base_fee": "0.00069585",
  733. # "created_at": "0001-01-01T00:00:00Z",
  734. # "id": 0,
  735. # "market_amount": "0.02820645",
  736. # "price": "9.86805058",
  737. # "taker": True
  738. # },
  739. # {
  740. # "base_amount": "9.58970687",
  741. # "base_fee": "0.02397426",
  742. # "created_at": "0001-01-01T00:00:00Z",
  743. # "id": 0,
  744. # "market_amount": "0.97179355",
  745. # "price": "9.86804952",
  746. # "taker": True
  747. # }
  748. # ]
  749. # }
  750. # }
  751. # }
  752. #
  753. data = self.safe_value(response, 'data', {})
  754. order = self.safe_value(data, 'order', {})
  755. return self.parse_order(order, market)
  756. def parse_order(self, order, market=None):
  757. #
  758. # createOrder
  759. #
  760. # {
  761. # "created_at": "2018-04-06T20:46:52.899248Z",
  762. # "id": 13253,
  763. # "market_amount": "1",
  764. # "market_amount_remaining": "0",
  765. # "market_id": 1,
  766. # "open": False,
  767. # "order_type": "sell_limit",
  768. # "price": "0.01",
  769. # "trades": [
  770. # {
  771. # "base_amount": "0.27834267",
  772. # "base_fee": "0.00069585",
  773. # "created_at": "0001-01-01T00:00:00Z",
  774. # "id": 0,
  775. # "market_amount": "0.02820645",
  776. # "price": "9.86805058",
  777. # "taker": True
  778. # },
  779. # {
  780. # "base_amount": "9.58970687",
  781. # "base_fee": "0.02397426",
  782. # "created_at": "0001-01-01T00:00:00Z",
  783. # "id": 0,
  784. # "market_amount": "0.97179355",
  785. # "price": "9.86804952",
  786. # "taker": True
  787. # }
  788. # ]
  789. # }
  790. #
  791. # fetchOrder
  792. #
  793. # {
  794. # id: 13790596,
  795. # market_amount: "0.15",
  796. # market_amount_remaining: "0",
  797. # created_at: "2020-05-04T06:08:18.513413Z",
  798. # price: "0.0230939",
  799. # base_amount: "0",
  800. # order_type: "sell_limit",
  801. # market_id: 41,
  802. # market_string: "ETH_BTC",
  803. # open: False,
  804. # trades: [
  805. # {
  806. # id: 107331,
  807. # market_amount: "0.1082536946986",
  808. # price: "0.0230939",
  809. # base_amount: "0.00249999",
  810. # taker: True,
  811. # base_fee: "0.00001249",
  812. # created_at: "2020-05-04T06:08:18.513413Z",
  813. # }
  814. # ],
  815. # close_reason: "canceled"
  816. # }
  817. #
  818. id = self.safe_string(order, 'id')
  819. timestamp = self.parse8601(self.safe_string(order, 'created_at'))
  820. sideType = self.safe_string(order, 'order_type')
  821. orderType = None
  822. side = None
  823. if sideType is not None:
  824. parts = sideType.split('_')
  825. side = self.safe_string(parts, 0)
  826. orderType = self.safe_string(parts, 1)
  827. price = self.safe_float(order, 'price')
  828. amount = self.safe_float(order, 'market_amount')
  829. remaining = self.safe_float(order, 'market_amount_remaining')
  830. filled = None
  831. open = self.safe_value(order, 'open', False)
  832. closeReason = self.safe_string(order, 'close_reason')
  833. status = None
  834. if open:
  835. status = 'open'
  836. elif closeReason == 'canceled':
  837. status = 'canceled'
  838. else:
  839. status = 'closed'
  840. symbol = None
  841. marketId = self.safe_string(order, 'market_string')
  842. if marketId is not None:
  843. if marketId in self.markets_by_id:
  844. market = self.markets_by_id[marketId]
  845. else:
  846. baseId, quoteId = marketId.split('_')
  847. base = self.safe_currency_code(baseId)
  848. quote = self.safe_currency_code(quoteId)
  849. symbol = base + '/' + quote
  850. if (symbol is None) and (market is not None):
  851. symbol = market['symbol']
  852. rawTrades = self.safe_value(order, 'trades', [])
  853. parsedTrades = self.parse_trades(rawTrades, market, None, None, {
  854. 'order': id,
  855. 'side': side,
  856. 'type': orderType,
  857. })
  858. numTrades = len(parsedTrades)
  859. lastTradeTimestamp = None
  860. feeCost = None
  861. cost = None
  862. if numTrades > 0:
  863. feeCost = 0
  864. cost = 0
  865. filled = 0
  866. remaining = amount
  867. for i in range(0, len(parsedTrades)):
  868. trade = parsedTrades[i]
  869. feeCost = self.sum(trade['fee']['cost'], feeCost)
  870. lastTradeTimestamp = self.safe_integer(trade, 'timestamp')
  871. cost = self.sum(trade['cost'], cost)
  872. filled = self.sum(trade['amount'], filled)
  873. remaining = max(0, remaining - trade['amount'])
  874. fee = None
  875. if feeCost is not None:
  876. feeCurrencyCode = None if (market is None) else market['quote']
  877. fee = {
  878. 'currency': feeCurrencyCode,
  879. 'cost': feeCost,
  880. }
  881. if (amount is not None) and (remaining is not None):
  882. filled = max(0, amount - remaining)
  883. average = None
  884. if filled is not None:
  885. if (price is not None) and (cost is None):
  886. cost = filled * price
  887. if (cost is not None) and (filled > 0):
  888. average = cost / filled
  889. return {
  890. 'info': order,
  891. 'id': id,
  892. 'clientOrderId': None,
  893. 'timestamp': timestamp,
  894. 'datetime': self.iso8601(timestamp),
  895. 'lastTradeTimestamp': lastTradeTimestamp,
  896. 'symbol': symbol,
  897. 'type': orderType,
  898. 'side': side,
  899. 'price': price,
  900. 'average': average,
  901. 'amount': amount,
  902. 'remaining': remaining,
  903. 'filled': filled,
  904. 'status': status,
  905. 'fee': fee,
  906. 'cost': cost,
  907. 'trades': parsedTrades,
  908. }
  909. def cancel_order(self, id, symbol=None, params={}):
  910. request = {
  911. 'id': int(id),
  912. }
  913. # successful cancellation returns 200 with no payload
  914. return self.privatePostCancelOrder(self.extend(request, params))
  915. def fetch_order(self, id, symbol=None, params={}):
  916. self.load_markets()
  917. request = {'order_id': id}
  918. response = self.privateGetOrderOrderId(self.extend(request, params))
  919. #
  920. # {
  921. # "data":{
  922. # "order":{
  923. # "id":13790596,
  924. # "market_amount":"0.15",
  925. # "market_amount_remaining":"0.0417463053014",
  926. # "created_at":"2020-05-04T06:08:18.513413Z",
  927. # "price":"0.0230939",
  928. # "order_type":"sell_limit",
  929. # "market_id":41,
  930. # "market_string":"ETH_BTC",
  931. # "open":true,
  932. # "trades":[
  933. # {
  934. # "id":107331,
  935. # "market_amount":"0.1082536946986",
  936. # "price":"0.0230939",
  937. # "base_amount":"0.00249999",
  938. # "taker":true,
  939. # "base_fee":"0.00001249",
  940. # "created_at":"2020-05-04T06:08:18.513413Z"
  941. # }
  942. # ]
  943. # }
  944. # }
  945. # }
  946. #
  947. data = self.safe_value(response, 'data', {})
  948. order = self.safe_value(data, 'order', {})
  949. return self.parse_order(order)
  950. def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
  951. self.load_markets()
  952. request = {
  953. # 'open': True,
  954. # 'older_than': 123, # returns orders with id < older_than
  955. # 'newer_than': 123, # returns orders with id > newer_than
  956. }
  957. market = None
  958. numericId = self.safe_value(params, 'market_id')
  959. if numericId is not None:
  960. request['market_id'] = numericId # mutually exclusive with market_string
  961. elif symbol is not None:
  962. market = self.market(symbol)
  963. request['market_string'] = market['id']
  964. response = self.privateGetOrders(self.extend(request, params))
  965. #
  966. # {
  967. # "data":{
  968. # "orders":[
  969. # {
  970. # "id":13790596,
  971. # "market_amount":"0.15",
  972. # "market_amount_remaining":"0.0417463053014",
  973. # "created_at":"2020-05-04T06:08:18.513413Z",
  974. # "price":"0.0230939",
  975. # "order_type":"sell_limit",
  976. # "market_id":41,
  977. # "market_string":"ETH_BTC",
  978. # "open":true,
  979. # "trades":[
  980. # {
  981. # "id":107331,
  982. # "market_amount":"0.1082536946986",
  983. # "price":"0.0230939",
  984. # "base_amount":"0.00249999",
  985. # "taker":true,
  986. # "base_fee":"0.00001249",
  987. # "created_at":"2020-05-04T06:08:18.513413Z"
  988. # }
  989. # ]
  990. # }
  991. # ]
  992. # }
  993. # }
  994. #
  995. data = self.safe_value(response, 'data', {})
  996. orders = self.safe_value(data, 'orders', [])
  997. return self.parse_orders(orders, market, since, limit)
  998. def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
  999. request = {'open': True}
  1000. return self.fetch_orders(symbol, since, limit, self.extend(request, params))
  1001. def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
  1002. request = {'open': False}
  1003. return self.fetch_orders(symbol, since, limit, self.extend(request, params))
  1004. def parse_deposit_address(self, depositAddress, currency=None):
  1005. #
  1006. # {
  1007. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1008. # "currency_status":"ok",
  1009. # "deposit_methods":{
  1010. # "address":{
  1011. # "deposit_type":"address",
  1012. # "render_type":"address",
  1013. # "label":"Address",
  1014. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1015. # },
  1016. # },
  1017. # }
  1018. #
  1019. code = None if (currency is None) else currency['code']
  1020. address = self.safe_string(depositAddress, 'address')
  1021. tag = None
  1022. if address is not None:
  1023. parts = address.split(':')
  1024. address = self.safe_string(parts, 0)
  1025. tag = self.safe_string(parts, 1)
  1026. self.check_address(address)
  1027. return {
  1028. 'currency': code,
  1029. 'address': address,
  1030. 'tag': tag,
  1031. 'info': depositAddress,
  1032. }
  1033. def fetch_deposit_address(self, code, params={}):
  1034. self.load_markets()
  1035. currency = self.currency(code)
  1036. request = {
  1037. 'currency': currency['id'],
  1038. }
  1039. response = self.privatePostDepositAddressCurrency(self.extend(request, params))
  1040. #
  1041. # {
  1042. # "data":{
  1043. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1044. # "currency_status":"ok",
  1045. # "deposit_methods":{
  1046. # "address":{
  1047. # "deposit_type":"address",
  1048. # "render_type":"address",
  1049. # "label":"Address",
  1050. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1051. # },
  1052. # },
  1053. # },
  1054. # }
  1055. #
  1056. data = self.safe_value(response, 'data', {})
  1057. return self.parse_deposit_address(data, currency)
  1058. def fetch_deposit(self, id, code=None, params={}):
  1059. self.load_markets()
  1060. request = {
  1061. 'deposit_id': id,
  1062. }
  1063. response = self.privateGetDepositDepositId(self.extend(request, params))
  1064. #
  1065. # {
  1066. # "data":{
  1067. # "deposit":{
  1068. # "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
  1069. # "amount":"0.13",
  1070. # "currency":"ETH",
  1071. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1072. # "status":"credited",
  1073. # "relay_status":"",
  1074. # "network_data":{
  1075. # "confirms":87,
  1076. # "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
  1077. # "sweep_balance":"0.150000000000000000",
  1078. # "confirms_required":80,
  1079. # "unsigned_sweep_tx":{
  1080. # "chainId":1,
  1081. # "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1082. # "gas":"0x5208",
  1083. # "gasPrice":"0x19b45a500",
  1084. # "nonce":"0x0",
  1085. # "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
  1086. # "value":"0x214646b6347d800"
  1087. # },
  1088. # "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
  1089. # "tx_index":"0x6f",
  1090. # "tx_value":"0.130000000000000000",
  1091. # "key_index":311,
  1092. # "blockheight":9877869,
  1093. # "signed_sweep_tx":{
  1094. # "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
  1095. # "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
  1096. # },
  1097. # "estimated_sweep_tx_fee":144900000000000
  1098. # },
  1099. # "created_at":"2020-05-04T05:38:42.145162Z"
  1100. # }
  1101. # }
  1102. # }
  1103. data = self.safe_value(response, 'data', {})
  1104. deposit = self.safe_value(data, 'deposit', {})
  1105. return self.parse_transaction(deposit)
  1106. def fetch_deposits(self, code=None, since=None, limit=None, params={}):
  1107. self.load_markets()
  1108. currency = None
  1109. if code is not None:
  1110. currency = self.currency(code)
  1111. response = self.privateGetDeposits(params)
  1112. #
  1113. # {
  1114. # "data":{
  1115. # "deposits":[
  1116. # {
  1117. # "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
  1118. # "amount":"0.13",
  1119. # "currency":"ETH",
  1120. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1121. # "status":"credited",
  1122. # "relay_status":"",
  1123. # "network_data":{
  1124. # "confirms":87,
  1125. # "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
  1126. # "sweep_balance":"0.150000000000000000",
  1127. # "confirms_required":80,
  1128. # "unsigned_sweep_tx":{
  1129. # "chainId":1,
  1130. # "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1131. # "gas":"0x5208",
  1132. # "gasPrice":"0x19b45a500",
  1133. # "nonce":"0x0",
  1134. # "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
  1135. # "value":"0x214646b6347d800"
  1136. # },
  1137. # "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
  1138. # "tx_index":"0x6f",
  1139. # "tx_value":"0.130000000000000000",
  1140. # "key_index":311,
  1141. # "blockheight":9877869,
  1142. # "signed_sweep_tx":{
  1143. # "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
  1144. # "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
  1145. # },
  1146. # "estimated_sweep_tx_fee":144900000000000
  1147. # },
  1148. # "created_at":"2020-05-04T05:38:42.145162Z"
  1149. # }
  1150. # ]
  1151. # }
  1152. # }
  1153. #
  1154. data = self.safe_value(response, 'data', {})
  1155. deposits = self.safe_value(data, 'deposits', [])
  1156. return self.parse_transactions(deposits, currency, since, limit)
  1157. def fetch_withdrawal(self, id, code=None, params={}):
  1158. self.load_markets()
  1159. request = {
  1160. 'withdraw_id': id,
  1161. }
  1162. response = self.privateGetWithdrawWithdrawId(self.extend(request, params))
  1163. #
  1164. # {
  1165. # data: {
  1166. # withdraw: {
  1167. # "id":25524,
  1168. # "amount":"0.0417463053014",
  1169. # "user_id":0,
  1170. # "currency":"ETH",
  1171. # "network_data":{
  1172. # "unsigned_tx":{
  1173. # "chainId":1,
  1174. # "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
  1175. # "gas":"0x5208",
  1176. # "gasPrice":"0x20c8558e9",
  1177. # "nonce":"0xf3",
  1178. # "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1179. # "value":"0x71712bcd113308"
  1180. # },
  1181. # "estimated_tx_fee":184800004893000,
  1182. # "confirms_required":80,
  1183. # "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
  1184. # "confirms":83,
  1185. # "signed_tx":{
  1186. # "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
  1187. # "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
  1188. # }
  1189. # },
  1190. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1191. # "status":"confirmed",
  1192. # "relay_status":"",
  1193. # "created_at":"2020-05-05T06:32:19.907061Z",
  1194. # "cancel_requested":false
  1195. # }
  1196. # }
  1197. # }
  1198. #
  1199. data = self.safe_value(response, 'data', {})
  1200. withdrawal = self.safe_value(data, 'withdraw', {})
  1201. return self.parse_transaction(withdrawal)
  1202. def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
  1203. self.load_markets()
  1204. currency = None
  1205. if code is not None:
  1206. currency = self.currency(code)
  1207. response = self.privateGetWithdraws(params)
  1208. # {
  1209. # "data":{
  1210. # "withdraws":[
  1211. # {
  1212. # "id":25524,
  1213. # "amount":"0.0417463053014",
  1214. # "user_id":0,
  1215. # "currency":"ETH",
  1216. # "network_data":{
  1217. # "unsigned_tx":{
  1218. # "chainId":1,
  1219. # "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
  1220. # "gas":"0x5208",
  1221. # "gasPrice":"0x20c8558e9",
  1222. # "nonce":"0xf3",
  1223. # "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1224. # "value":"0x71712bcd113308"
  1225. # },
  1226. # "estimated_tx_fee":184800004893000,
  1227. # "confirms_required":80,
  1228. # "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
  1229. # "confirms":83,
  1230. # "signed_tx":{
  1231. # "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
  1232. # "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
  1233. # }
  1234. # },
  1235. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1236. # "status":"confirmed",
  1237. # "relay_status":"",
  1238. # "created_at":"2020-05-05T06:32:19.907061Z",
  1239. # "cancel_requested":false
  1240. # }
  1241. # ]
  1242. # }
  1243. # }
  1244. #
  1245. data = self.safe_value(response, 'data', {})
  1246. withdrawals = self.safe_value(data, 'withdraws', [])
  1247. return self.parse_transactions(withdrawals, currency, since, limit)
  1248. def parse_transaction(self, transaction, currency=None):
  1249. #
  1250. # fetchDeposits, fetchDeposit
  1251. #
  1252. # {
  1253. # "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
  1254. # "amount":"0.13",
  1255. # "currency":"ETH",
  1256. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1257. # "status":"credited",
  1258. # "relay_status":"",
  1259. # "network_data":{
  1260. # "confirms":87,
  1261. # "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
  1262. # "sweep_balance":"0.150000000000000000",
  1263. # "confirms_required":80,
  1264. # "unsigned_sweep_tx":{
  1265. # "chainId":1,
  1266. # "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1267. # "gas":"0x5208",
  1268. # "gasPrice":"0x19b45a500",
  1269. # "nonce":"0x0",
  1270. # "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
  1271. # "value":"0x214646b6347d800"
  1272. # },
  1273. # "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
  1274. # "tx_index":"0x6f",
  1275. # "tx_value":"0.130000000000000000",
  1276. # "key_index":311,
  1277. # "blockheight":9877869,
  1278. # "signed_sweep_tx":{
  1279. # "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
  1280. # "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
  1281. # },
  1282. # "estimated_sweep_tx_fee":144900000000000
  1283. # },
  1284. # "created_at":"2020-05-04T05:38:42.145162Z"
  1285. # }
  1286. #
  1287. # fetchWithdrawals, fetchWithdrawal
  1288. #
  1289. # {
  1290. # "id":25524,
  1291. # "amount":"0.0417463053014",
  1292. # "user_id":0,
  1293. # "currency":"ETH",
  1294. # "network_data":{
  1295. # "unsigned_tx":{
  1296. # "chainId":1,
  1297. # "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
  1298. # "gas":"0x5208",
  1299. # "gasPrice":"0x20c8558e9",
  1300. # "nonce":"0xf3",
  1301. # "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1302. # "value":"0x71712bcd113308"
  1303. # },
  1304. # "estimated_tx_fee":184800004893000,
  1305. # "confirms_required":80,
  1306. # "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
  1307. # "confirms":83,
  1308. # "signed_tx":{
  1309. # "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
  1310. # "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
  1311. # }
  1312. # },
  1313. # "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
  1314. # "status":"confirmed",
  1315. # "relay_status":"",
  1316. # "created_at":"2020-05-05T06:32:19.907061Z",
  1317. # "cancel_requested":false
  1318. # }
  1319. #
  1320. # withdraw
  1321. #
  1322. # {
  1323. # "code": "initiated",
  1324. # "id": 3,
  1325. # "result": "Withdraw initiated. Please allow 3-5 minutes for our system to process."
  1326. # }
  1327. #
  1328. timestamp = self.parse8601(self.safe_string(transaction, 'created_at'))
  1329. id = self.safe_string(transaction, 'id')
  1330. networkData = self.safe_value(transaction, 'network_data', {})
  1331. unsignedTx = self.safe_value(networkData, 'unsigned_tx', {})
  1332. addressFrom = self.safe_string(unsignedTx, 'from')
  1333. txid = self.safe_string(networkData, 'txid')
  1334. address = self.safe_string(transaction, 'address')
  1335. tag = None
  1336. if address is not None:
  1337. parts = address.split(':')
  1338. numParts = len(parts)
  1339. if numParts > 1:
  1340. address = self.safe_string(parts, 0)
  1341. tag = self.safe_string(parts, 1)
  1342. addressTo = address
  1343. tagFrom = None
  1344. tagTo = tag
  1345. cancelRequested = self.safe_value(transaction, 'cancel_requested')
  1346. type = 'deposit' if (cancelRequested is None) else 'withdrawal'
  1347. amount = self.safe_float(transaction, 'amount')
  1348. currencyId = self.safe_string(transaction, 'currency')
  1349. code = self.safe_currency_code(currencyId)
  1350. status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
  1351. statusCode = self.safe_string(transaction, 'code')
  1352. if cancelRequested:
  1353. status = 'canceled'
  1354. elif status is None:
  1355. status = self.parse_transaction_status(statusCode)
  1356. fee = None
  1357. return {
  1358. 'info': transaction,
  1359. 'id': id,
  1360. 'txid': txid,
  1361. 'timestamp': timestamp,
  1362. 'datetime': self.iso8601(timestamp),
  1363. 'addressFrom': addressFrom,
  1364. 'addressTo': addressTo,
  1365. 'address': address,
  1366. 'tagFrom': tagFrom,
  1367. 'tagTo': tagTo,
  1368. 'tag': tag,
  1369. 'type': type,
  1370. 'amount': amount,
  1371. 'currency': code,
  1372. 'status': status,
  1373. 'updated': None,
  1374. 'fee': fee,
  1375. }
  1376. def parse_transaction_status(self, status):
  1377. statuses = {
  1378. 'initiated': 'pending',
  1379. 'needs_create': 'pending',
  1380. 'credited': 'ok',
  1381. 'confirmed': 'ok',
  1382. }
  1383. return self.safe_string(statuses, status, status)
  1384. def withdraw(self, code, amount, address, tag=None, params={}):
  1385. self.load_markets()
  1386. currency = self.currency(code)
  1387. request = {
  1388. 'address': address,
  1389. 'amount': amount,
  1390. 'currency': currency['id'],
  1391. }
  1392. if tag is not None:
  1393. request['address'] += ':' + tag
  1394. response = self.privatePostWithdraw(self.extend(request, params))
  1395. #
  1396. # {
  1397. # "data": {
  1398. # "code": "initiated",
  1399. # "id": 3,
  1400. # "result": "Withdraw initiated. Please allow 3-5 minutes for our system to process."
  1401. # }
  1402. # }
  1403. #
  1404. data = self.safe_value(response, 'data', {})
  1405. result = self.parse_transaction(data)
  1406. return self.extend(result, {
  1407. 'currency': code,
  1408. 'address': address,
  1409. 'addressTo': address,
  1410. 'tag': tag,
  1411. 'tagTo': tag,
  1412. 'amount': amount,
  1413. })
  1414. def nonce(self):
  1415. return self.milliseconds()
  1416. def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
  1417. url = '/' + self.version + '/'
  1418. if api == 'private':
  1419. url += 'user/'
  1420. url += self.implode_params(path, params)
  1421. request = self.omit(params, self.extract_params(path))
  1422. if method == 'POST':
  1423. body = self.json(request)
  1424. else:
  1425. if request:
  1426. url += '?' + self.urlencode(request)
  1427. if api == 'private':
  1428. timestamp = str(self.milliseconds())
  1429. bodyAsString = body if (method == 'POST') else ''
  1430. auth = "\n".join([
  1431. method,
  1432. url,
  1433. timestamp,
  1434. bodyAsString,
  1435. self.secret,
  1436. ]) # eslint-disable-line quotes
  1437. hash = self.hash(self.encode(auth), 'sha256', 'base64')
  1438. key = self.apiKey
  1439. if not isinstance(key, basestring):
  1440. key = str(key)
  1441. signature = 'HMAC-SHA256 ' + key + ':' + hash
  1442. headers = {
  1443. 'Authorization': signature,
  1444. 'HMAC-Timestamp': timestamp,
  1445. }
  1446. if method == 'POST':
  1447. headers['Content-Type'] = 'application/json'
  1448. url = self.urls['api'] + url
  1449. return {'url': url, 'method': method, 'body': body, 'headers': headers}
  1450. def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
  1451. #
  1452. # {"errors":[{"code":"insuff_funds","title":"Your available balance is too low for that action"}]}
  1453. # {"errors":[{"code": "invalid_auth","title": "Invalid HMAC signature"}]}
  1454. #
  1455. if response is None:
  1456. return
  1457. errors = self.safe_value(response, 'errors', [])
  1458. numErrors = len(errors)
  1459. if numErrors < 1:
  1460. return
  1461. feedback = self.id + ' ' + body
  1462. for i in range(0, len(errors)):
  1463. error = errors[i]
  1464. errorCode = self.safe_string(error, 'code')
  1465. self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
  1466. raise ExchangeError(feedback) # unknown message