/python/ccxt/async_support/coinex.py

https://github.com/kroitor/ccxt · Python · 895 lines · 620 code · 32 blank · 243 comment · 50 complexity · af99fc6813fd8ec876d259c78a696831 MD5 · raw file

  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. import math
  6. from ccxt.base.errors import ExchangeError
  7. from ccxt.base.errors import AuthenticationError
  8. from ccxt.base.errors import ArgumentsRequired
  9. from ccxt.base.errors import InsufficientFunds
  10. from ccxt.base.errors import InvalidOrder
  11. from ccxt.base.errors import OrderNotFound
  12. class coinex(Exchange):
  13. def describe(self):
  14. return self.deep_extend(super(coinex, self).describe(), {
  15. 'id': 'coinex',
  16. 'name': 'CoinEx',
  17. 'version': 'v1',
  18. 'countries': ['CN'],
  19. 'rateLimit': 1000,
  20. 'has': {
  21. 'cancelOrder': True,
  22. 'createOrder': True,
  23. 'fetchBalance': True,
  24. 'fetchClosedOrders': True,
  25. 'fetchDeposits': True,
  26. 'fetchMarkets': True,
  27. 'fetchMyTrades': True,
  28. 'fetchOHLCV': True,
  29. 'fetchOpenOrders': True,
  30. 'fetchOrder': True,
  31. 'fetchOrderBook': True,
  32. 'fetchTicker': True,
  33. 'fetchTickers': True,
  34. 'fetchTrades': True,
  35. 'fetchWithdrawals': True,
  36. 'withdraw': True,
  37. },
  38. 'timeframes': {
  39. '1m': '1min',
  40. '3m': '3min',
  41. '5m': '5min',
  42. '15m': '15min',
  43. '30m': '30min',
  44. '1h': '1hour',
  45. '2h': '2hour',
  46. '4h': '4hour',
  47. '6h': '6hour',
  48. '12h': '12hour',
  49. '1d': '1day',
  50. '3d': '3day',
  51. '1w': '1week',
  52. },
  53. 'urls': {
  54. 'logo': 'https://user-images.githubusercontent.com/51840849/87182089-1e05fa00-c2ec-11ea-8da9-cc73b45abbbc.jpg',
  55. 'api': 'https://api.coinex.com',
  56. 'www': 'https://www.coinex.com',
  57. 'doc': 'https://github.com/coinexcom/coinex_exchange_api/wiki',
  58. 'fees': 'https://www.coinex.com/fees',
  59. 'referral': 'https://www.coinex.com/register?refer_code=yw5fz',
  60. },
  61. 'api': {
  62. 'public': {
  63. 'get': [
  64. 'common/currency/rate',
  65. 'common/asset/config',
  66. 'market/info',
  67. 'market/list',
  68. 'market/ticker',
  69. 'market/ticker/all',
  70. 'market/depth',
  71. 'market/deals',
  72. 'market/kline',
  73. ],
  74. },
  75. 'private': {
  76. 'get': [
  77. 'balance/coin/deposit',
  78. 'balance/coin/withdraw',
  79. 'balance/info',
  80. 'future/account',
  81. 'future/config',
  82. 'future/limitprice',
  83. 'future/loan/history',
  84. 'future/market',
  85. 'margin/account',
  86. 'margin/config',
  87. 'margin/loan/history',
  88. 'margin/market',
  89. 'order',
  90. 'order/deals',
  91. 'order/finished',
  92. 'order/finished/{id}',
  93. 'order/pending',
  94. 'order/status',
  95. 'order/status/batch',
  96. 'order/user/deals',
  97. ],
  98. 'post': [
  99. 'balance/coin/withdraw',
  100. 'future/flat',
  101. 'future/loan',
  102. 'future/transfer',
  103. 'margin/flat',
  104. 'margin/loan',
  105. 'margin/transfer',
  106. 'order/batchlimit',
  107. 'order/ioc',
  108. 'order/limit',
  109. 'order/market',
  110. 'sub_account/transfer',
  111. ],
  112. 'delete': [
  113. 'balance/coin/withdraw',
  114. 'order/pending/batch',
  115. 'order/pending',
  116. ],
  117. },
  118. },
  119. 'fees': {
  120. 'trading': {
  121. 'maker': 0.001,
  122. 'taker': 0.001,
  123. },
  124. 'funding': {
  125. 'withdraw': {
  126. 'BCH': 0.0,
  127. 'BTC': 0.001,
  128. 'LTC': 0.001,
  129. 'ETH': 0.001,
  130. 'ZEC': 0.0001,
  131. 'DASH': 0.0001,
  132. },
  133. },
  134. },
  135. 'limits': {
  136. 'amount': {
  137. 'min': 0.001,
  138. 'max': None,
  139. },
  140. },
  141. 'precision': {
  142. 'amount': 8,
  143. 'price': 8,
  144. },
  145. 'options': {
  146. 'createMarketBuyOrderRequiresPrice': True,
  147. },
  148. })
  149. async def fetch_markets(self, params={}):
  150. response = await self.publicGetMarketInfo(params)
  151. #
  152. # {
  153. # "code": 0,
  154. # "data": {
  155. # "WAVESBTC": {
  156. # "name": "WAVESBTC",
  157. # "min_amount": "1",
  158. # "maker_fee_rate": "0.001",
  159. # "taker_fee_rate": "0.001",
  160. # "pricing_name": "BTC",
  161. # "pricing_decimal": 8,
  162. # "trading_name": "WAVES",
  163. # "trading_decimal": 8
  164. # }
  165. # }
  166. # }
  167. #
  168. markets = self.safe_value(response, 'data', {})
  169. result = []
  170. keys = list(markets.keys())
  171. for i in range(0, len(keys)):
  172. key = keys[i]
  173. market = markets[key]
  174. id = self.safe_string(market, 'name')
  175. tradingName = self.safe_string(market, 'trading_name')
  176. baseId = tradingName
  177. quoteId = self.safe_string(market, 'pricing_name')
  178. base = self.safe_currency_code(baseId)
  179. quote = self.safe_currency_code(quoteId)
  180. symbol = base + '/' + quote
  181. if tradingName == id:
  182. symbol = id
  183. precision = {
  184. 'amount': self.safe_integer(market, 'trading_decimal'),
  185. 'price': self.safe_integer(market, 'pricing_decimal'),
  186. }
  187. active = None
  188. result.append({
  189. 'id': id,
  190. 'symbol': symbol,
  191. 'base': base,
  192. 'quote': quote,
  193. 'baseId': baseId,
  194. 'quoteId': quoteId,
  195. 'active': active,
  196. 'taker': self.safe_float(market, 'taker_fee_rate'),
  197. 'maker': self.safe_float(market, 'maker_fee_rate'),
  198. 'info': market,
  199. 'precision': precision,
  200. 'limits': {
  201. 'amount': {
  202. 'min': self.safe_float(market, 'min_amount'),
  203. 'max': None,
  204. },
  205. 'price': {
  206. 'min': math.pow(10, -precision['price']),
  207. 'max': None,
  208. },
  209. },
  210. })
  211. return result
  212. def parse_ticker(self, ticker, market=None):
  213. timestamp = self.safe_integer(ticker, 'date')
  214. symbol = None
  215. if market is not None:
  216. symbol = market['symbol']
  217. ticker = self.safe_value(ticker, 'ticker', {})
  218. last = self.safe_float(ticker, 'last')
  219. return {
  220. 'symbol': symbol,
  221. 'timestamp': timestamp,
  222. 'datetime': self.iso8601(timestamp),
  223. 'high': self.safe_float(ticker, 'high'),
  224. 'low': self.safe_float(ticker, 'low'),
  225. 'bid': self.safe_float(ticker, 'buy'),
  226. 'bidVolume': None,
  227. 'ask': self.safe_float(ticker, 'sell'),
  228. 'askVolume': None,
  229. 'vwap': None,
  230. 'open': None,
  231. 'close': last,
  232. 'last': last,
  233. 'previousClose': None,
  234. 'change': None,
  235. 'percentage': None,
  236. 'average': None,
  237. 'baseVolume': self.safe_float_2(ticker, 'vol', 'volume'),
  238. 'quoteVolume': None,
  239. 'info': ticker,
  240. }
  241. async def fetch_ticker(self, symbol, params={}):
  242. await self.load_markets()
  243. market = self.market(symbol)
  244. request = {
  245. 'market': market['id'],
  246. }
  247. response = await self.publicGetMarketTicker(self.extend(request, params))
  248. return self.parse_ticker(response['data'], market)
  249. async def fetch_tickers(self, symbols=None, params={}):
  250. await self.load_markets()
  251. response = await self.publicGetMarketTickerAll(params)
  252. data = self.safe_value(response, 'data')
  253. timestamp = self.safe_integer(data, 'date')
  254. tickers = self.safe_value(data, 'ticker')
  255. marketIds = list(tickers.keys())
  256. result = {}
  257. for i in range(0, len(marketIds)):
  258. marketId = marketIds[i]
  259. symbol = marketId
  260. market = None
  261. if marketId in self.markets_by_id:
  262. market = self.markets_by_id[marketId]
  263. symbol = market['symbol']
  264. ticker = self.parse_ticker({
  265. 'date': timestamp,
  266. 'ticker': tickers[marketId],
  267. }, market)
  268. ticker['symbol'] = symbol
  269. result[symbol] = ticker
  270. return self.filter_by_array(result, 'symbol', symbols)
  271. async def fetch_order_book(self, symbol, limit=20, params={}):
  272. await self.load_markets()
  273. if limit is None:
  274. limit = 20 # default
  275. request = {
  276. 'market': self.market_id(symbol),
  277. 'merge': '0.0000000001',
  278. 'limit': str(limit),
  279. }
  280. response = await self.publicGetMarketDepth(self.extend(request, params))
  281. return self.parse_order_book(response['data'])
  282. def parse_trade(self, trade, market=None):
  283. # self method parses both public and private trades
  284. timestamp = self.safe_timestamp(trade, 'create_time')
  285. if timestamp is None:
  286. timestamp = self.safe_integer(trade, 'date_ms')
  287. tradeId = self.safe_string(trade, 'id')
  288. orderId = self.safe_string(trade, 'order_id')
  289. price = self.safe_float(trade, 'price')
  290. amount = self.safe_float(trade, 'amount')
  291. marketId = self.safe_string(trade, 'market')
  292. market = self.safe_value(self.markets_by_id, marketId, market)
  293. symbol = None
  294. if market is not None:
  295. symbol = market['symbol']
  296. cost = self.safe_float(trade, 'deal_money')
  297. if not cost:
  298. cost = float(self.cost_to_precision(symbol, price * amount))
  299. fee = None
  300. feeCost = self.safe_float(trade, 'fee')
  301. if feeCost is not None:
  302. feeCurrencyId = self.safe_string(trade, 'fee_asset')
  303. feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
  304. fee = {
  305. 'cost': feeCost,
  306. 'currency': feeCurrencyCode,
  307. }
  308. takerOrMaker = self.safe_string(trade, 'role')
  309. side = self.safe_string(trade, 'type')
  310. return {
  311. 'info': trade,
  312. 'timestamp': timestamp,
  313. 'datetime': self.iso8601(timestamp),
  314. 'symbol': symbol,
  315. 'id': tradeId,
  316. 'order': orderId,
  317. 'type': None,
  318. 'side': side,
  319. 'takerOrMaker': takerOrMaker,
  320. 'price': price,
  321. 'amount': amount,
  322. 'cost': cost,
  323. 'fee': fee,
  324. }
  325. async def fetch_trades(self, symbol, since=None, limit=None, params={}):
  326. await self.load_markets()
  327. market = self.market(symbol)
  328. request = {
  329. 'market': market['id'],
  330. }
  331. response = await self.publicGetMarketDeals(self.extend(request, params))
  332. return self.parse_trades(response['data'], market, since, limit)
  333. def parse_ohlcv(self, ohlcv, market=None):
  334. #
  335. # [
  336. # 1591484400,
  337. # "0.02505349",
  338. # "0.02506988",
  339. # "0.02507000",
  340. # "0.02505304",
  341. # "343.19716223",
  342. # "8.6021323866383196",
  343. # "ETHBTC"
  344. # ]
  345. #
  346. return [
  347. self.safe_timestamp(ohlcv, 0),
  348. self.safe_float(ohlcv, 1),
  349. self.safe_float(ohlcv, 3),
  350. self.safe_float(ohlcv, 4),
  351. self.safe_float(ohlcv, 2),
  352. self.safe_float(ohlcv, 5),
  353. ]
  354. async def fetch_ohlcv(self, symbol, timeframe='5m', since=None, limit=None, params={}):
  355. await self.load_markets()
  356. market = self.market(symbol)
  357. request = {
  358. 'market': market['id'],
  359. 'type': self.timeframes[timeframe],
  360. }
  361. if limit is not None:
  362. request['limit'] = limit
  363. response = await self.publicGetMarketKline(self.extend(request, params))
  364. #
  365. # {
  366. # "code": 0,
  367. # "data": [
  368. # [1591484400, "0.02505349", "0.02506988", "0.02507000", "0.02505304", "343.19716223", "8.6021323866383196", "ETHBTC"],
  369. # [1591484700, "0.02506990", "0.02508109", "0.02508109", "0.02506979", "91.59841581", "2.2972047780447000", "ETHBTC"],
  370. # [1591485000, "0.02508106", "0.02507996", "0.02508106", "0.02507500", "65.15307697", "1.6340597822306000", "ETHBTC"],
  371. # ],
  372. # "message": "OK"
  373. # }
  374. #
  375. data = self.safe_value(response, 'data', [])
  376. return self.parse_ohlcvs(data, market, timeframe, since, limit)
  377. async def fetch_balance(self, params={}):
  378. await self.load_markets()
  379. response = await self.privateGetBalanceInfo(params)
  380. #
  381. # {
  382. # "code": 0,
  383. # "data": {
  384. # "BCH": { # BCH account
  385. # "available": "13.60109", # Available BCH
  386. # "frozen": "0.00000" # Frozen BCH
  387. # },
  388. # "BTC": { # BTC account
  389. # "available": "32590.16", # Available BTC
  390. # "frozen": "7000.00" # Frozen BTC
  391. # },
  392. # "ETH": { # ETH account
  393. # "available": "5.06000", # Available ETH
  394. # "frozen": "0.00000" # Frozen ETH
  395. # }
  396. # },
  397. # "message": "Ok"
  398. # }
  399. #
  400. result = {'info': response}
  401. balances = self.safe_value(response, 'data')
  402. currencyIds = list(balances.keys())
  403. for i in range(0, len(currencyIds)):
  404. currencyId = currencyIds[i]
  405. code = self.safe_currency_code(currencyId)
  406. balance = self.safe_value(balances, currencyId, {})
  407. account = self.account()
  408. account['free'] = self.safe_float(balance, 'available')
  409. account['used'] = self.safe_float(balance, 'frozen')
  410. result[code] = account
  411. return self.parse_balance(result)
  412. def parse_order_status(self, status):
  413. statuses = {
  414. 'not_deal': 'open',
  415. 'part_deal': 'open',
  416. 'done': 'closed',
  417. 'cancel': 'canceled',
  418. }
  419. return self.safe_string(statuses, status, status)
  420. def parse_order(self, order, market=None):
  421. #
  422. # fetchOrder
  423. #
  424. # {
  425. # "amount": "0.1",
  426. # "asset_fee": "0.22736197736197736197",
  427. # "avg_price": "196.85000000000000000000",
  428. # "create_time": 1537270135,
  429. # "deal_amount": "0.1",
  430. # "deal_fee": "0",
  431. # "deal_money": "19.685",
  432. # "fee_asset": "CET",
  433. # "fee_discount": "0.5",
  434. # "id": 1788259447,
  435. # "left": "0",
  436. # "maker_fee_rate": "0",
  437. # "market": "ETHUSDT",
  438. # "order_type": "limit",
  439. # "price": "170.00000000",
  440. # "status": "done",
  441. # "taker_fee_rate": "0.0005",
  442. # "type": "sell",
  443. # }
  444. #
  445. timestamp = self.safe_timestamp(order, 'create_time')
  446. price = self.safe_float(order, 'price')
  447. cost = self.safe_float(order, 'deal_money')
  448. amount = self.safe_float(order, 'amount')
  449. filled = self.safe_float(order, 'deal_amount')
  450. average = self.safe_float(order, 'avg_price')
  451. symbol = None
  452. marketId = self.safe_string(order, 'market')
  453. market = self.safe_value(self.markets_by_id, marketId)
  454. feeCurrencyId = self.safe_string(order, 'fee_asset')
  455. feeCurrency = self.safe_currency_code(feeCurrencyId)
  456. if market is not None:
  457. symbol = market['symbol']
  458. if feeCurrency is None:
  459. feeCurrency = market['quote']
  460. remaining = self.safe_float(order, 'left')
  461. status = self.parse_order_status(self.safe_string(order, 'status'))
  462. type = self.safe_string(order, 'order_type')
  463. side = self.safe_string(order, 'type')
  464. return {
  465. 'id': self.safe_string(order, 'id'),
  466. 'clientOrderId': None,
  467. 'datetime': self.iso8601(timestamp),
  468. 'timestamp': timestamp,
  469. 'lastTradeTimestamp': None,
  470. 'status': status,
  471. 'symbol': symbol,
  472. 'type': type,
  473. 'side': side,
  474. 'price': price,
  475. 'cost': cost,
  476. 'average': average,
  477. 'amount': amount,
  478. 'filled': filled,
  479. 'remaining': remaining,
  480. 'trades': None,
  481. 'fee': {
  482. 'currency': feeCurrency,
  483. 'cost': self.safe_float(order, 'deal_fee'),
  484. },
  485. 'info': order,
  486. }
  487. async def create_order(self, symbol, type, side, amount, price=None, params={}):
  488. await self.load_markets()
  489. method = 'privatePostOrder' + self.capitalize(type)
  490. market = self.market(symbol)
  491. request = {
  492. 'market': market['id'],
  493. 'type': side,
  494. }
  495. amount = float(amount)
  496. # for market buy it requires the amount of quote currency to spend
  497. if (type == 'market') and (side == 'buy'):
  498. if self.options['createMarketBuyOrderRequiresPrice']:
  499. if price is None:
  500. raise InvalidOrder(self.id + " createOrder() requires the price argument with market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = False to supply the cost in the amount argument(the exchange-specific behaviour)")
  501. else:
  502. price = float(price)
  503. request['amount'] = self.cost_to_precision(symbol, amount * price)
  504. else:
  505. request['amount'] = self.cost_to_precision(symbol, amount)
  506. else:
  507. request['amount'] = self.amount_to_precision(symbol, amount)
  508. if (type == 'limit') or (type == 'ioc'):
  509. request['price'] = self.price_to_precision(symbol, price)
  510. response = await getattr(self, method)(self.extend(request, params))
  511. data = self.safe_value(response, 'data')
  512. return self.parse_order(data, market)
  513. async def cancel_order(self, id, symbol=None, params={}):
  514. await self.load_markets()
  515. market = self.market(symbol)
  516. request = {
  517. 'id': id,
  518. 'market': market['id'],
  519. }
  520. response = await self.privateDeleteOrderPending(self.extend(request, params))
  521. data = self.safe_value(response, 'data')
  522. return self.parse_order(data, market)
  523. async def fetch_order(self, id, symbol=None, params={}):
  524. if symbol is None:
  525. raise ArgumentsRequired(self.id + ' fetchOrder requires a symbol argument')
  526. await self.load_markets()
  527. market = self.market(symbol)
  528. request = {
  529. 'id': id,
  530. 'market': market['id'],
  531. }
  532. response = await self.privateGetOrder(self.extend(request, params))
  533. #
  534. # {
  535. # "code": 0,
  536. # "data": {
  537. # "amount": "0.1",
  538. # "asset_fee": "0.22736197736197736197",
  539. # "avg_price": "196.85000000000000000000",
  540. # "create_time": 1537270135,
  541. # "deal_amount": "0.1",
  542. # "deal_fee": "0",
  543. # "deal_money": "19.685",
  544. # "fee_asset": "CET",
  545. # "fee_discount": "0.5",
  546. # "id": 1788259447,
  547. # "left": "0",
  548. # "maker_fee_rate": "0",
  549. # "market": "ETHUSDT",
  550. # "order_type": "limit",
  551. # "price": "170.00000000",
  552. # "status": "done",
  553. # "taker_fee_rate": "0.0005",
  554. # "type": "sell",
  555. # },
  556. # "message": "Ok"
  557. # }
  558. #
  559. data = self.safe_value(response, 'data')
  560. return self.parse_order(data, market)
  561. async def fetch_orders_by_status(self, status, symbol=None, since=None, limit=None, params={}):
  562. await self.load_markets()
  563. if limit is None:
  564. limit = 100
  565. request = {
  566. 'page': 1,
  567. 'limit': limit,
  568. }
  569. market = None
  570. if symbol is not None:
  571. market = self.market(symbol)
  572. request['market'] = market['id']
  573. method = 'privateGetOrder' + self.capitalize(status)
  574. response = await getattr(self, method)(self.extend(request, params))
  575. data = self.safe_value(response, 'data')
  576. orders = self.safe_value(data, 'data', [])
  577. return self.parse_orders(orders, market, since, limit)
  578. async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
  579. return await self.fetch_orders_by_status('pending', symbol, since, limit, params)
  580. async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
  581. return await self.fetch_orders_by_status('finished', symbol, since, limit, params)
  582. async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
  583. await self.load_markets()
  584. if limit is None:
  585. limit = 100
  586. request = {
  587. 'page': 1,
  588. 'limit': limit,
  589. }
  590. market = None
  591. if symbol is not None:
  592. market = self.market(symbol)
  593. request['market'] = market['id']
  594. response = await self.privateGetOrderUserDeals(self.extend(request, params))
  595. data = self.safe_value(response, 'data')
  596. trades = self.safe_value(data, 'data', [])
  597. return self.parse_trades(trades, market, since, limit)
  598. async def withdraw(self, code, amount, address, tag=None, params={}):
  599. self.check_address(address)
  600. await self.load_markets()
  601. currency = self.currency(code)
  602. if tag:
  603. address = address + ':' + tag
  604. request = {
  605. 'coin_type': currency['id'],
  606. 'coin_address': address, # must be authorized, inter-user transfer by a registered mobile phone number or an email address is supported
  607. 'actual_amount': float(amount), # the actual amount without fees, https://www.coinex.com/fees
  608. 'transfer_method': 'onchain', # onchain, local
  609. }
  610. response = await self.privatePostBalanceCoinWithdraw(self.extend(request, params))
  611. #
  612. # {
  613. # "code": 0,
  614. # "data": {
  615. # "actual_amount": "1.00000000",
  616. # "amount": "1.00000000",
  617. # "coin_address": "1KAv3pazbTk2JnQ5xTo6fpKK7p1it2RzD4",
  618. # "coin_type": "BCH",
  619. # "coin_withdraw_id": 206,
  620. # "confirmations": 0,
  621. # "create_time": 1524228297,
  622. # "status": "audit",
  623. # "tx_fee": "0",
  624. # "tx_id": ""
  625. # },
  626. # "message": "Ok"
  627. # }
  628. #
  629. transaction = self.safe_value(response, 'data', {})
  630. return self.parse_transaction(transaction, currency)
  631. def parse_transaction_status(self, status):
  632. statuses = {
  633. 'audit': 'pending',
  634. 'pass': 'pending',
  635. 'processing': 'pending',
  636. 'confirming': 'pending',
  637. 'not_pass': 'failed',
  638. 'cancel': 'canceled',
  639. 'finish': 'ok',
  640. 'fail': 'failed',
  641. }
  642. return self.safe_string(statuses, status, status)
  643. def parse_transaction(self, transaction, currency=None):
  644. #
  645. # fetchDeposits
  646. #
  647. # {
  648. # "actual_amount": "120.00000000",
  649. # "actual_amount_display": "120",
  650. # "add_explorer": "XXX",
  651. # "amount": "120.00000000",
  652. # "amount_display": "120",
  653. # "coin_address": "XXXXXXXX",
  654. # "coin_address_display": "XXXXXXXX",
  655. # "coin_deposit_id": 1866,
  656. # "coin_type": "USDT",
  657. # "confirmations": 0,
  658. # "create_time": 1539595701,
  659. # "explorer": "",
  660. # "remark": "",
  661. # "status": "finish",
  662. # "status_display": "finish",
  663. # "transfer_method": "local",
  664. # "tx_id": "",
  665. # "tx_id_display": "XXXXXXXXXX"
  666. # }
  667. #
  668. # fetchWithdrawals
  669. #
  670. # {
  671. # "actual_amount": "0.10000000",
  672. # "amount": "0.10000000",
  673. # "coin_address": "15sr1VdyXQ6sVLqeJUJ1uPzLpmQtgUeBSB",
  674. # "coin_type": "BCH",
  675. # "coin_withdraw_id": 203,
  676. # "confirmations": 11,
  677. # "create_time": 1515806440,
  678. # "status": "finish",
  679. # "tx_fee": "0",
  680. # "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6"
  681. # }
  682. #
  683. id = self.safe_string_2(transaction, 'coin_withdraw_id', 'coin_deposit_id')
  684. address = self.safe_string(transaction, 'coin_address')
  685. tag = self.safe_string(transaction, 'remark') # set but unused
  686. if tag is not None:
  687. if len(tag) < 1:
  688. tag = None
  689. txid = self.safe_value(transaction, 'tx_id')
  690. if txid is not None:
  691. if len(txid) < 1:
  692. txid = None
  693. currencyId = self.safe_string(transaction, 'coin_type')
  694. code = self.safe_currency_code(currencyId, currency)
  695. timestamp = self.safe_timestamp(transaction, 'create_time')
  696. type = 'withdraw' if ('coin_withdraw_id' in transaction) else 'deposit'
  697. status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
  698. amount = self.safe_float(transaction, 'amount')
  699. feeCost = self.safe_float(transaction, 'tx_fee')
  700. if type == 'deposit':
  701. feeCost = 0
  702. fee = {
  703. 'cost': feeCost,
  704. 'currency': code,
  705. }
  706. return {
  707. 'info': transaction,
  708. 'id': id,
  709. 'txid': txid,
  710. 'timestamp': timestamp,
  711. 'datetime': self.iso8601(timestamp),
  712. 'address': address,
  713. 'tag': tag,
  714. 'type': type,
  715. 'amount': amount,
  716. 'currency': code,
  717. 'status': status,
  718. 'updated': None,
  719. 'fee': fee,
  720. }
  721. async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
  722. if code is None:
  723. raise ArgumentsRequired(self.id + ' fetchWithdrawals requires a currency code argument')
  724. await self.load_markets()
  725. currency = self.currency(code)
  726. request = {
  727. 'coin_type': currency['id'],
  728. }
  729. if limit is not None:
  730. request['Limit'] = limit
  731. response = await self.privateGetBalanceCoinWithdraw(self.extend(request, params))
  732. #
  733. # {
  734. # "code": 0,
  735. # "data": [
  736. # {
  737. # "actual_amount": "1.00000000",
  738. # "amount": "1.00000000",
  739. # "coin_address": "1KAv3pazbTk2JnQ5xTo6fpKK7p1it2RzD4",
  740. # "coin_type": "BCH",
  741. # "coin_withdraw_id": 206,
  742. # "confirmations": 0,
  743. # "create_time": 1524228297,
  744. # "status": "audit",
  745. # "tx_fee": "0",
  746. # "tx_id": ""
  747. # },
  748. # {
  749. # "actual_amount": "0.10000000",
  750. # "amount": "0.10000000",
  751. # "coin_address": "15sr1VdyXQ6sVLqeJUJ1uPzLpmQtgUeBSB",
  752. # "coin_type": "BCH",
  753. # "coin_withdraw_id": 203,
  754. # "confirmations": 11,
  755. # "create_time": 1515806440,
  756. # "status": "finish",
  757. # "tx_fee": "0",
  758. # "tx_id": "896371d0e23d64d1cac65a0b7c9e9093d835affb572fec89dd4547277fbdd2f6"
  759. # },
  760. # {
  761. # "actual_amount": "0.00100000",
  762. # "amount": "0.00100000",
  763. # "coin_address": "1GVVx5UBddLKrckTprNi4VhHSymeQ8tsLF",
  764. # "coin_type": "BCH",
  765. # "coin_withdraw_id": 27,
  766. # "confirmations": 0,
  767. # "create_time": 1513933541,
  768. # "status": "cancel",
  769. # "tx_fee": "0",
  770. # "tx_id": ""
  771. # }
  772. # ],
  773. # "message": "Ok"
  774. # }
  775. #
  776. data = self.safe_value(response, 'data', [])
  777. return self.parse_transactions(data, currency, since, limit)
  778. async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
  779. if code is None:
  780. raise ArgumentsRequired(self.id + ' fetchDeposits requires a currency code argument')
  781. await self.load_markets()
  782. currency = self.currency(code)
  783. request = {
  784. 'coin_type': currency['id'],
  785. }
  786. if limit is not None:
  787. request['Limit'] = limit
  788. response = await self.privateGetBalanceCoinDeposit(self.extend(request, params))
  789. # {
  790. # "code": 0,
  791. # "data": [
  792. # {
  793. # "actual_amount": "4.65397682",
  794. # "actual_amount_display": "4.65397682",
  795. # "add_explorer": "https://etherscan.io/address/0x361XXXXXX",
  796. # "amount": "4.65397682",
  797. # "amount_display": "4.65397682",
  798. # "coin_address": "0x36dabcdXXXXXX",
  799. # "coin_address_display": "0x361X*****XXXXX",
  800. # "coin_deposit_id": 966191,
  801. # "coin_type": "ETH",
  802. # "confirmations": 30,
  803. # "create_time": 1531661445,
  804. # "explorer": "https://etherscan.io/tx/0x361XXXXXX",
  805. # "remark": "",
  806. # "status": "finish",
  807. # "status_display": "finish",
  808. # "transfer_method": "onchain",
  809. # "tx_id": "0x361XXXXXX",
  810. # "tx_id_display": "0x361XXXXXX"
  811. # }
  812. # ],
  813. # "message": "Ok"
  814. # }
  815. #
  816. data = self.safe_value(response, 'data', [])
  817. return self.parse_transactions(data, currency, since, limit)
  818. def nonce(self):
  819. return self.milliseconds()
  820. def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
  821. path = self.implode_params(path, params)
  822. url = self.urls['api'] + '/' + self.version + '/' + path
  823. query = self.omit(params, self.extract_params(path))
  824. if api == 'public':
  825. if query:
  826. url += '?' + self.urlencode(query)
  827. else:
  828. self.check_required_credentials()
  829. nonce = self.nonce()
  830. query = self.extend({
  831. 'access_id': self.apiKey,
  832. 'tonce': str(nonce),
  833. }, query)
  834. query = self.keysort(query)
  835. urlencoded = self.urlencode(query)
  836. signature = self.hash(self.encode(urlencoded + '&secret_key=' + self.secret))
  837. headers = {
  838. 'Authorization': signature.upper(),
  839. 'Content-Type': 'application/json',
  840. }
  841. if (method == 'GET') or (method == 'DELETE'):
  842. url += '?' + urlencoded
  843. else:
  844. body = self.json(query)
  845. return {'url': url, 'method': method, 'body': body, 'headers': headers}
  846. async def request(self, path, api='public', method='GET', params={}, headers=None, body=None):
  847. response = await self.fetch2(path, api, method, params, headers, body)
  848. code = self.safe_string(response, 'code')
  849. data = self.safe_value(response, 'data')
  850. message = self.safe_string(response, 'message')
  851. if (code != '0') or (data is None) or ((message != 'Ok') and not data):
  852. responseCodes = {
  853. '24': AuthenticationError,
  854. '25': AuthenticationError,
  855. '107': InsufficientFunds,
  856. '600': OrderNotFound,
  857. '601': InvalidOrder,
  858. '602': InvalidOrder,
  859. '606': InvalidOrder,
  860. }
  861. ErrorClass = self.safe_value(responseCodes, code, ExchangeError)
  862. raise ErrorClass(response['message'])
  863. return response