/python/ccxt/gateio.py

https://github.com/kroitor/ccxt · Python · 926 lines · 718 code · 38 blank · 170 comment · 58 complexity · 161d6819f2246eb72cfcc2bf38b466a1 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.base.exchange import Exchange
  5. import hashlib
  6. import math
  7. from ccxt.base.errors import ExchangeError
  8. from ccxt.base.errors import AuthenticationError
  9. from ccxt.base.errors import ArgumentsRequired
  10. from ccxt.base.errors import InsufficientFunds
  11. from ccxt.base.errors import InvalidAddress
  12. from ccxt.base.errors import InvalidOrder
  13. from ccxt.base.errors import OrderNotFound
  14. from ccxt.base.errors import NotSupported
  15. from ccxt.base.errors import DDoSProtection
  16. class gateio(Exchange):
  17. def describe(self):
  18. return self.deep_extend(super(gateio, self).describe(), {
  19. 'id': 'gateio',
  20. 'name': 'Gate.io',
  21. 'countries': ['CN'],
  22. 'version': '2',
  23. 'rateLimit': 1000,
  24. 'pro': True,
  25. 'has': {
  26. 'cancelOrder': True,
  27. 'CORS': False,
  28. 'createDepositAddress': True,
  29. 'createMarketOrder': False,
  30. 'createOrder': True,
  31. 'fetchBalance': True,
  32. 'fetchClosedOrders': False,
  33. 'fetchCurrencies': True,
  34. 'fetchDepositAddress': True,
  35. 'fetchDeposits': True,
  36. 'fetchMarkets': True,
  37. 'fetchMyTrades': True,
  38. 'fetchOHLCV': True,
  39. 'fetchOpenOrders': True,
  40. 'fetchOrder': True,
  41. 'fetchOrderBook': True,
  42. 'fetchOrders': True,
  43. 'fetchOrderTrades': True,
  44. 'fetchTicker': True,
  45. 'fetchTickers': True,
  46. 'fetchTrades': True,
  47. 'fetchTransactions': True,
  48. 'fetchWithdrawals': True,
  49. 'withdraw': True,
  50. },
  51. 'timeframes': {
  52. '1m': 60,
  53. '5m': 300,
  54. '10m': 600,
  55. '15m': 900,
  56. '30m': 1800,
  57. '1h': 3600,
  58. '2h': 7200,
  59. '4h': 14400,
  60. '6h': 21600,
  61. '12h': 43200,
  62. '1d': 86400,
  63. '1w': 604800,
  64. },
  65. 'urls': {
  66. 'logo': 'https://user-images.githubusercontent.com/1294454/31784029-0313c702-b509-11e7-9ccc-bc0da6a0e435.jpg',
  67. 'api': {
  68. 'public': 'https://data.gate.io/api',
  69. 'private': 'https://data.gate.io/api',
  70. },
  71. 'www': 'https://gate.io/',
  72. 'doc': 'https://gate.io/api2',
  73. 'fees': [
  74. 'https://gate.io/fee',
  75. 'https://support.gate.io/hc/en-us/articles/115003577673',
  76. ],
  77. 'referral': 'https://www.gate.io/signup/2436035',
  78. },
  79. 'api': {
  80. 'public': {
  81. 'get': [
  82. 'candlestick2/{id}',
  83. 'pairs',
  84. 'coininfo',
  85. 'marketinfo',
  86. 'marketlist',
  87. 'coininfo',
  88. 'tickers',
  89. 'ticker/{id}',
  90. 'orderBook/{id}',
  91. 'trade/{id}',
  92. 'tradeHistory/{id}',
  93. 'tradeHistory/{id}/{tid}',
  94. ],
  95. },
  96. 'private': {
  97. 'post': [
  98. 'balances',
  99. 'depositAddress',
  100. 'newAddress',
  101. 'depositsWithdrawals',
  102. 'buy',
  103. 'sell',
  104. 'cancelOrder',
  105. 'cancelAllOrders',
  106. 'getOrder',
  107. 'openOrders',
  108. 'tradeHistory',
  109. 'feelist',
  110. 'withdraw',
  111. ],
  112. },
  113. },
  114. 'fees': {
  115. 'trading': {
  116. 'tierBased': True,
  117. 'percentage': True,
  118. 'maker': 0.002,
  119. 'taker': 0.002,
  120. },
  121. },
  122. 'exceptions': {
  123. 'exact': {
  124. '4': DDoSProtection,
  125. '5': AuthenticationError, # {result: "false", code: 5, message: "Error: invalid key or sign, please re-generate it from your account"}
  126. '6': AuthenticationError, # {result: 'false', code: 6, message: 'Error: invalid data '}
  127. '7': NotSupported,
  128. '8': NotSupported,
  129. '9': NotSupported,
  130. '15': DDoSProtection,
  131. '16': OrderNotFound,
  132. '17': OrderNotFound,
  133. '20': InvalidOrder,
  134. '21': InsufficientFunds,
  135. },
  136. # https://gate.io/api2#errCode
  137. 'errorCodeNames': {
  138. '1': 'Invalid request',
  139. '2': 'Invalid version',
  140. '3': 'Invalid request',
  141. '4': 'Too many attempts',
  142. '5': 'Invalid sign',
  143. '6': 'Invalid sign',
  144. '7': 'Currency is not supported',
  145. '8': 'Currency is not supported',
  146. '9': 'Currency is not supported',
  147. '10': 'Verified failed',
  148. '11': 'Obtaining address failed',
  149. '12': 'Empty params',
  150. '13': 'Internal error, please report to administrator',
  151. '14': 'Invalid user',
  152. '15': 'Cancel order too fast, please wait 1 min and try again',
  153. '16': 'Invalid order id or order is already closed',
  154. '17': 'Invalid orderid',
  155. '18': 'Invalid amount',
  156. '19': 'Not permitted or trade is disabled',
  157. '20': 'Your order size is too small',
  158. '21': 'You don\'t have enough fund',
  159. },
  160. },
  161. 'options': {
  162. 'limits': {
  163. 'cost': {
  164. 'min': {
  165. 'BTC': 0.0001,
  166. 'ETH': 0.001,
  167. 'USDT': 1,
  168. },
  169. },
  170. },
  171. },
  172. 'commonCurrencies': {
  173. 'BTCBEAR': 'BEAR',
  174. 'BTCBULL': 'BULL',
  175. },
  176. })
  177. def fetch_currencies(self, params={}):
  178. response = self.publicGetCoininfo(params)
  179. #
  180. # {
  181. # "result":"true",
  182. # "coins":[
  183. # {
  184. # "CNYX":{
  185. # "delisted":0,
  186. # "withdraw_disabled":1,
  187. # "withdraw_delayed":0,
  188. # "deposit_disabled":0,
  189. # "trade_disabled":0
  190. # }
  191. # },
  192. # {
  193. # "USDT_ETH":{
  194. # "delisted":0,
  195. # "withdraw_disabled":1,
  196. # "withdraw_delayed":0,
  197. # "deposit_disabled":0,
  198. # "trade_disabled":1
  199. # }
  200. # }
  201. # ]
  202. # }
  203. #
  204. coins = self.safe_value(response, 'coins')
  205. if not coins:
  206. raise ExchangeError(self.id + ' fetchCurrencies got an unrecognized response')
  207. result = {}
  208. for i in range(0, len(coins)):
  209. coin = coins[i]
  210. ids = list(coin.keys())
  211. for j in range(0, len(ids)):
  212. id = ids[j]
  213. currency = coin[id]
  214. code = self.safe_currency_code(id)
  215. delisted = self.safe_value(currency, 'delisted', 0)
  216. withdrawDisabled = self.safe_value(currency, 'withdraw_disabled', 0)
  217. depositDisabled = self.safe_value(currency, 'deposit_disabled', 0)
  218. tradeDisabled = self.safe_value(currency, 'trade_disabled', 0)
  219. listed = (delisted == 0)
  220. withdrawEnabled = (withdrawDisabled == 0)
  221. depositEnabled = (depositDisabled == 0)
  222. tradeEnabled = (tradeDisabled == 0)
  223. active = listed and withdrawEnabled and depositEnabled and tradeEnabled
  224. result[code] = {
  225. 'id': id,
  226. 'code': code,
  227. 'active': active,
  228. 'info': currency,
  229. 'name': None,
  230. 'fee': None,
  231. 'precision': None,
  232. 'limits': {
  233. 'amount': {
  234. 'min': None,
  235. 'max': None,
  236. },
  237. 'price': {
  238. 'min': None,
  239. 'max': None,
  240. },
  241. 'cost': {
  242. 'min': None,
  243. 'max': None,
  244. },
  245. 'withdraw': {
  246. 'min': None,
  247. 'max': None,
  248. },
  249. },
  250. }
  251. return result
  252. def fetch_markets(self, params={}):
  253. response = self.publicGetMarketinfo(params)
  254. #
  255. # {
  256. # "result":"true",
  257. # "pairs":[
  258. # {
  259. # "usdt_cnyx":{
  260. # "decimal_places":3,
  261. # "amount_decimal_places":3,
  262. # "min_amount":1,
  263. # "min_amount_a":1,
  264. # "min_amount_b":3,
  265. # "fee":0.02,
  266. # "trade_disabled":0,
  267. # "buy_disabled":0,
  268. # "sell_disabled":0
  269. # }
  270. # },
  271. # ]
  272. # }
  273. #
  274. markets = self.safe_value(response, 'pairs')
  275. if not markets:
  276. raise ExchangeError(self.id + ' fetchMarkets got an unrecognized response')
  277. result = []
  278. for i in range(0, len(markets)):
  279. market = markets[i]
  280. keys = list(market.keys())
  281. id = self.safe_string(keys, 0)
  282. details = market[id]
  283. # all of their symbols are separated with an underscore
  284. # but not boe_eth_eth(BOE_ETH/ETH) which has two underscores
  285. # https://github.com/ccxt/ccxt/issues/4894
  286. parts = id.split('_')
  287. numParts = len(parts)
  288. baseId = parts[0]
  289. quoteId = parts[1]
  290. if numParts > 2:
  291. baseId = parts[0] + '_' + parts[1]
  292. quoteId = parts[2]
  293. base = self.safe_currency_code(baseId)
  294. quote = self.safe_currency_code(quoteId)
  295. symbol = base + '/' + quote
  296. precision = {
  297. 'amount': self.safe_integer(details, 'amount_decimal_places'),
  298. 'price': self.safe_integer(details, 'decimal_places'),
  299. }
  300. amountLimits = {
  301. 'min': self.safe_float(details, 'min_amount'),
  302. 'max': None,
  303. }
  304. priceLimits = {
  305. 'min': math.pow(10, -precision['price']),
  306. 'max': None,
  307. }
  308. defaultCost = amountLimits['min'] * priceLimits['min']
  309. minCost = self.safe_float(self.options['limits']['cost']['min'], quote, defaultCost)
  310. costLimits = {
  311. 'min': minCost,
  312. 'max': None,
  313. }
  314. limits = {
  315. 'amount': amountLimits,
  316. 'price': priceLimits,
  317. 'cost': costLimits,
  318. }
  319. disabled = self.safe_value(details, 'trade_disabled')
  320. active = not disabled
  321. uppercaseId = id.upper()
  322. fee = self.safe_float(details, 'fee')
  323. result.append({
  324. 'id': id,
  325. 'uppercaseId': uppercaseId,
  326. 'symbol': symbol,
  327. 'base': base,
  328. 'quote': quote,
  329. 'baseId': baseId,
  330. 'quoteId': quoteId,
  331. 'info': market,
  332. 'active': active,
  333. 'maker': fee / 100,
  334. 'taker': fee / 100,
  335. 'precision': precision,
  336. 'limits': limits,
  337. })
  338. return result
  339. def fetch_balance(self, params={}):
  340. self.load_markets()
  341. response = self.privatePostBalances(params)
  342. result = {'info': response}
  343. available = self.safe_value(response, 'available', {})
  344. if isinstance(available, list):
  345. available = {}
  346. locked = self.safe_value(response, 'locked', {})
  347. currencyIds = list(available.keys())
  348. for i in range(0, len(currencyIds)):
  349. currencyId = currencyIds[i]
  350. code = self.safe_currency_code(currencyId)
  351. account = self.account()
  352. account['free'] = self.safe_float(available, currencyId)
  353. account['used'] = self.safe_float(locked, currencyId)
  354. result[code] = account
  355. return self.parse_balance(result)
  356. def fetch_order_book(self, symbol, limit=None, params={}):
  357. self.load_markets()
  358. request = {
  359. 'id': self.market_id(symbol),
  360. }
  361. response = self.publicGetOrderBookId(self.extend(request, params))
  362. return self.parse_order_book(response)
  363. def parse_ohlcv(self, ohlcv, market=None):
  364. # they return [Timestamp, Volume, Close, High, Low, Open]
  365. return [
  366. self.safe_integer(ohlcv, 0), # t
  367. self.safe_float(ohlcv, 5), # o
  368. self.safe_float(ohlcv, 3), # h
  369. self.safe_float(ohlcv, 4), # l
  370. self.safe_float(ohlcv, 2), # c
  371. self.safe_float(ohlcv, 1), # v
  372. ]
  373. def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
  374. self.load_markets()
  375. market = self.market(symbol)
  376. request = {
  377. 'id': market['id'],
  378. 'group_sec': self.timeframes[timeframe],
  379. }
  380. # max limit = 1001
  381. if limit is not None:
  382. periodDurationInSeconds = self.parse_timeframe(timeframe)
  383. hours = int((periodDurationInSeconds * limit) / 3600)
  384. request['range_hour'] = max(0, hours - 1)
  385. response = self.publicGetCandlestick2Id(self.extend(request, params))
  386. #
  387. # {
  388. # "elapsed": "15ms",
  389. # "result": "true",
  390. # "data": [
  391. # ["1553930820000", "1.005299", "4081.05", "4086.18", "4081.05", "4086.18"],
  392. # ["1553930880000", "0.110923277", "4095.2", "4095.23", "4091.15", "4091.15"],
  393. # ...
  394. # ["1553934420000", "0", "4089.42", "4089.42", "4089.42", "4089.42"],
  395. # ]
  396. # }
  397. #
  398. data = self.safe_value(response, 'data', [])
  399. return self.parse_ohlcvs(data, market, timeframe, since, limit)
  400. def parse_ticker(self, ticker, market=None):
  401. timestamp = self.milliseconds()
  402. symbol = None
  403. if market:
  404. symbol = market['symbol']
  405. last = self.safe_float(ticker, 'last')
  406. percentage = self.safe_float(ticker, 'percentChange')
  407. open = None
  408. change = None
  409. average = None
  410. if (last is not None) and (percentage is not None):
  411. relativeChange = percentage / 100
  412. open = last / self.sum(1, relativeChange)
  413. change = last - open
  414. average = self.sum(last, open) / 2
  415. open = self.safe_float(ticker, 'open', open)
  416. change = self.safe_float(ticker, 'change', change)
  417. return {
  418. 'symbol': symbol,
  419. 'timestamp': timestamp,
  420. 'datetime': self.iso8601(timestamp),
  421. 'high': self.safe_float_2(ticker, 'high24hr', 'high'),
  422. 'low': self.safe_float_2(ticker, 'low24hr', 'low'),
  423. 'bid': self.safe_float(ticker, 'highestBid'),
  424. 'bidVolume': None,
  425. 'ask': self.safe_float(ticker, 'lowestAsk'),
  426. 'askVolume': None,
  427. 'vwap': None,
  428. 'open': open,
  429. 'close': last,
  430. 'last': last,
  431. 'previousClose': None,
  432. 'change': change,
  433. 'percentage': percentage,
  434. 'average': average,
  435. 'baseVolume': self.safe_float(ticker, 'quoteVolume'), # gateio has them reversed
  436. 'quoteVolume': self.safe_float(ticker, 'baseVolume'),
  437. 'info': ticker,
  438. }
  439. def fetch_tickers(self, symbols=None, params={}):
  440. self.load_markets()
  441. response = self.publicGetTickers(params)
  442. result = {}
  443. ids = list(response.keys())
  444. for i in range(0, len(ids)):
  445. id = ids[i]
  446. baseId, quoteId = id.split('_')
  447. base = baseId.upper()
  448. quote = quoteId.upper()
  449. base = self.safe_currency_code(base)
  450. quote = self.safe_currency_code(quote)
  451. symbol = base + '/' + quote
  452. market = None
  453. if symbol in self.markets:
  454. market = self.markets[symbol]
  455. if id in self.markets_by_id:
  456. market = self.markets_by_id[id]
  457. result[symbol] = self.parse_ticker(response[id], market)
  458. return self.filter_by_array(result, 'symbol', symbols)
  459. def fetch_ticker(self, symbol, params={}):
  460. self.load_markets()
  461. market = self.market(symbol)
  462. ticker = self.publicGetTickerId(self.extend({
  463. 'id': market['id'],
  464. }, params))
  465. return self.parse_ticker(ticker, market)
  466. def parse_trade(self, trade, market=None):
  467. # {
  468. # "tradeID": 3175762,
  469. # "date": "2017-08-25 07:24:28",
  470. # "type": "sell",
  471. # "rate": 29011,
  472. # "amount": 0.0019,
  473. # "total": 55.1209,
  474. # "fee": "0",
  475. # "fee_coin": "btc",
  476. # "gt_fee":"0",
  477. # "point_fee":"0.1213",
  478. # },
  479. timestamp = self.safe_timestamp_2(trade, 'timestamp', 'time_unix')
  480. timestamp = self.safe_timestamp(trade, 'time', timestamp)
  481. id = self.safe_string_2(trade, 'tradeID', 'id')
  482. # take either of orderid or orderId
  483. orderId = self.safe_string_2(trade, 'orderid', 'orderNumber')
  484. price = self.safe_float_2(trade, 'rate', 'price')
  485. amount = self.safe_float(trade, 'amount')
  486. type = self.safe_string(trade, 'type')
  487. takerOrMaker = self.safe_string(trade, 'role')
  488. cost = None
  489. if price is not None:
  490. if amount is not None:
  491. cost = price * amount
  492. symbol = None
  493. if market is not None:
  494. symbol = market['symbol']
  495. fee = None
  496. feeCurrency = self.safe_currency_code(self.safe_string(trade, 'fee_coin'))
  497. feeCost = self.safe_float(trade, 'point_fee')
  498. if (feeCost is None) or (feeCost == 0):
  499. feeCost = self.safe_float(trade, 'gt_fee')
  500. if (feeCost is None) or (feeCost == 0):
  501. feeCost = self.safe_float(trade, 'fee')
  502. else:
  503. feeCurrency = self.safe_currency_code('GT')
  504. else:
  505. feeCurrency = self.safe_currency_code('POINT')
  506. if feeCost is not None:
  507. fee = {
  508. 'cost': feeCost,
  509. 'currency': feeCurrency,
  510. }
  511. return {
  512. 'id': id,
  513. 'info': trade,
  514. 'timestamp': timestamp,
  515. 'datetime': self.iso8601(timestamp),
  516. 'symbol': symbol,
  517. 'order': orderId,
  518. 'type': None,
  519. 'side': type,
  520. 'takerOrMaker': takerOrMaker,
  521. 'price': price,
  522. 'amount': amount,
  523. 'cost': cost,
  524. 'fee': fee,
  525. }
  526. def fetch_trades(self, symbol, since=None, limit=None, params={}):
  527. self.load_markets()
  528. market = self.market(symbol)
  529. request = {
  530. 'id': market['id'],
  531. }
  532. method = None
  533. if 'tid' in params:
  534. method = 'publicGetTradeHistoryIdTid'
  535. else:
  536. method = 'publicGetTradeHistoryId'
  537. response = getattr(self, method)(self.extend(request, params))
  538. return self.parse_trades(response['data'], market, since, limit)
  539. def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
  540. response = self.privatePostOpenOrders(params)
  541. return self.parse_orders(response['orders'], None, since, limit)
  542. def fetch_order(self, id, symbol=None, params={}):
  543. self.load_markets()
  544. request = {
  545. 'orderNumber': id,
  546. 'currencyPair': self.market_id(symbol),
  547. }
  548. response = self.privatePostGetOrder(self.extend(request, params))
  549. return self.parse_order(response['order'])
  550. def parse_order_status(self, status):
  551. statuses = {
  552. 'cancelled': 'canceled',
  553. # 'closed': 'closed', # these two statuses aren't actually needed
  554. # 'open': 'open', # as they are mapped one-to-one
  555. }
  556. return self.safe_string(statuses, status, status)
  557. def parse_order(self, order, market=None):
  558. #
  559. # createOrder
  560. #
  561. # {
  562. # "fee": "0 ZEC",
  563. # "code": 0,
  564. # "rate": "0.0055",
  565. # "side": 2,
  566. # "type": "buy",
  567. # "ctime": 1586460839.138,
  568. # "market": "ZEC_BTC",
  569. # "result": "true",
  570. # "status": "open",
  571. # "iceberg": "0",
  572. # "message": "Success",
  573. # "feeValue": "0",
  574. # "filledRate": "0.005500000",
  575. # "leftAmount": "0.60607456",
  576. # "feeCurrency": "ZEC",
  577. # "orderNumber": 10755887009,
  578. # "filledAmount": "0",
  579. # "feePercentage": 0.002,
  580. # "initialAmount": "0.60607456"
  581. # }
  582. #
  583. # {
  584. # 'amount': '0.00000000',
  585. # 'currencyPair': 'xlm_usdt',
  586. # 'fee': '0.0113766632239302 USDT',
  587. # 'feeCurrency': 'USDT',
  588. # 'feePercentage': 0.18,
  589. # 'feeValue': '0.0113766632239302',
  590. # 'filledAmount': '30.14004987',
  591. # 'filledRate': 0.2097,
  592. # 'initialAmount': '30.14004987',
  593. # 'initialRate': '0.2097',
  594. # 'left': 0,
  595. # 'orderNumber': '998307286',
  596. # 'rate': '0.2097',
  597. # 'status': 'closed',
  598. # 'timestamp': 1531158583,
  599. # 'type': 'sell'
  600. # }
  601. #
  602. # {
  603. # "orderNumber": 10802237760,
  604. # "orderType": 1,
  605. # "type": "buy",
  606. # "rate": "0.54250000",
  607. # "amount": "45.55638518",
  608. # "total": "24.71433896",
  609. # "initialRate": "0.54250000",
  610. # "initialAmount": "45.55638518",
  611. # "filledRate": "0.54250000",
  612. # "filledAmount": "0",
  613. # "currencyPair": "nano_usdt",
  614. # "timestamp": 1586556143,
  615. # "status": "open"
  616. # }
  617. #
  618. id = self.safe_string_2(order, 'orderNumber', 'id')
  619. symbol = None
  620. marketId = self.safe_string(order, 'currencyPair')
  621. if marketId in self.markets_by_id:
  622. market = self.markets_by_id[marketId]
  623. if market is not None:
  624. symbol = market['symbol']
  625. timestamp = self.safe_timestamp_2(order, 'timestamp', 'ctime')
  626. lastTradeTimestamp = self.safe_timestamp(order, 'mtime')
  627. status = self.parse_order_status(self.safe_string(order, 'status'))
  628. side = self.safe_string(order, 'type')
  629. # handling for order.update messages
  630. if side == '1':
  631. side = 'sell'
  632. elif side == '2':
  633. side = 'buy'
  634. price = self.safe_float_2(order, 'initialRate', 'rate')
  635. average = self.safe_float(order, 'filledRate')
  636. amount = self.safe_float_2(order, 'initialAmount', 'amount')
  637. filled = self.safe_float(order, 'filledAmount')
  638. # In the order status response, self field has a different name.
  639. remaining = self.safe_float_2(order, 'leftAmount', 'left')
  640. if remaining is None:
  641. remaining = amount - filled
  642. feeCost = self.safe_float(order, 'feeValue')
  643. feeCurrencyId = self.safe_string(order, 'feeCurrency')
  644. feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
  645. feeRate = self.safe_float(order, 'feePercentage')
  646. if feeRate is not None:
  647. feeRate = feeRate / 100
  648. return {
  649. 'id': id,
  650. 'clientOrderId': None,
  651. 'datetime': self.iso8601(timestamp),
  652. 'timestamp': timestamp,
  653. 'lastTradeTimestamp': lastTradeTimestamp,
  654. 'status': status,
  655. 'symbol': symbol,
  656. 'type': 'limit',
  657. 'side': side,
  658. 'price': price,
  659. 'cost': None,
  660. 'amount': amount,
  661. 'filled': filled,
  662. 'remaining': remaining,
  663. 'average': average,
  664. 'trades': None,
  665. 'fee': {
  666. 'cost': feeCost,
  667. 'currency': feeCurrencyCode,
  668. 'rate': feeRate,
  669. },
  670. 'info': order,
  671. }
  672. def create_order(self, symbol, type, side, amount, price=None, params={}):
  673. if type == 'market':
  674. raise ExchangeError(self.id + ' allows limit orders only')
  675. self.load_markets()
  676. method = 'privatePost' + self.capitalize(side)
  677. market = self.market(symbol)
  678. request = {
  679. 'currencyPair': market['id'],
  680. 'rate': price,
  681. 'amount': amount,
  682. }
  683. response = getattr(self, method)(self.extend(request, params))
  684. return self.parse_order(self.extend({
  685. 'status': 'open',
  686. 'type': side,
  687. 'initialAmount': amount,
  688. }, response), market)
  689. def cancel_order(self, id, symbol=None, params={}):
  690. if symbol is None:
  691. raise ArgumentsRequired(self.id + ' cancelOrder requires symbol argument')
  692. self.load_markets()
  693. request = {
  694. 'orderNumber': id,
  695. 'currencyPair': self.market_id(symbol),
  696. }
  697. return self.privatePostCancelOrder(self.extend(request, params))
  698. def query_deposit_address(self, method, code, params={}):
  699. self.load_markets()
  700. currency = self.currency(code)
  701. method = 'privatePost' + method + 'Address'
  702. request = {
  703. 'currency': currency['id'],
  704. }
  705. response = getattr(self, method)(self.extend(request, params))
  706. address = self.safe_string(response, 'addr')
  707. tag = None
  708. if (address is not None) and (address.find('address') >= 0):
  709. raise InvalidAddress(self.id + ' queryDepositAddress ' + address)
  710. if code == 'XRP':
  711. parts = address.split(' ')
  712. address = parts[0]
  713. tag = parts[1]
  714. return {
  715. 'currency': currency,
  716. 'address': address,
  717. 'tag': tag,
  718. 'info': response,
  719. }
  720. def create_deposit_address(self, code, params={}):
  721. return self.query_deposit_address('New', code, params)
  722. def fetch_deposit_address(self, code, params={}):
  723. return self.query_deposit_address('Deposit', code, params)
  724. def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
  725. self.load_markets()
  726. market = None
  727. if symbol is not None:
  728. market = self.market(symbol)
  729. response = self.privatePostOpenOrders(params)
  730. return self.parse_orders(response['orders'], market, since, limit)
  731. def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
  732. if symbol is None:
  733. raise ArgumentsRequired(self.id + ' fetchMyTrades requires a symbol argument')
  734. self.load_markets()
  735. market = self.market(symbol)
  736. request = {
  737. 'currencyPair': market['id'],
  738. 'orderNumber': id,
  739. }
  740. response = self.privatePostTradeHistory(self.extend(request, params))
  741. return self.parse_trades(response['trades'], market, since, limit)
  742. def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
  743. if symbol is None:
  744. raise ArgumentsRequired(self.id + ' fetchMyTrades requires symbol argument')
  745. self.load_markets()
  746. market = self.market(symbol)
  747. request = {
  748. 'currencyPair': market['id'],
  749. }
  750. response = self.privatePostTradeHistory(self.extend(request, params))
  751. return self.parse_trades(response['trades'], market, since, limit)
  752. def withdraw(self, code, amount, address, tag=None, params={}):
  753. self.check_address(address)
  754. self.load_markets()
  755. currency = self.currency(code)
  756. request = {
  757. 'currency': currency['id'],
  758. 'amount': amount,
  759. 'address': address, # Address must exist in you AddressBook in security settings
  760. }
  761. if tag is not None:
  762. request['address'] += ' ' + tag
  763. response = self.privatePostWithdraw(self.extend(request, params))
  764. return {
  765. 'info': response,
  766. 'id': None,
  767. }
  768. def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
  769. prefix = (api + '/') if (api == 'private') else ''
  770. url = self.urls['api'][api] + self.version + '/1/' + prefix + self.implode_params(path, params)
  771. query = self.omit(params, self.extract_params(path))
  772. if api == 'public':
  773. if query:
  774. url += '?' + self.urlencode(query)
  775. else:
  776. self.check_required_credentials()
  777. nonce = self.nonce()
  778. request = {'nonce': nonce}
  779. body = self.urlencode(self.extend(request, query))
  780. signature = self.hmac(self.encode(body), self.encode(self.secret), hashlib.sha512)
  781. headers = {
  782. 'Key': self.apiKey,
  783. 'Sign': signature,
  784. 'Content-Type': 'application/x-www-form-urlencoded',
  785. }
  786. return {'url': url, 'method': method, 'body': body, 'headers': headers}
  787. def fetch_transactions_by_type(self, type=None, code=None, since=None, limit=None, params={}):
  788. self.load_markets()
  789. request = {}
  790. if since is not None:
  791. request['start'] = since
  792. response = self.privatePostDepositsWithdrawals(self.extend(request, params))
  793. transactions = None
  794. if type is None:
  795. deposits = self.safe_value(response, 'deposits', [])
  796. withdrawals = self.safe_value(response, 'withdraws', [])
  797. transactions = self.array_concat(deposits, withdrawals)
  798. else:
  799. transactions = self.safe_value(response, type, [])
  800. currency = None
  801. if code is not None:
  802. currency = self.currency(code)
  803. return self.parse_transactions(transactions, currency, since, limit)
  804. def fetch_transactions(self, code=None, since=None, limit=None, params={}):
  805. return self.fetch_transactions_by_type(None, code, since, limit, params)
  806. def fetch_deposits(self, code=None, since=None, limit=None, params={}):
  807. return self.fetch_transactions_by_type('deposits', code, since, limit, params)
  808. def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
  809. return self.fetch_transactions_by_type('withdraws', code, since, limit, params)
  810. def parse_transaction(self, transaction, currency=None):
  811. #
  812. # deposit
  813. #
  814. # {
  815. # 'id': 'd16520849',
  816. # 'currency': 'NEO',
  817. # 'address': False,
  818. # 'amount': '1',
  819. # 'txid': '01acf6b8ce4d24a....',
  820. # 'timestamp': '1553125968',
  821. # 'status': 'DONE',
  822. # 'type': 'deposit'
  823. # }
  824. #
  825. # withdrawal
  826. #
  827. # {
  828. # 'id': 'w5864259',
  829. # 'currency': 'ETH',
  830. # 'address': '0x72632f462....',
  831. # 'amount': '0.4947',
  832. # 'txid': '0x111167d120f736....',
  833. # 'timestamp': '1553123688',
  834. # 'status': 'DONE',
  835. # 'type': 'withdrawal'
  836. # }
  837. #
  838. currencyId = self.safe_string(transaction, 'currency')
  839. code = self.safe_currency_code(currencyId, currency)
  840. id = self.safe_string(transaction, 'id')
  841. txid = self.safe_string(transaction, 'txid')
  842. amount = self.safe_float(transaction, 'amount')
  843. address = self.safe_string(transaction, 'address')
  844. if address == 'false':
  845. address = None
  846. timestamp = self.safe_timestamp(transaction, 'timestamp')
  847. status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
  848. type = self.parse_transaction_type(id[0])
  849. return {
  850. 'info': transaction,
  851. 'id': id,
  852. 'txid': txid,
  853. 'currency': code,
  854. 'amount': amount,
  855. 'address': address,
  856. 'tag': None,
  857. 'status': status,
  858. 'type': type,
  859. 'timestamp': timestamp,
  860. 'datetime': self.iso8601(timestamp),
  861. 'fee': None,
  862. }
  863. def parse_transaction_status(self, status):
  864. statuses = {
  865. 'PEND': 'pending',
  866. 'REQUEST': 'pending',
  867. 'DMOVE': 'pending',
  868. 'CANCEL': 'failed',
  869. 'DONE': 'ok',
  870. }
  871. return self.safe_string(statuses, status, status)
  872. def parse_transaction_type(self, type):
  873. types = {
  874. 'd': 'deposit',
  875. 'w': 'withdrawal',
  876. }
  877. return self.safe_string(types, type, type)
  878. def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
  879. if response is None:
  880. return
  881. resultString = self.safe_string(response, 'result', '')
  882. if resultString != 'false':
  883. return
  884. errorCode = self.safe_string(response, 'code')
  885. message = self.safe_string(response, 'message', body)
  886. if errorCode is not None:
  887. feedback = self.safe_string(self.exceptions['errorCodeNames'], errorCode, message)
  888. self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)