/python/ccxt/bybit.py

https://github.com/kroitor/ccxt · Python · 2053 lines · 1069 code · 40 blank · 944 comment · 143 complexity · da22c282b4e26a40469107db0765476d 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. from ccxt.base.errors import ExchangeError
  6. from ccxt.base.errors import AuthenticationError
  7. from ccxt.base.errors import PermissionDenied
  8. from ccxt.base.errors import ArgumentsRequired
  9. from ccxt.base.errors import BadRequest
  10. from ccxt.base.errors import InsufficientFunds
  11. from ccxt.base.errors import InvalidOrder
  12. from ccxt.base.errors import OrderNotFound
  13. from ccxt.base.errors import RateLimitExceeded
  14. from ccxt.base.errors import InvalidNonce
  15. from ccxt.base.decimal_to_precision import TICK_SIZE
  16. class bybit(Exchange):
  17. def describe(self):
  18. return self.deep_extend(super(bybit, self).describe(), {
  19. 'id': 'bybit',
  20. 'name': 'Bybit',
  21. 'countries': ['VG'], # British Virgin Islands
  22. 'version': 'v2',
  23. 'userAgent': None,
  24. 'rateLimit': 100,
  25. 'has': {
  26. 'cancelOrder': True,
  27. 'CORS': True,
  28. 'cancelAllOrders': True,
  29. 'createOrder': True,
  30. 'editOrder': True,
  31. 'fetchBalance': True,
  32. 'fetchClosedOrders': True,
  33. 'fetchDeposits': True,
  34. 'fetchLedger': True,
  35. 'fetchMarkets': True,
  36. 'fetchMyTrades': True,
  37. 'fetchOHLCV': True,
  38. 'fetchOpenOrders': True,
  39. 'fetchOrder': True,
  40. 'fetchOrderBook': True,
  41. 'fetchOrders': True,
  42. 'fetchOrderTrades': True,
  43. 'fetchTicker': True,
  44. 'fetchTickers': True,
  45. 'fetchTime': True,
  46. 'fetchTrades': True,
  47. 'fetchTransactions': False,
  48. 'fetchWithdrawals': True,
  49. },
  50. 'timeframes': {
  51. '1m': '1',
  52. '3m': '3',
  53. '5m': '5',
  54. '15m': '15',
  55. '30m': '30',
  56. '1h': '60',
  57. '2h': '120',
  58. '4h': '240',
  59. '6h': '360',
  60. '12h': '720',
  61. '1d': 'D',
  62. '1w': 'W',
  63. '1M': 'M',
  64. '1y': 'Y',
  65. },
  66. 'urls': {
  67. 'test': 'https://api-testnet.bybit.com',
  68. 'logo': 'https://user-images.githubusercontent.com/51840849/76547799-daff5b80-649e-11ea-87fb-3be9bac08954.jpg',
  69. 'api': 'https://api.bybit.com',
  70. 'www': 'https://www.bybit.com',
  71. 'doc': [
  72. 'https://bybit-exchange.github.io/docs/inverse/',
  73. 'https://bybit-exchange.github.io/docs/linear/',
  74. 'https://github.com/bybit-exchange',
  75. ],
  76. 'fees': 'https://help.bybit.com/hc/en-us/articles/360039261154',
  77. 'referral': 'https://www.bybit.com/app/register?ref=X7Prm',
  78. },
  79. 'api': {
  80. 'public': {
  81. 'get': [
  82. 'orderBook/L2',
  83. 'kline/list',
  84. 'tickers',
  85. 'trading-records',
  86. 'symbols',
  87. 'time',
  88. 'announcement',
  89. ],
  90. },
  91. 'private': {
  92. 'get': [
  93. 'order',
  94. 'stop-order',
  95. 'position/list',
  96. 'wallet/balance',
  97. 'execution/list',
  98. ],
  99. 'post': [
  100. 'order/create',
  101. 'order/cancel',
  102. 'order/cancelAll',
  103. 'stop-order/cancelAll',
  104. ],
  105. },
  106. 'openapi': {
  107. 'get': [
  108. 'order/list',
  109. 'stop-order/list',
  110. 'wallet/risk-limit/list',
  111. 'wallet/risk-limit',
  112. 'funding/prev-funding-rate',
  113. 'funding/prev-funding',
  114. 'funding/predicted-funding',
  115. 'api-key',
  116. 'wallet/fund/records',
  117. 'wallet/withdraw/list',
  118. ],
  119. 'post': [
  120. 'order/replace',
  121. 'stop-order/create',
  122. 'stop-order/cancel',
  123. 'stop-order/replace',
  124. 'position/trading-stop',
  125. ],
  126. },
  127. 'publicLinear': {
  128. 'get': [
  129. 'kline',
  130. 'recent-trading-records',
  131. 'funding/prev-funding-rate',
  132. 'mark-price-kline',
  133. ],
  134. },
  135. 'privateLinear': {
  136. 'get': [
  137. 'order/list',
  138. 'order/search',
  139. 'stop-order/list',
  140. 'stop-order/search',
  141. 'position/list',
  142. 'trade/execution/list',
  143. 'trade/closed-pnl/list',
  144. 'risk-limit',
  145. 'funding/prev-funding',
  146. 'funding/predicted-funding',
  147. ],
  148. 'post': [
  149. 'order/create',
  150. 'order/cancel',
  151. 'order/cancelAll',
  152. 'order/replace',
  153. 'stop-order/create',
  154. 'stop-order/cancel',
  155. 'stop-order/cancelAll',
  156. 'stop-order/replace',
  157. 'position/switch-isolated',
  158. 'position/set-auto-add-margin',
  159. 'position/set-leverage',
  160. 'position/trading-stop',
  161. 'position/add-margin',
  162. ],
  163. },
  164. 'position': {
  165. 'post': [
  166. 'change-position-margin',
  167. ],
  168. },
  169. 'user': {
  170. 'get': [
  171. 'leverage',
  172. ],
  173. 'post': [
  174. 'leverage/save',
  175. ],
  176. },
  177. },
  178. 'httpExceptions': {
  179. '403': RateLimitExceeded, # Forbidden -- You request too many times
  180. },
  181. 'exceptions': {
  182. 'exact': {
  183. '10001': BadRequest, # parameter error
  184. '10002': InvalidNonce, # request expired, check your timestamp and recv_window
  185. '10003': AuthenticationError, # Invalid apikey
  186. '10004': AuthenticationError, # invalid sign
  187. '10005': PermissionDenied, # permission denied for current apikey
  188. '10006': RateLimitExceeded, # too many requests
  189. '10007': AuthenticationError, # api_key not found in your request parameters
  190. '10010': PermissionDenied, # request ip mismatch
  191. '10017': BadRequest, # request path not found or request method is invalid
  192. '20001': OrderNotFound, # Order not exists
  193. '20003': InvalidOrder, # missing parameter side
  194. '20004': InvalidOrder, # invalid parameter side
  195. '20005': InvalidOrder, # missing parameter symbol
  196. '20006': InvalidOrder, # invalid parameter symbol
  197. '20007': InvalidOrder, # missing parameter order_type
  198. '20008': InvalidOrder, # invalid parameter order_type
  199. '20009': InvalidOrder, # missing parameter qty
  200. '20010': InvalidOrder, # qty must be greater than 0
  201. '20011': InvalidOrder, # qty must be an integer
  202. '20012': InvalidOrder, # qty must be greater than zero and less than 1 million
  203. '20013': InvalidOrder, # missing parameter price
  204. '20014': InvalidOrder, # price must be greater than 0
  205. '20015': InvalidOrder, # missing parameter time_in_force
  206. '20016': InvalidOrder, # invalid value for parameter time_in_force
  207. '20017': InvalidOrder, # missing parameter order_id
  208. '20018': InvalidOrder, # invalid date format
  209. '20019': InvalidOrder, # missing parameter stop_px
  210. '20020': InvalidOrder, # missing parameter base_price
  211. '20021': InvalidOrder, # missing parameter stop_order_id
  212. '20022': BadRequest, # missing parameter leverage
  213. '20023': BadRequest, # leverage must be a number
  214. '20031': BadRequest, # leverage must be greater than zero
  215. '20070': BadRequest, # missing parameter margin
  216. '20071': BadRequest, # margin must be greater than zero
  217. '20084': BadRequest, # order_id or order_link_id is required
  218. '30001': BadRequest, # order_link_id is repeated
  219. '30003': InvalidOrder, # qty must be more than the minimum allowed
  220. '30004': InvalidOrder, # qty must be less than the maximum allowed
  221. '30005': InvalidOrder, # price exceeds maximum allowed
  222. '30007': InvalidOrder, # price exceeds minimum allowed
  223. '30008': InvalidOrder, # invalid order_type
  224. '30009': ExchangeError, # no position found
  225. '30010': InsufficientFunds, # insufficient wallet balance
  226. '30011': PermissionDenied, # operation not allowed as position is undergoing liquidation
  227. '30012': PermissionDenied, # operation not allowed as position is undergoing ADL
  228. '30013': PermissionDenied, # position is in liq or adl status
  229. '30014': InvalidOrder, # invalid closing order, qty should not greater than size
  230. '30015': InvalidOrder, # invalid closing order, side should be opposite
  231. '30016': ExchangeError, # TS and SL must be cancelled first while closing position
  232. '30017': InvalidOrder, # estimated fill price cannot be lower than current Buy liq_price
  233. '30018': InvalidOrder, # estimated fill price cannot be higher than current Sell liq_price
  234. '30019': InvalidOrder, # cannot attach TP/SL params for non-zero position when placing non-opening position order
  235. '30020': InvalidOrder, # position already has TP/SL params
  236. '30021': InvalidOrder, # cannot afford estimated position_margin
  237. '30022': InvalidOrder, # estimated buy liq_price cannot be higher than current mark_price
  238. '30023': InvalidOrder, # estimated sell liq_price cannot be lower than current mark_price
  239. '30024': InvalidOrder, # cannot set TP/SL/TS for zero-position
  240. '30025': InvalidOrder, # trigger price should bigger than 10% of last price
  241. '30026': InvalidOrder, # price too high
  242. '30027': InvalidOrder, # price set for Take profit should be higher than Last Traded Price
  243. '30028': InvalidOrder, # price set for Stop loss should be between Liquidation price and Last Traded Price
  244. '30029': InvalidOrder, # price set for Stop loss should be between Last Traded Price and Liquidation price
  245. '30030': InvalidOrder, # price set for Take profit should be lower than Last Traded Price
  246. '30031': InsufficientFunds, # insufficient available balance for order cost
  247. '30032': InvalidOrder, # order has been filled or cancelled
  248. '30033': RateLimitExceeded, # The number of stop orders exceeds maximum limit allowed
  249. '30034': OrderNotFound, # no order found
  250. '30035': RateLimitExceeded, # too fast to cancel
  251. '30036': ExchangeError, # the expected position value after order execution exceeds the current risk limit
  252. '30037': InvalidOrder, # order already cancelled
  253. '30041': ExchangeError, # no position found
  254. '30042': InsufficientFunds, # insufficient wallet balance
  255. '30043': PermissionDenied, # operation not allowed as position is undergoing liquidation
  256. '30044': PermissionDenied, # operation not allowed as position is undergoing AD
  257. '30045': PermissionDenied, # operation not allowed as position is not normal status
  258. '30049': InsufficientFunds, # insufficient available balance
  259. '30050': ExchangeError, # any adjustments made will trigger immediate liquidation
  260. '30051': ExchangeError, # due to risk limit, cannot adjust leverage
  261. '30052': ExchangeError, # leverage can not less than 1
  262. '30054': ExchangeError, # position margin is invalid
  263. '30057': ExchangeError, # requested quantity of contracts exceeds risk limit
  264. '30063': ExchangeError, # reduce-only rule not satisfied
  265. '30067': InsufficientFunds, # insufficient available balance
  266. '30068': ExchangeError, # exit value must be positive
  267. '34026': ExchangeError, # the limit is no change
  268. },
  269. 'broad': {
  270. 'unknown orderInfo': OrderNotFound, # {"ret_code":-1,"ret_msg":"unknown orderInfo","ext_code":"","ext_info":"","result":null,"time_now":"1584030414.005545","rate_limit_status":99,"rate_limit_reset_ms":1584030414003,"rate_limit":100}
  271. 'invalid api_key': AuthenticationError, # {"ret_code":10003,"ret_msg":"invalid api_key","ext_code":"","ext_info":"","result":null,"time_now":"1599547085.415797"}
  272. },
  273. },
  274. 'precisionMode': TICK_SIZE,
  275. 'options': {
  276. 'marketTypes': {
  277. 'BTC/USDT': 'linear',
  278. },
  279. 'code': 'BTC',
  280. 'fetchBalance': {
  281. 'code': 'BTC',
  282. },
  283. 'cancelAllOrders': {
  284. 'method': 'privatePostOrderCancelAll', # privatePostStopOrderCancelAll
  285. },
  286. 'recvWindow': 5 * 1000, # 5 sec default
  287. 'timeDifference': 0, # the difference between system clock and Binance clock
  288. 'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
  289. },
  290. 'fees': {
  291. 'trading': {
  292. 'tierBased': False,
  293. 'percentage': True,
  294. 'taker': 0.00075,
  295. 'maker': -0.00025,
  296. },
  297. 'funding': {
  298. 'tierBased': False,
  299. 'percentage': False,
  300. 'withdraw': {},
  301. 'deposit': {},
  302. },
  303. },
  304. })
  305. def nonce(self):
  306. return self.milliseconds() - self.options['timeDifference']
  307. def load_time_difference(self, params={}):
  308. serverTime = self.fetch_time(params)
  309. after = self.milliseconds()
  310. self.options['timeDifference'] = after - serverTime
  311. return self.options['timeDifference']
  312. def fetch_time(self, params={}):
  313. response = self.publicGetTime(params)
  314. #
  315. # {
  316. # ret_code: 0,
  317. # ret_msg: 'OK',
  318. # ext_code: '',
  319. # ext_info: '',
  320. # result: {},
  321. # time_now: '1583933682.448826'
  322. # }
  323. #
  324. return self.safe_timestamp(response, 'time_now')
  325. def fetch_markets(self, params={}):
  326. if self.options['adjustForTimeDifference']:
  327. self.load_time_difference()
  328. response = self.publicGetSymbols(params)
  329. #
  330. # {
  331. # ret_code: 0,
  332. # ret_msg: 'OK',
  333. # ext_code: '',
  334. # ext_info: '',
  335. # result: [
  336. # {
  337. # name: 'BTCUSD',
  338. # base_currency: 'BTC',
  339. # quote_currency: 'USD',
  340. # price_scale: 2,
  341. # taker_fee: '0.00075',
  342. # maker_fee: '-0.00025',
  343. # leverage_filter: {min_leverage: 1, max_leverage: 100, leverage_step: '0.01'},
  344. # price_filter: {min_price: '0.5', max_price: '999999.5', tick_size: '0.5'},
  345. # lot_size_filter: {max_trading_qty: 1000000, min_trading_qty: 1, qty_step: 1}
  346. # },
  347. # ],
  348. # time_now: '1583930495.454196'
  349. # }
  350. #
  351. markets = self.safe_value(response, 'result', [])
  352. options = self.safe_value(self.options, 'fetchMarkets', {})
  353. linearQuoteCurrencies = self.safe_value(options, 'linear', {'USDT': True})
  354. result = []
  355. for i in range(0, len(markets)):
  356. market = markets[i]
  357. id = self.safe_string(market, 'name')
  358. baseId = self.safe_string(market, 'base_currency')
  359. quoteId = self.safe_string(market, 'quote_currency')
  360. base = self.safe_currency_code(baseId)
  361. quote = self.safe_currency_code(quoteId)
  362. linear = (quote in linearQuoteCurrencies)
  363. inverse = not linear
  364. symbol = base + '/' + quote
  365. baseQuote = base + quote
  366. if baseQuote != id:
  367. symbol = id
  368. lotSizeFilter = self.safe_value(market, 'lot_size_filter', {})
  369. priceFilter = self.safe_value(market, 'price_filter', {})
  370. precision = {
  371. 'amount': self.safe_float(lotSizeFilter, 'qty_step'),
  372. 'price': self.safe_float(priceFilter, 'tick_size'),
  373. }
  374. result.append({
  375. 'id': id,
  376. 'symbol': symbol,
  377. 'base': base,
  378. 'quote': quote,
  379. 'active': None,
  380. 'precision': precision,
  381. 'taker': self.safe_float(market, 'taker_fee'),
  382. 'maker': self.safe_float(market, 'maker_fee'),
  383. 'type': 'future',
  384. 'spot': False,
  385. 'future': True,
  386. 'option': False,
  387. 'linear': linear,
  388. 'inverse': inverse,
  389. 'limits': {
  390. 'amount': {
  391. 'min': self.safe_float(lotSizeFilter, 'min_trading_qty'),
  392. 'max': self.safe_float(lotSizeFilter, 'max_trading_qty'),
  393. },
  394. 'price': {
  395. 'min': self.safe_float(priceFilter, 'min_price'),
  396. 'max': self.safe_float(priceFilter, 'max_price'),
  397. },
  398. 'cost': {
  399. 'min': None,
  400. 'max': None,
  401. },
  402. },
  403. 'info': market,
  404. })
  405. return result
  406. def fetch_balance(self, params={}):
  407. self.load_markets()
  408. defaultCode = self.safe_value(self.options, 'code', 'BTC')
  409. options = self.safe_value(self.options, 'fetchBalance', {})
  410. code = self.safe_value(options, 'code', defaultCode)
  411. currency = self.currency(code)
  412. request = {
  413. 'coin': currency['id'],
  414. }
  415. response = self.privateGetWalletBalance(self.extend(request, params))
  416. #
  417. # {
  418. # ret_code: 0,
  419. # ret_msg: 'OK',
  420. # ext_code: '',
  421. # ext_info: '',
  422. # result: {
  423. # BTC: {
  424. # equity: 0,
  425. # available_balance: 0,
  426. # used_margin: 0,
  427. # order_margin: 0,
  428. # position_margin: 0,
  429. # occ_closing_fee: 0,
  430. # occ_funding_fee: 0,
  431. # wallet_balance: 0,
  432. # realised_pnl: 0,
  433. # unrealised_pnl: 0,
  434. # cum_realised_pnl: 0,
  435. # given_cash: 0,
  436. # service_cash: 0
  437. # }
  438. # },
  439. # time_now: '1583937810.370020',
  440. # rate_limit_status: 119,
  441. # rate_limit_reset_ms: 1583937810367,
  442. # rate_limit: 120
  443. # }
  444. #
  445. result = {
  446. 'info': response,
  447. }
  448. balances = self.safe_value(response, 'result', {})
  449. currencyIds = list(balances.keys())
  450. for i in range(0, len(currencyIds)):
  451. currencyId = currencyIds[i]
  452. balance = balances[currencyId]
  453. code = self.safe_currency_code(currencyId)
  454. account = self.account()
  455. account['free'] = self.safe_float(balance, 'available_balance')
  456. account['used'] = self.safe_float(balance, 'used_margin')
  457. account['total'] = self.safe_float(balance, 'equity')
  458. result[code] = account
  459. return self.parse_balance(result)
  460. def parse_ticker(self, ticker, market=None):
  461. #
  462. # fetchTicker
  463. #
  464. # {
  465. # symbol: 'BTCUSD',
  466. # bid_price: '7680',
  467. # ask_price: '7680.5',
  468. # last_price: '7680.00',
  469. # last_tick_direction: 'MinusTick',
  470. # prev_price_24h: '7870.50',
  471. # price_24h_pcnt: '-0.024204',
  472. # high_price_24h: '8035.00',
  473. # low_price_24h: '7671.00',
  474. # prev_price_1h: '7780.00',
  475. # price_1h_pcnt: '-0.012853',
  476. # mark_price: '7683.27',
  477. # index_price: '7682.74',
  478. # open_interest: 188829147,
  479. # open_value: '23670.06',
  480. # total_turnover: '25744224.90',
  481. # turnover_24h: '102997.83',
  482. # total_volume: 225448878806,
  483. # volume_24h: 809919408,
  484. # funding_rate: '0.0001',
  485. # predicted_funding_rate: '0.0001',
  486. # next_funding_time: '2020-03-12T00:00:00Z',
  487. # countdown_hour: 7
  488. # }
  489. #
  490. timestamp = None
  491. marketId = self.safe_string(ticker, 'symbol')
  492. symbol = marketId
  493. if marketId in self.markets_by_id:
  494. market = self.markets_by_id[marketId]
  495. if (symbol is None) and (market is not None):
  496. symbol = market['symbol']
  497. last = self.safe_float(ticker, 'last_price')
  498. open = self.safe_float(ticker, 'prev_price_24h')
  499. percentage = self.safe_float(ticker, 'price_24h_pcnt')
  500. if percentage is not None:
  501. percentage *= 100
  502. change = None
  503. average = None
  504. if (last is not None) and (open is not None):
  505. change = last - open
  506. average = self.sum(open, last) / 2
  507. baseVolume = self.safe_float(ticker, 'turnover_24h')
  508. quoteVolume = self.safe_float(ticker, 'volume_24h')
  509. vwap = self.vwap(baseVolume, quoteVolume)
  510. return {
  511. 'symbol': symbol,
  512. 'timestamp': timestamp,
  513. 'datetime': self.iso8601(timestamp),
  514. 'high': self.safe_float(ticker, 'high_price_24h'),
  515. 'low': self.safe_float(ticker, 'low_price_24h'),
  516. 'bid': self.safe_float(ticker, 'bid_price'),
  517. 'bidVolume': None,
  518. 'ask': self.safe_float(ticker, 'ask_price'),
  519. 'askVolume': None,
  520. 'vwap': vwap,
  521. 'open': open,
  522. 'close': last,
  523. 'last': last,
  524. 'previousClose': None,
  525. 'change': change,
  526. 'percentage': percentage,
  527. 'average': average,
  528. 'baseVolume': baseVolume,
  529. 'quoteVolume': quoteVolume,
  530. 'info': ticker,
  531. }
  532. def fetch_ticker(self, symbol, params={}):
  533. self.load_markets()
  534. market = self.market(symbol)
  535. request = {
  536. 'symbol': market['id'],
  537. }
  538. response = self.publicGetTickers(self.extend(request, params))
  539. #
  540. # {
  541. # ret_code: 0,
  542. # ret_msg: 'OK',
  543. # ext_code: '',
  544. # ext_info: '',
  545. # result: [
  546. # {
  547. # symbol: 'BTCUSD',
  548. # bid_price: '7680',
  549. # ask_price: '7680.5',
  550. # last_price: '7680.00',
  551. # last_tick_direction: 'MinusTick',
  552. # prev_price_24h: '7870.50',
  553. # price_24h_pcnt: '-0.024204',
  554. # high_price_24h: '8035.00',
  555. # low_price_24h: '7671.00',
  556. # prev_price_1h: '7780.00',
  557. # price_1h_pcnt: '-0.012853',
  558. # mark_price: '7683.27',
  559. # index_price: '7682.74',
  560. # open_interest: 188829147,
  561. # open_value: '23670.06',
  562. # total_turnover: '25744224.90',
  563. # turnover_24h: '102997.83',
  564. # total_volume: 225448878806,
  565. # volume_24h: 809919408,
  566. # funding_rate: '0.0001',
  567. # predicted_funding_rate: '0.0001',
  568. # next_funding_time: '2020-03-12T00:00:00Z',
  569. # countdown_hour: 7
  570. # }
  571. # ],
  572. # time_now: '1583948195.818255'
  573. # }
  574. #
  575. result = self.safe_value(response, 'result', [])
  576. first = self.safe_value(result, 0)
  577. timestamp = self.safe_timestamp(response, 'time_now')
  578. ticker = self.parse_ticker(first, market)
  579. ticker['timestamp'] = timestamp
  580. ticker['datetime'] = self.iso8601(timestamp)
  581. return ticker
  582. def fetch_tickers(self, symbols=None, params={}):
  583. self.load_markets()
  584. response = self.publicGetTickers(params)
  585. #
  586. # {
  587. # ret_code: 0,
  588. # ret_msg: 'OK',
  589. # ext_code: '',
  590. # ext_info: '',
  591. # result: [
  592. # {
  593. # symbol: 'BTCUSD',
  594. # bid_price: '7680',
  595. # ask_price: '7680.5',
  596. # last_price: '7680.00',
  597. # last_tick_direction: 'MinusTick',
  598. # prev_price_24h: '7870.50',
  599. # price_24h_pcnt: '-0.024204',
  600. # high_price_24h: '8035.00',
  601. # low_price_24h: '7671.00',
  602. # prev_price_1h: '7780.00',
  603. # price_1h_pcnt: '-0.012853',
  604. # mark_price: '7683.27',
  605. # index_price: '7682.74',
  606. # open_interest: 188829147,
  607. # open_value: '23670.06',
  608. # total_turnover: '25744224.90',
  609. # turnover_24h: '102997.83',
  610. # total_volume: 225448878806,
  611. # volume_24h: 809919408,
  612. # funding_rate: '0.0001',
  613. # predicted_funding_rate: '0.0001',
  614. # next_funding_time: '2020-03-12T00:00:00Z',
  615. # countdown_hour: 7
  616. # }
  617. # ],
  618. # time_now: '1583948195.818255'
  619. # }
  620. #
  621. result = self.safe_value(response, 'result', [])
  622. tickers = {}
  623. for i in range(0, len(result)):
  624. ticker = self.parse_ticker(result[i])
  625. symbol = ticker['symbol']
  626. tickers[symbol] = ticker
  627. return self.filter_by_array(tickers, 'symbol', symbols)
  628. def parse_ohlcv(self, ohlcv, market=None):
  629. #
  630. # inverse perpetual BTC/USD
  631. #
  632. # {
  633. # symbol: 'BTCUSD',
  634. # interval: '1',
  635. # open_time: 1583952540,
  636. # open: '7760.5',
  637. # high: '7764',
  638. # low: '7757',
  639. # close: '7763.5',
  640. # volume: '1259766',
  641. # turnover: '162.32773718999994'
  642. # }
  643. #
  644. # linear perpetual BTC/USDT
  645. #
  646. # {
  647. # "id":143536,
  648. # "symbol":"BTCUSDT",
  649. # "period":"15",
  650. # "start_at":1587883500,
  651. # "volume":1.035,
  652. # "open":7540.5,
  653. # "high":7541,
  654. # "low":7540.5,
  655. # "close":7541
  656. # }
  657. #
  658. return [
  659. self.safe_timestamp_2(ohlcv, 'open_time', 'start_at'),
  660. self.safe_float(ohlcv, 'open'),
  661. self.safe_float(ohlcv, 'high'),
  662. self.safe_float(ohlcv, 'low'),
  663. self.safe_float(ohlcv, 'close'),
  664. self.safe_float_2(ohlcv, 'turnover', 'volume'),
  665. ]
  666. def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
  667. self.load_markets()
  668. market = self.market(symbol)
  669. request = {
  670. 'symbol': market['id'],
  671. 'interval': self.timeframes[timeframe],
  672. }
  673. duration = self.parse_timeframe(timeframe)
  674. now = self.seconds()
  675. if since is None:
  676. if limit is None:
  677. raise ArgumentsRequired(self.id + ' fetchOHLCV requires a since argument or a limit argument')
  678. else:
  679. request['from'] = now - limit * duration
  680. else:
  681. request['from'] = int(since / 1000)
  682. if limit is not None:
  683. request['limit'] = limit # max 200, default 200
  684. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  685. marketType = self.safe_string(marketTypes, symbol)
  686. method = 'publicLinearGetKline' if (marketType == 'linear') else 'publicGetKlineList'
  687. response = getattr(self, method)(self.extend(request, params))
  688. #
  689. # inverse perpetual BTC/USD
  690. #
  691. # {
  692. # ret_code: 0,
  693. # ret_msg: 'OK',
  694. # ext_code: '',
  695. # ext_info: '',
  696. # result: [
  697. # {
  698. # symbol: 'BTCUSD',
  699. # interval: '1',
  700. # open_time: 1583952540,
  701. # open: '7760.5',
  702. # high: '7764',
  703. # low: '7757',
  704. # close: '7763.5',
  705. # volume: '1259766',
  706. # turnover: '162.32773718999994'
  707. # },
  708. # ],
  709. # time_now: '1583953082.397330'
  710. # }
  711. #
  712. # linear perpetual BTC/USDT
  713. #
  714. # {
  715. # "ret_code":0,
  716. # "ret_msg":"OK",
  717. # "ext_code":"",
  718. # "ext_info":"",
  719. # "result":[
  720. # {
  721. # "id":143536,
  722. # "symbol":"BTCUSDT",
  723. # "period":"15",
  724. # "start_at":1587883500,
  725. # "volume":1.035,
  726. # "open":7540.5,
  727. # "high":7541,
  728. # "low":7540.5,
  729. # "close":7541
  730. # }
  731. # ],
  732. # "time_now":"1587884120.168077"
  733. # }
  734. #
  735. result = self.safe_value(response, 'result', {})
  736. return self.parse_ohlcvs(result, market, timeframe, since, limit)
  737. def parse_trade(self, trade, market=None):
  738. #
  739. # fetchTrades(public)
  740. #
  741. # {
  742. # id: 43785688,
  743. # symbol: 'BTCUSD',
  744. # price: 7786,
  745. # qty: 67,
  746. # side: 'Sell',
  747. # time: '2020-03-11T19:18:30.123Z'
  748. # }
  749. #
  750. # fetchMyTrades, fetchOrderTrades(private)
  751. #
  752. # {
  753. # "closed_size": 0,
  754. # "cross_seq": 277136382,
  755. # "exec_fee": "0.0000001",
  756. # "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68",
  757. # "exec_price": "8178.5",
  758. # "exec_qty": 1,
  759. # # the docs say the exec_time field is "abandoned" now
  760. # # the user should use "trade_time_ms"
  761. # "exec_time": "1571676941.70682",
  762. # "exec_type": "Trade", #Exec Type Enum
  763. # "exec_value": "0.00012227",
  764. # "fee_rate": "0.00075",
  765. # "last_liquidity_ind": "RemovedLiquidity", #Liquidity Enum
  766. # "leaves_qty": 0,
  767. # "nth_fill": 2,
  768. # "order_id": "7ad50cb1-9ad0-4f74-804b-d82a516e1029",
  769. # "order_link_id": "",
  770. # "order_price": "8178",
  771. # "order_qty": 1,
  772. # "order_type": "Market", #Order Type Enum
  773. # "side": "Buy", #Side Enum
  774. # "symbol": "BTCUSD", #Symbol Enum
  775. # "user_id": 1,
  776. # "trade_time_ms": 1577480599000
  777. # }
  778. #
  779. id = self.safe_string_2(trade, 'id', 'exec_id')
  780. symbol = None
  781. base = None
  782. marketId = self.safe_string(trade, 'symbol')
  783. amount = self.safe_float_2(trade, 'qty', 'exec_qty')
  784. cost = self.safe_float(trade, 'exec_value')
  785. price = self.safe_float_2(trade, 'price', 'exec_price')
  786. if marketId in self.markets_by_id:
  787. market = self.markets_by_id[marketId]
  788. symbol = market['symbol']
  789. base = market['base']
  790. if market is not None:
  791. if symbol is None:
  792. symbol = market['symbol']
  793. base = market['base']
  794. if cost is None:
  795. if amount is not None:
  796. if price is not None:
  797. cost = amount * price
  798. timestamp = self.parse8601(self.safe_string(trade, 'time'))
  799. if timestamp is None:
  800. timestamp = self.safe_integer(trade, 'trade_time_ms')
  801. side = self.safe_string_lower(trade, 'side')
  802. lastLiquidityInd = self.safe_string(trade, 'last_liquidity_ind')
  803. takerOrMaker = 'maker' if (lastLiquidityInd == 'AddedLiquidity') else 'taker'
  804. feeCost = self.safe_float(trade, 'exec_fee')
  805. fee = None
  806. if feeCost is not None:
  807. fee = {
  808. 'cost': feeCost,
  809. 'currency': base,
  810. 'rate': self.safe_float(trade, 'fee_rate'),
  811. }
  812. return {
  813. 'id': id,
  814. 'info': trade,
  815. 'timestamp': timestamp,
  816. 'datetime': self.iso8601(timestamp),
  817. 'symbol': symbol,
  818. 'order': self.safe_string(trade, 'order_id'),
  819. 'type': self.safe_string_lower(trade, 'order_type'),
  820. 'side': side,
  821. 'takerOrMaker': takerOrMaker,
  822. 'price': price,
  823. 'amount': amount,
  824. 'cost': cost,
  825. 'fee': fee,
  826. }
  827. def fetch_trades(self, symbol, since=None, limit=None, params={}):
  828. self.load_markets()
  829. market = self.market(symbol)
  830. request = {
  831. 'symbol': market['id'],
  832. # 'from': 123, # from id
  833. }
  834. if limit is not None:
  835. request['count'] = limit # default 500, max 1000
  836. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  837. marketType = self.safe_string(marketTypes, symbol)
  838. method = 'publicLinearGetRecentTradingRecords' if (marketType == 'linear') else 'publicGetTradingRecords'
  839. response = getattr(self, method)(self.extend(request, params))
  840. #
  841. # {
  842. # ret_code: 0,
  843. # ret_msg: 'OK',
  844. # ext_code: '',
  845. # ext_info: '',
  846. # result: [
  847. # {
  848. # id: 43785688,
  849. # symbol: 'BTCUSD',
  850. # price: 7786,
  851. # qty: 67,
  852. # side: 'Sell',
  853. # time: '2020-03-11T19:18:30.123Z'
  854. # },
  855. # ],
  856. # time_now: '1583954313.393362'
  857. # }
  858. #
  859. result = self.safe_value(response, 'result', {})
  860. return self.parse_trades(result, market, since, limit)
  861. def parse_order_book(self, orderbook, timestamp=None, bidsKey='Buy', asksKey='Sell', priceKey='price', amountKey='size'):
  862. bids = []
  863. asks = []
  864. for i in range(0, len(orderbook)):
  865. bidask = orderbook[i]
  866. side = self.safe_string(bidask, 'side')
  867. if side == 'Buy':
  868. bids.append(self.parse_bid_ask(bidask, priceKey, amountKey))
  869. elif side == 'Sell':
  870. asks.append(self.parse_bid_ask(bidask, priceKey, amountKey))
  871. else:
  872. raise ExchangeError(self.id + ' parseOrderBook encountered an unrecognized bidask format: ' + self.json(bidask))
  873. return {
  874. 'bids': self.sort_by(bids, 0, True),
  875. 'asks': self.sort_by(asks, 0),
  876. 'timestamp': timestamp,
  877. 'datetime': self.iso8601(timestamp),
  878. 'nonce': None,
  879. }
  880. def fetch_order_book(self, symbol, limit=None, params={}):
  881. self.load_markets()
  882. market = self.market(symbol)
  883. request = {
  884. 'symbol': market['id'],
  885. }
  886. response = self.publicGetOrderBookL2(self.extend(request, params))
  887. #
  888. # {
  889. # ret_code: 0,
  890. # ret_msg: 'OK',
  891. # ext_code: '',
  892. # ext_info: '',
  893. # result: [
  894. # {symbol: 'BTCUSD', price: '7767.5', size: 677956, side: 'Buy'},
  895. # {symbol: 'BTCUSD', price: '7767', size: 580690, side: 'Buy'},
  896. # {symbol: 'BTCUSD', price: '7766.5', size: 475252, side: 'Buy'},
  897. # {symbol: 'BTCUSD', price: '7768', size: 330847, side: 'Sell'},
  898. # {symbol: 'BTCUSD', price: '7768.5', size: 97159, side: 'Sell'},
  899. # {symbol: 'BTCUSD', price: '7769', size: 6508, side: 'Sell'},
  900. # ],
  901. # time_now: '1583954829.874823'
  902. # }
  903. #
  904. result = self.safe_value(response, 'result', [])
  905. timestamp = self.safe_timestamp(response, 'time_now')
  906. return self.parse_order_book(result, timestamp, 'Buy', 'Sell', 'price', 'size')
  907. def parse_order_status(self, status):
  908. statuses = {
  909. # basic orders
  910. 'Created': 'open',
  911. 'Rejected': 'rejected', # order is triggered but failed upon being placed
  912. 'New': 'open',
  913. 'PartiallyFilled': 'open',
  914. 'Filled': 'closed',
  915. 'Cancelled': 'canceled',
  916. 'PendingCancel': 'canceling', # the engine has received the cancellation but there is no guarantee that it will be successful
  917. # conditional orders
  918. 'Active': 'open', # order is triggered and placed successfully
  919. 'Untriggered': 'open', # order waits to be triggered
  920. 'Triggered': 'closed', # order is triggered
  921. # 'Cancelled': 'canceled', # order is cancelled
  922. # 'Rejected': 'rejected', # order is triggered but fail to be placed
  923. 'Deactivated': 'canceled', # conditional order was cancelled before triggering
  924. }
  925. return self.safe_string(statuses, status, status)
  926. def parse_order(self, order, market=None):
  927. #
  928. # createOrder
  929. #
  930. # {
  931. # "user_id": 1,
  932. # "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b",
  933. # "symbol": "BTCUSD",
  934. # "side": "Buy",
  935. # "order_type": "Limit",
  936. # "price": 8800,
  937. # "qty": 1,
  938. # "time_in_force": "GoodTillCancel",
  939. # "order_status": "Created",
  940. # "last_exec_time": 0,
  941. # "last_exec_price": 0,
  942. # "leaves_qty": 1,
  943. # "cum_exec_qty": 0, # in contracts, where 1 contract = 1 quote currency unit(USD for inverse contracts)
  944. # "cum_exec_value": 0, # in contract's underlying currency(BTC for inverse contracts)
  945. # "cum_exec_fee": 0,
  946. # "reject_reason": "",
  947. # "order_link_id": "",
  948. # "created_at": "2019-11-30T11:03:43.452Z",
  949. # "updated_at": "2019-11-30T11:03:43.455Z"
  950. # }
  951. #
  952. # fetchOrder
  953. #
  954. # {
  955. # "user_id" : 599946,
  956. # "symbol" : "BTCUSD",
  957. # "side" : "Buy",
  958. # "order_type" : "Limit",
  959. # "price" : "7948",
  960. # "qty" : 10,
  961. # "time_in_force" : "GoodTillCancel",
  962. # "order_status" : "Filled",
  963. # "ext_fields" : {
  964. # "o_req_num" : -1600687220498,
  965. # "xreq_type" : "x_create"
  966. # },
  967. # "last_exec_time" : "1588150113.968422",
  968. # "last_exec_price" : "7948",
  969. # "leaves_qty" : 0,
  970. # "leaves_value" : "0",
  971. # "cum_exec_qty" : 10,
  972. # "cum_exec_value" : "0.00125817",
  973. # "cum_exec_fee" : "-0.00000031",
  974. # "reject_reason" : "",
  975. # "cancel_type" : "",
  976. # "order_link_id" : "",
  977. # "created_at" : "2020-04-29T08:45:24.399146Z",
  978. # "updated_at" : "2020-04-29T08:48:33.968422Z",
  979. # "order_id" : "dd2504b9-0157-406a-99e1-efa522373944"
  980. # }
  981. #
  982. # conditional order
  983. #
  984. # {
  985. # "user_id":##,
  986. # "symbol":"BTCUSD",
  987. # "side":"Buy",
  988. # "order_type":"Market",
  989. # "price":0,
  990. # "qty":10,
  991. # "time_in_force":"GoodTillCancel",
  992. # "stop_order_type":"Stop",
  993. # "trigger_by":"LastPrice",
  994. # "base_price":11833,
  995. # "order_status":"Untriggered",
  996. # "ext_fields":{
  997. # "stop_order_type":"Stop",
  998. # "trigger_by":"LastPrice",
  999. # "base_price":11833,
  1000. # "expected_direction":"Rising",
  1001. # "trigger_price":12400,
  1002. # "close_on_trigger":true,
  1003. # "op_from":"api",
  1004. # "remark":"145.53.159.48",
  1005. # "o_req_num":0
  1006. # },
  1007. # "leaves_qty":10,
  1008. # "leaves_value":0.00080645,
  1009. # "reject_reason":null,
  1010. # "cross_seq":-1,
  1011. # "created_at":"2020-08-21T09:18:48.000Z",
  1012. # "updated_at":"2020-08-21T09:18:48.000Z",
  1013. # "stop_px":12400,
  1014. # "stop_order_id":"3f3b54b1-3379-42c7-8510-44f4d9915be0"
  1015. # }
  1016. #
  1017. marketId = self.safe_string(order, 'symbol')
  1018. symbol = None
  1019. base = None
  1020. if marketId in self.markets_by_id:
  1021. market = self.markets_by_id[marketId]
  1022. timestamp = self.parse8601(self.safe_string(order, 'created_at'))
  1023. id = self.safe_string_2(order, 'order_id', 'stop_order_id')
  1024. price = self.safe_float(order, 'price')
  1025. average = self.safe_float(order, 'average_price')
  1026. amount = self.safe_float(order, 'qty')
  1027. cost = self.safe_float(order, 'cum_exec_value')
  1028. filled = self.safe_float(order, 'cum_exec_qty')
  1029. remaining = self.safe_float(order, 'leaves_qty')
  1030. if market is not None:
  1031. symbol = market['symbol']
  1032. base = market['base']
  1033. lastTradeTimestamp = self.safe_timestamp(order, 'last_exec_time')
  1034. if lastTradeTimestamp == 0:
  1035. lastTradeTimestamp = None
  1036. if (filled is None) and (amount is not None) and (remaining is not None):
  1037. filled = amount - remaining
  1038. if filled is not None:
  1039. if (remaining is None) and (amount is not None):
  1040. remaining = amount - filled
  1041. if cost is None:
  1042. if price is not None:
  1043. cost = price * filled
  1044. status = self.parse_order_status(self.safe_string_2(order, 'order_status', 'stop_order_status'))
  1045. side = self.safe_string_lower(order, 'side')
  1046. feeCost = self.safe_float(order, 'cum_exec_fee')
  1047. fee = None
  1048. if feeCost is not None:
  1049. feeCost = abs(feeCost)
  1050. fee = {
  1051. 'cost': feeCost,
  1052. 'currency': base,
  1053. }
  1054. type = self.safe_string_lower(order, 'order_type')
  1055. clientOrderId = self.safe_string(order, 'order_link_id')
  1056. if (clientOrderId is not None) and (len(clientOrderId) < 1):
  1057. clientOrderId = None
  1058. return {
  1059. 'info': order,
  1060. 'id': id,
  1061. 'clientOrderId': clientOrderId,
  1062. 'timestamp': timestamp,
  1063. 'datetime': self.iso8601(timestamp),
  1064. 'lastTradeTimestamp': lastTradeTimestamp,
  1065. 'symbol': symbol,
  1066. 'type': type,
  1067. 'side': side,
  1068. 'price': price,
  1069. 'amount': amount,
  1070. 'cost': cost,
  1071. 'average': average,
  1072. 'filled': filled,
  1073. 'remaining': remaining,
  1074. 'status': status,
  1075. 'fee': fee,
  1076. 'trades': None,
  1077. }
  1078. def fetch_order(self, id, symbol=None, params={}):
  1079. if symbol is None:
  1080. raise ArgumentsRequired(self.id + ' fetchOrder requires a symbol argument')
  1081. self.load_markets()
  1082. market = self.market(symbol)
  1083. request = {
  1084. 'symbol': market['id'],
  1085. # 'order_link_id': 'string', # one of order_id, stop_order_id or order_link_id is required
  1086. # regular orders ---------------------------------------------
  1087. # 'order_id': id, # one of order_id or order_link_id is required for regular orders
  1088. # conditional orders ---------------------------------------------
  1089. # 'stop_order_id': id, # one of stop_order_id or order_link_id is required for conditional orders
  1090. }
  1091. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  1092. marketType = self.safe_string(marketTypes, symbol)
  1093. method = 'privateLinearGetOrderSearch' if (marketType == 'linear') else 'privateGetOrder'
  1094. stopOrderId = self.safe_string(params, 'stop_order_id')
  1095. if stopOrderId is None:
  1096. orderLinkId = self.safe_string(params, 'order_link_id')
  1097. if orderLinkId is None:
  1098. request['order_id'] = id
  1099. else:
  1100. method = 'privateLinearGetStopOrderSearch' if (marketType == 'linear') else 'privateGetStopOrder'
  1101. response = getattr(self, method)(self.extend(request, params))
  1102. #
  1103. # {
  1104. # "ret_code": 0,
  1105. # "ret_msg": "OK",
  1106. # "ext_code": "",
  1107. # "ext_info": "",
  1108. # "result": {
  1109. # "user_id": 1,
  1110. # "symbol": "BTCUSD",
  1111. # "side": "Sell",
  1112. # "order_type": "Limit",
  1113. # "price": "8083",
  1114. # "qty": 10,
  1115. # "time_in_force": "GoodTillCancel",
  1116. # "order_status": "New",
  1117. # "ext_fields": {"o_req_num": -308787, "xreq_type": "x_create", "xreq_offset": 4154640},
  1118. # "leaves_qty": 10,
  1119. # "leaves_value": "0.00123716",
  1120. # "cum_exec_qty": 0,
  1121. # "reject_reason": "",
  1122. # "order_link_id": "",
  1123. # "created_at": "2019-10-21T07:28:19.396246Z",
  1124. # "updated_at": "2019-10-21T07:28:19.396246Z",
  1125. # "order_id": "efa44157-c355-4a98-b6d6-1d846a936b93"
  1126. # },
  1127. # "time_now": "1571651135.291930",
  1128. # "rate_limit_status": 99, # The remaining number of accesses in one minute
  1129. # "rate_limit_reset_ms": 1580885703683,
  1130. # "rate_limit": 100
  1131. # }
  1132. #
  1133. # conditional orders
  1134. #
  1135. # {
  1136. # "ret_code": 0,
  1137. # "ret_msg": "OK",
  1138. # "ext_code": "",
  1139. # "ext_info": "",
  1140. # "result": {
  1141. # "user_id": 1,
  1142. # "symbol": "BTCUSD",
  1143. # "side": "Buy",
  1144. # "order_type": "Limit",
  1145. # "price": "8000",
  1146. # "qty": 1,
  1147. # "time_in_force": "GoodTillCancel",
  1148. # "order_status": "Untriggered",
  1149. # "ext_fields": {},
  1150. # "leaves_qty": 1,
  1151. # "leaves_value": "0.00013333",
  1152. # "cum_exec_qty": 0,
  1153. # "cum_exec_value": null,
  1154. # "cum_exec_fee": null,
  1155. # "reject_reason": "",
  1156. # "order_link_id": "",
  1157. # "created_at": "2019-12-27T19:56:24.052194Z",
  1158. # "updated_at": "2019-12-27T19:56:24.052194Z",
  1159. # "order_id": "378a1bbc-a93a-4e75-87f4-502ea754ba36"
  1160. # },
  1161. # "time_now": "1577476584.386958",
  1162. # "rate_limit_status": 99,
  1163. # "rate_limit_reset_ms": 1580885703683,
  1164. # "rate_limit": 100
  1165. # }
  1166. #
  1167. result = self.safe_value(response, 'result')
  1168. return self.parse_order(result, market)
  1169. def create_order(self, symbol, type, side, amount, price=None, params={}):
  1170. self.load_markets()
  1171. market = self.market(symbol)
  1172. qty = self.amount_to_precision(symbol, amount)
  1173. if market['inverse']:
  1174. qty = int(qty)
  1175. else:
  1176. qty = float(qty)
  1177. request = {
  1178. # orders ---------------------------------------------------------
  1179. 'side': self.capitalize(side),
  1180. 'symbol': market['id'],
  1181. 'order_type': self.capitalize(type),
  1182. 'qty': qty, # order quantity in USD, integer only
  1183. # 'price': float(self.price_to_precision(symbol, price)), # required for limit orders
  1184. 'time_in_force': 'GoodTillCancel', # ImmediateOrCancel, FillOrKill, PostOnly
  1185. # 'take_profit': 123.45, # take profit price, only take effect upon opening the position
  1186. # 'stop_loss': 123.45, # stop loss price, only take effect upon opening the position
  1187. # 'reduce_only': False, # reduce only
  1188. # when creating a closing order, bybit recommends a True value for
  1189. # close_on_trigger to avoid failing due to insufficient available margin
  1190. # 'close_on_trigger': False,
  1191. # 'order_link_id': 'string', # unique client order id, max 36 characters
  1192. # conditional orders ---------------------------------------------
  1193. # base_price is used to compare with the value of stop_px, to decide
  1194. # whether your conditional order will be triggered by crossing trigger
  1195. # price from upper side or lower side, mainly used to identify the
  1196. # expected direction of the current conditional order
  1197. # 'base_price': 123.45, # required for conditional orders
  1198. # 'stop_px': 123.45, # trigger price, required for conditional orders
  1199. # 'trigger_by': 'LastPrice', # IndexPrice, MarkPrice
  1200. }
  1201. priceIsRequired = False
  1202. if type == 'limit':
  1203. priceIsRequired = True
  1204. if priceIsRequired:
  1205. if price is not None:
  1206. request['price'] = float(self.price_to_precision(symbol, price))
  1207. else:
  1208. raise ArgumentsRequired(self.id + ' createOrder requires a price argument for a ' + type + ' order')
  1209. stopPx = self.safe_value(params, 'stop_px')
  1210. basePrice = self.safe_value(params, 'base_price')
  1211. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  1212. marketType = self.safe_string(marketTypes, symbol)
  1213. method = 'privateLinearPostOrderCreate' if (marketType == 'linear') else 'privatePostOrderCreate'
  1214. if stopPx is not None:
  1215. if basePrice is None:
  1216. raise ArgumentsRequired(self.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order')
  1217. else:
  1218. method = 'privateLinearPostStopOrderCreate' if (marketType == 'linear') else 'openapiPostStopOrderCreate'
  1219. request['stop_px'] = float(self.price_to_precision(symbol, stopPx))
  1220. request['base_price'] = float(self.price_to_precision(symbol, basePrice))
  1221. params = self.omit(params, ['stop_px', 'base_price'])
  1222. elif basePrice is not None:
  1223. raise ArgumentsRequired(self.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order')
  1224. response = getattr(self, method)(self.extend(request, params))
  1225. #
  1226. # {
  1227. # "ret_code": 0,
  1228. # "ret_msg": "OK",
  1229. # "ext_code": "",
  1230. # "ext_info": "",
  1231. # "result": {
  1232. # "user_id": 1,
  1233. # "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b",
  1234. # "symbol": "BTCUSD",
  1235. # "side": "Buy",
  1236. # "order_type": "Limit",
  1237. # "price": 8800,
  1238. # "qty": 1,
  1239. # "time_in_force": "GoodTillCancel",
  1240. # "order_status": "Created",
  1241. # "last_exec_time": 0,
  1242. # "last_exec_price": 0,
  1243. # "leaves_qty": 1,
  1244. # "cum_exec_qty": 0,
  1245. # "cum_exec_value": 0,
  1246. # "cum_exec_fee": 0,
  1247. # "reject_reason": "",
  1248. # "order_link_id": "",
  1249. # "created_at": "2019-11-30T11:03:43.452Z",
  1250. # "updated_at": "2019-11-30T11:03:43.455Z"
  1251. # },
  1252. # "time_now": "1575111823.458705",
  1253. # "rate_limit_status": 98,
  1254. # "rate_limit_reset_ms": 1580885703683,
  1255. # "rate_limit": 100
  1256. # }
  1257. #
  1258. # conditional orders
  1259. #
  1260. # {
  1261. # "ret_code": 0,
  1262. # "ret_msg": "ok",
  1263. # "ext_code": "",
  1264. # "result": {
  1265. # "user_id": 1,
  1266. # "symbol": "BTCUSD",
  1267. # "side": "Buy",
  1268. # "order_type": "Limit",
  1269. # "price": 8000,
  1270. # "qty": 1,
  1271. # "time_in_force": "GoodTillCancel",
  1272. # "stop_order_type": "Stop",
  1273. # "trigger_by": "LastPrice",
  1274. # "base_price": 7000,
  1275. # "order_status": "Untriggered",
  1276. # "ext_fields": {
  1277. # "stop_order_type": "Stop",
  1278. # "trigger_by": "LastPrice",
  1279. # "base_price": 7000,
  1280. # "expected_direction": "Rising",
  1281. # "trigger_price": 7500,
  1282. # "op_from": "api",
  1283. # "remark": "127.0.01",
  1284. # "o_req_num": 0
  1285. # },
  1286. # "leaves_qty": 1,
  1287. # "leaves_value": 0.00013333,
  1288. # "reject_reason": null,
  1289. # "cross_seq": -1,
  1290. # "created_at": "2019-12-27T12:48:24.000Z",
  1291. # "updated_at": "2019-12-27T12:48:24.000Z",
  1292. # "stop_px": 7500,
  1293. # "stop_order_id": "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5"
  1294. # },
  1295. # "ext_info": null,
  1296. # "time_now": "1577450904.327654",
  1297. # "rate_limit_status": 99,
  1298. # "rate_limit_reset_ms": 1577450904335,
  1299. # "rate_limit": "100"
  1300. # }
  1301. #
  1302. result = self.safe_value(response, 'result')
  1303. return self.parse_order(result, market)
  1304. def edit_order(self, id, symbol, type, side, amount=None, price=None, params={}):
  1305. if symbol is None:
  1306. raise ArgumentsRequired(self.id + ' editOrder requires an symbol argument')
  1307. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  1308. marketType = self.safe_string(marketTypes, symbol)
  1309. self.load_markets()
  1310. market = self.market(symbol)
  1311. request = {
  1312. # 'order_id': id, # only for non-conditional orders
  1313. 'symbol': market['id'],
  1314. # 'p_r_qty': self.amount_to_precision(symbol, amount), # new order quantity, optional
  1315. # 'p_r_price' self.priceToprecision(symbol, price), # new order price, optional
  1316. # ----------------------------------------------------------------
  1317. # conditional orders
  1318. # 'stop_order_id': id, # only for conditional orders
  1319. # 'p_r_trigger_price': 123.45, # new trigger price also known as stop_px
  1320. }
  1321. method = 'privateLinearPostOrderReplace' if (marketType == 'linear') else 'openapiPostOrderReplace'
  1322. stopOrderId = self.safe_string(params, 'stop_order_id')
  1323. if stopOrderId is not None:
  1324. method = 'privateLinearPostStopOrderReplace' if (marketType == 'linear') else 'openapiPostStopOrderReplace'
  1325. request['stop_order_id'] = stopOrderId
  1326. params = self.omit(params, ['stop_order_id'])
  1327. else:
  1328. request['order_id'] = id
  1329. if amount is not None:
  1330. request['p_r_qty'] = int(self.amount_to_precision(symbol, amount))
  1331. if price is not None:
  1332. request['p_r_price'] = float(self.price_to_precision(symbol, price))
  1333. response = getattr(self, method)(self.extend(request, params))
  1334. #
  1335. # {
  1336. # "ret_code": 0,
  1337. # "ret_msg": "ok",
  1338. # "ext_code": "",
  1339. # "result": {"order_id": "efa44157-c355-4a98-b6d6-1d846a936b93"},
  1340. # "time_now": "1539778407.210858",
  1341. # "rate_limit_status": 99, # remaining number of accesses in one minute
  1342. # "rate_limit_reset_ms": 1580885703683,
  1343. # "rate_limit": 100
  1344. # }
  1345. #
  1346. # conditional orders
  1347. #
  1348. # {
  1349. # "ret_code": 0,
  1350. # "ret_msg": "ok",
  1351. # "ext_code": "",
  1352. # "result": {"stop_order_id": "378a1bbc-a93a-4e75-87f4-502ea754ba36"},
  1353. # "ext_info": null,
  1354. # "time_now": "1577475760.604942",
  1355. # "rate_limit_status": 96,
  1356. # "rate_limit_reset_ms": 1577475760612,
  1357. # "rate_limit": "100"
  1358. # }
  1359. #
  1360. result = self.safe_value(response, 'result', {})
  1361. return {
  1362. 'info': response,
  1363. 'id': self.safe_string_2(result, 'order_id', 'stop_order_id'),
  1364. 'order_id': self.safe_string(result, 'order_id'),
  1365. 'stop_order_id': self.safe_string(result, 'stop_order_id'),
  1366. }
  1367. def cancel_order(self, id, symbol=None, params={}):
  1368. if symbol is None:
  1369. raise ArgumentsRequired(self.id + ' cancelOrder requires a symbol argument')
  1370. self.load_markets()
  1371. market = self.market(symbol)
  1372. request = {
  1373. 'symbol': market['id'],
  1374. # 'order_link_id': 'string', # one of order_id, stop_order_id or order_link_id is required
  1375. # regular orders ---------------------------------------------
  1376. # 'order_id': id, # one of order_id or order_link_id is required for regular orders
  1377. # conditional orders ---------------------------------------------
  1378. # 'stop_order_id': id, # one of stop_order_id or order_link_id is required for conditional orders
  1379. }
  1380. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  1381. marketType = self.safe_value(marketTypes, symbol)
  1382. method = 'privateLinearPostOrderCancel' if (marketType == 'linear') else 'privatePostOrderCancel'
  1383. stopOrderId = self.safe_string(params, 'stop_order_id')
  1384. if stopOrderId is None:
  1385. orderLinkId = self.safe_string(params, 'order_link_id')
  1386. if orderLinkId is None:
  1387. request['order_id'] = id
  1388. else:
  1389. method = 'privateLinearPostStopOrderCancel' if (marketType == 'linear') else 'openapiPostStopOrderCancel'
  1390. response = getattr(self, method)(self.extend(request, params))
  1391. result = self.safe_value(response, 'result', {})
  1392. return self.parse_order(result, market)
  1393. def cancel_all_orders(self, symbol=None, params={}):
  1394. if symbol is None:
  1395. raise ArgumentsRequired(self.id + ' cancelAllOrders requires a symbol argument')
  1396. self.load_markets()
  1397. market = self.market(symbol)
  1398. request = {
  1399. 'symbol': market['id'],
  1400. }
  1401. options = self.safe_value(self.options, 'cancelAllOrders')
  1402. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  1403. marketType = self.safe_string(marketTypes, symbol)
  1404. defaultMethod = 'privateLinearPostOrderCancelAll' if (marketType == 'linear') else 'privatePostOrderCancelAll'
  1405. method = self.safe_string(options, 'method', defaultMethod)
  1406. response = getattr(self, method)(self.extend(request, params))
  1407. result = self.safe_value(response, 'result', [])
  1408. return self.parse_orders(result, market)
  1409. def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
  1410. self.load_markets()
  1411. request = {
  1412. # 'order_id': 'string'
  1413. # 'order_link_id': 'string', # unique client order id, max 36 characters
  1414. # 'symbol': market['id'], # default BTCUSD
  1415. # 'order': 'desc', # asc
  1416. # 'page': 1,
  1417. # 'limit': 20, # max 50
  1418. # 'order_status': 'Created,New'
  1419. # conditional orders ---------------------------------------------
  1420. # 'stop_order_id': 'string',
  1421. # 'stop_order_status': 'Untriggered',
  1422. }
  1423. market = None
  1424. if symbol is not None:
  1425. market = self.market(symbol)
  1426. request['symbol'] = market['id']
  1427. if limit is not None:
  1428. request['limit'] = limit
  1429. options = self.safe_value(self.options, 'fetchOrders', {})
  1430. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  1431. marketType = self.safe_string(marketTypes, symbol)
  1432. defaultMethod = 'privateLinearGetOrderList' if (marketType == 'linear') else 'openapiGetOrderList'
  1433. query = params
  1434. if ('stop_order_id' in params) or ('stop_order_status' in params):
  1435. stopOrderStatus = self.safe_value(params, 'stopOrderStatus')
  1436. if stopOrderStatus is not None:
  1437. if isinstance(stopOrderStatus, list):
  1438. stopOrderStatus = ','.join(stopOrderStatus)
  1439. request['stop_order_status'] = stopOrderStatus
  1440. query = self.omit(params, 'stop_order_status')
  1441. defaultMethod = 'privateLinearGetStopOrderList' if (marketType == 'linear') else 'openapiGetStopOrderList'
  1442. method = self.safe_string(options, 'method', defaultMethod)
  1443. response = getattr(self, method)(self.extend(request, query))
  1444. #
  1445. # {
  1446. # "ret_code": 0,
  1447. # "ret_msg": "ok",
  1448. # "ext_code": "",
  1449. # "result": {
  1450. # "current_page": 1,
  1451. # "last_page": 6,
  1452. # "data": [
  1453. # {
  1454. # "user_id": 1,
  1455. # "symbol": "BTCUSD",
  1456. # "side": "Sell",
  1457. # "order_type": "Market",
  1458. # "price": 7074,
  1459. # "qty": 2,
  1460. # "time_in_force": "ImmediateOrCancel",
  1461. # "order_status": "Filled",
  1462. # "ext_fields": {
  1463. # "close_on_trigger": True,
  1464. # "orig_order_type": "BLimit",
  1465. # "prior_x_req_price": 5898.5,
  1466. # "op_from": "pc",
  1467. # "remark": "127.0.0.1",
  1468. # "o_req_num": -34799032763,
  1469. # "xreq_type": "x_create"
  1470. # },
  1471. # "last_exec_time": "1577448481.696421",
  1472. # "last_exec_price": 7070.5,
  1473. # "leaves_qty": 0,
  1474. # "leaves_value": 0,
  1475. # "cum_exec_qty": 2,
  1476. # "cum_exec_value": 0.00028283,
  1477. # "cum_exec_fee": 0.00002,
  1478. # "reject_reason": "NoError",
  1479. # "order_link_id": "",
  1480. # "created_at": "2019-12-27T12:08:01.000Z",
  1481. # "updated_at": "2019-12-27T12:08:01.000Z",
  1482. # "order_id": "f185806b-b801-40ff-adec-52289370ed62"
  1483. # }
  1484. # ]
  1485. # },
  1486. # "ext_info": null,
  1487. # "time_now": "1577448922.437871",
  1488. # "rate_limit_status": 98,
  1489. # "rate_limit_reset_ms": 1580885703683,
  1490. # "rate_limit": 100
  1491. # }
  1492. #
  1493. # conditional orders
  1494. #
  1495. # {
  1496. # "ret_code": 0,
  1497. # "ret_msg": "ok",
  1498. # "ext_code": "",
  1499. # "result": {
  1500. # "current_page": 1,
  1501. # "last_page": 1,
  1502. # "data": [
  1503. # {
  1504. # "user_id": 1,
  1505. # "stop_order_status": "Untriggered",
  1506. # "symbol": "BTCUSD",
  1507. # "side": "Buy",
  1508. # "order_type": "Limit",
  1509. # "price": 8000,
  1510. # "qty": 1,
  1511. # "time_in_force": "GoodTillCancel",
  1512. # "stop_order_type": "Stop",
  1513. # "trigger_by": "LastPrice",
  1514. # "base_price": 7000,
  1515. # "order_link_id": "",
  1516. # "created_at": "2019-12-27T12:48:24.000Z",
  1517. # "updated_at": "2019-12-27T12:48:24.000Z",
  1518. # "stop_px": 7500,
  1519. # "stop_order_id": "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5"
  1520. # },
  1521. # ]
  1522. # },
  1523. # "ext_info": null,
  1524. # "time_now": "1577451658.755468",
  1525. # "rate_limit_status": 599,
  1526. # "rate_limit_reset_ms": 1577451658762,
  1527. # "rate_limit": 600
  1528. # }
  1529. #
  1530. result = self.safe_value(response, 'result', {})
  1531. data = self.safe_value(result, 'data', [])
  1532. return self.parse_orders(data, market, since, limit)
  1533. def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
  1534. defaultStatuses = [
  1535. 'Rejected',
  1536. 'Filled',
  1537. 'Cancelled',
  1538. # conditional orders
  1539. # 'Active',
  1540. # 'Triggered',
  1541. # 'Cancelled',
  1542. # 'Rejected',
  1543. # 'Deactivated',
  1544. ]
  1545. options = self.safe_value(self.options, 'fetchClosedOrders', {})
  1546. status = self.safe_value(options, 'order_status', defaultStatuses)
  1547. if isinstance(status, list):
  1548. status = ','.join(status)
  1549. request = {}
  1550. stopOrderStatus = self.safe_value(params, 'stop_order_status')
  1551. if stopOrderStatus is None:
  1552. request['order_status'] = status
  1553. else:
  1554. request['stop_order_status'] = stopOrderStatus
  1555. return self.fetch_orders(symbol, since, limit, self.extend(request, params))
  1556. def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
  1557. defaultStatuses = [
  1558. 'Created',
  1559. 'New',
  1560. 'PartiallyFilled',
  1561. 'PendingCancel',
  1562. # conditional orders
  1563. # 'Untriggered',
  1564. ]
  1565. options = self.safe_value(self.options, 'fetchOpenOrders', {})
  1566. status = self.safe_value(options, 'order_status', defaultStatuses)
  1567. if isinstance(status, list):
  1568. status = ','.join(status)
  1569. request = {}
  1570. stopOrderStatus = self.safe_value(params, 'stop_order_status')
  1571. if stopOrderStatus is None:
  1572. request['order_status'] = status
  1573. else:
  1574. request['stop_order_status'] = stopOrderStatus
  1575. return self.fetch_orders(symbol, since, limit, self.extend(request, params))
  1576. def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
  1577. request = {
  1578. 'order_id': id,
  1579. }
  1580. return self.fetch_my_trades(symbol, since, limit, self.extend(request, params))
  1581. def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
  1582. self.load_markets()
  1583. request = {
  1584. # 'order_id': 'f185806b-b801-40ff-adec-52289370ed62', # if not provided will return user's trading records
  1585. # 'symbol': market['id'],
  1586. # 'start_time': int(since / 1000),
  1587. # 'page': 1,
  1588. # 'limit' 20, # max 50
  1589. }
  1590. market = None
  1591. if symbol is None:
  1592. orderId = self.safe_string(params, 'order_id')
  1593. if orderId is None:
  1594. raise ArgumentsRequired(self.id + ' fetchMyTrades requires a symbol argument or an order_id param')
  1595. else:
  1596. request['order_id'] = orderId
  1597. params = self.omit(params, 'order_id')
  1598. else:
  1599. market = self.market(symbol)
  1600. request['symbol'] = market['id']
  1601. if since is not None:
  1602. request['start_time'] = since
  1603. if limit is not None:
  1604. request['limit'] = limit # default 20, max 50
  1605. marketTypes = self.safe_value(self.options, 'marketTypes', {})
  1606. marketType = self.safe_string(marketTypes, symbol)
  1607. method = 'privateLinearGetTradeExecutionList' if (marketType == 'linear') else 'privateGetExecutionList'
  1608. response = getattr(self, method)(self.extend(request, params))
  1609. #
  1610. # inverse
  1611. #
  1612. # {
  1613. # "ret_code": 0,
  1614. # "ret_msg": "OK",
  1615. # "ext_code": "",
  1616. # "ext_info": "",
  1617. # "result": {
  1618. # "order_id": "Abandonednot !", # Abandonednot !
  1619. # "trade_list": [
  1620. # {
  1621. # "closed_size": 0,
  1622. # "cross_seq": 277136382,
  1623. # "exec_fee": "0.0000001",
  1624. # "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68",
  1625. # "exec_price": "8178.5",
  1626. # "exec_qty": 1,
  1627. # "exec_time": "1571676941.70682",
  1628. # "exec_type": "Trade", #Exec Type Enum
  1629. # "exec_value": "0.00012227",
  1630. # "fee_rate": "0.00075",
  1631. # "last_liquidity_ind": "RemovedLiquidity", #Liquidity Enum
  1632. # "leaves_qty": 0,
  1633. # "nth_fill": 2,
  1634. # "order_id": "7ad50cb1-9ad0-4f74-804b-d82a516e1029",
  1635. # "order_link_id": "",
  1636. # "order_price": "8178",
  1637. # "order_qty": 1,
  1638. # "order_type": "Market", #Order Type Enum
  1639. # "side": "Buy", #Side Enum
  1640. # "symbol": "BTCUSD", #Symbol Enum
  1641. # "user_id": 1
  1642. # }
  1643. # ]
  1644. # },
  1645. # "time_now": "1577483699.281488",
  1646. # "rate_limit_status": 118,
  1647. # "rate_limit_reset_ms": 1577483699244737,
  1648. # "rate_limit": 120
  1649. # }
  1650. #
  1651. # linear
  1652. #
  1653. # {
  1654. # "ret_code":0,
  1655. # "ret_msg":"OK",
  1656. # "ext_code":"",
  1657. # "ext_info":"",
  1658. # "result":{
  1659. # "current_page":1,
  1660. # "data":[
  1661. # {
  1662. # "order_id":"b59418ec-14d4-4ef9-b9f4-721d5d576974",
  1663. # "order_link_id":"",
  1664. # "side":"Sell",
  1665. # "symbol":"BTCUSDT",
  1666. # "exec_id":"0327284d-faec-5191-bd89-acc5b4fafda9",
  1667. # "price":0.5,
  1668. # "order_price":0.5,
  1669. # "order_qty":0.01,
  1670. # "order_type":"Market",
  1671. # "fee_rate":0.00075,
  1672. # "exec_price":9709.5,
  1673. # "exec_type":"Trade",
  1674. # "exec_qty":0.01,
  1675. # "exec_fee":0.07282125,
  1676. # "exec_value":97.095,
  1677. # "leaves_qty":0,
  1678. # "closed_size":0.01,
  1679. # "last_liquidity_ind":"RemovedLiquidity",
  1680. # "trade_time":1591648052,
  1681. # "trade_time_ms":1591648052861
  1682. # }
  1683. # ]
  1684. # },
  1685. # "time_now":"1591736501.979264",
  1686. # "rate_limit_status":119,
  1687. # "rate_limit_reset_ms":1591736501974,
  1688. # "rate_limit":120
  1689. # }
  1690. #
  1691. result = self.safe_value(response, 'result', {})
  1692. trades = self.safe_value_2(result, 'trade_list', 'data', [])
  1693. return self.parse_trades(trades, market, since, limit)
  1694. def fetch_deposits(self, code=None, since=None, limit=None, params={}):
  1695. if code is None:
  1696. raise ArgumentsRequired(self.id + ' fetchWithdrawals() requires a currency code argument')
  1697. self.load_markets()
  1698. currency = self.currency(code)
  1699. request = {
  1700. 'currency': currency['id'],
  1701. }
  1702. if limit is not None:
  1703. request['count'] = limit
  1704. response = self.privateGetGetDeposits(self.extend(request, params))
  1705. #
  1706. # {
  1707. # "jsonrpc": "2.0",
  1708. # "id": 5611,
  1709. # "result": {
  1710. # "count": 1,
  1711. # "data": [
  1712. # {
  1713. # "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax",
  1714. # "amount": 5,
  1715. # "currency": "BTC",
  1716. # "received_timestamp": 1549295017670,
  1717. # "state": "completed",
  1718. # "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda",
  1719. # "updated_timestamp": 1549295130159
  1720. # }
  1721. # ]
  1722. # }
  1723. # }
  1724. #
  1725. result = self.safe_value(response, 'result', {})
  1726. data = self.safe_value(result, 'data', [])
  1727. return self.parse_transactions(data, currency, since, limit, params)
  1728. def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
  1729. self.load_markets()
  1730. request = {
  1731. # 'coin': currency['id'],
  1732. # 'start_date': self.iso8601(since),
  1733. # 'end_date': self.iso8601(till),
  1734. # 'status': 'Pending', # ToBeConfirmed, UnderReview, Pending, Success, CancelByUser, Reject, Expire
  1735. # 'page': 1,
  1736. # 'limit': 20, # max 50
  1737. }
  1738. currency = None
  1739. if code is not None:
  1740. currency = self.currency(code)
  1741. request['coin'] = currency['id']
  1742. if since is not None:
  1743. request['start_date'] = self.iso8601(since)
  1744. if limit is not None:
  1745. request['limit'] = limit
  1746. response = self.openapiGetWalletWithdrawList(self.extend(request, params))
  1747. #
  1748. # {
  1749. # "ret_code": 0,
  1750. # "ret_msg": "ok",
  1751. # "ext_code": "",
  1752. # "result": {
  1753. # "data": [
  1754. # {
  1755. # "id": 137,
  1756. # "user_id": 1,
  1757. # "coin": "XRP", # Coin Enum
  1758. # "status": "Pending", # Withdraw Status Enum
  1759. # "amount": "20.00000000",
  1760. # "fee": "0.25000000",
  1761. # "address": "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM",
  1762. # "tx_id": "",
  1763. # "submited_at": "2019-06-11T02:20:24.000Z",
  1764. # "updated_at": "2019-06-11T02:20:24.000Z"
  1765. # },
  1766. # ],
  1767. # "current_page": 1,
  1768. # "last_page": 1
  1769. # },
  1770. # "ext_info": null,
  1771. # "time_now": "1577482295.125488",
  1772. # "rate_limit_status": 119,
  1773. # "rate_limit_reset_ms": 1577482295132,
  1774. # "rate_limit": 120
  1775. # }
  1776. #
  1777. result = self.safe_value(response, 'result', {})
  1778. data = self.safe_value(result, 'data', [])
  1779. return self.parse_transactions(data, currency, since, limit, params)
  1780. def parse_transaction_status(self, status):
  1781. statuses = {
  1782. 'ToBeConfirmed': 'pending',
  1783. 'UnderReview': 'pending',
  1784. 'Pending': 'pending',
  1785. 'Success': 'ok',
  1786. 'CancelByUser': 'canceled',
  1787. 'Reject': 'rejected',
  1788. 'Expire': 'expired',
  1789. }
  1790. return self.safe_string(statuses, status, status)
  1791. def parse_transaction(self, transaction, currency=None):
  1792. #
  1793. # fetchWithdrawals
  1794. #
  1795. # {
  1796. # "id": 137,
  1797. # "user_id": 1,
  1798. # "coin": "XRP", # Coin Enum
  1799. # "status": "Pending", # Withdraw Status Enum
  1800. # "amount": "20.00000000",
  1801. # "fee": "0.25000000",
  1802. # "address": "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM",
  1803. # "tx_id": "",
  1804. # "submited_at": "2019-06-11T02:20:24.000Z",
  1805. # "updated_at": "2019-06-11T02:20:24.000Z"
  1806. # }
  1807. #
  1808. currencyId = self.safe_string(transaction, 'coin')
  1809. code = self.safe_currency_code(currencyId, currency)
  1810. timestamp = self.parse8601(self.safe_string(transaction, 'submited_at'))
  1811. updated = self.parse8601(self.safe_string(transaction, 'updated_at'))
  1812. status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
  1813. address = self.safe_string(transaction, 'address')
  1814. feeCost = self.safe_float(transaction, 'fee')
  1815. fee = None
  1816. if feeCost is not None:
  1817. fee = {
  1818. 'cost': feeCost,
  1819. 'currency': code,
  1820. }
  1821. return {
  1822. 'info': transaction,
  1823. 'id': self.safe_string(transaction, 'id'),
  1824. 'txid': self.safe_string(transaction, 'tx_id'),
  1825. 'timestamp': timestamp,
  1826. 'datetime': self.iso8601(timestamp),
  1827. 'address': address,
  1828. 'addressTo': None,
  1829. 'addressFrom': None,
  1830. 'tag': None,
  1831. 'tagTo': None,
  1832. 'tagFrom': None,
  1833. 'type': 'withdrawal',
  1834. 'amount': self.safe_float(transaction, 'amount'),
  1835. 'currency': code,
  1836. 'status': status,
  1837. 'updated': updated,
  1838. 'fee': fee,
  1839. }
  1840. def fetch_ledger(self, code=None, since=None, limit=None, params={}):
  1841. self.load_markets()
  1842. request = {
  1843. # 'coin': currency['id'],
  1844. # 'currency': currency['id'], # alias
  1845. # 'start_date': self.iso8601(since),
  1846. # 'end_date': self.iso8601(till),
  1847. # 'wallet_fund_type': 'Deposit', # Withdraw, RealisedPNL, Commission, Refund, Prize, ExchangeOrderWithdraw, ExchangeOrderDeposit
  1848. # 'page': 1,
  1849. # 'limit': 20, # max 50
  1850. }
  1851. currency = None
  1852. if code is not None:
  1853. currency = self.currency(code)
  1854. request['coin'] = currency['id']
  1855. if since is not None:
  1856. request['start_date'] = self.iso8601(since)
  1857. if limit is not None:
  1858. request['limit'] = limit
  1859. response = self.openapiGetWalletFundRecords(self.extend(request, params))
  1860. #
  1861. # {
  1862. # "ret_code": 0,
  1863. # "ret_msg": "ok",
  1864. # "ext_code": "",
  1865. # "result": {
  1866. # "data": [
  1867. # {
  1868. # "id": 234467,
  1869. # "user_id": 1,
  1870. # "coin": "BTC",
  1871. # "wallet_id": 27913,
  1872. # "type": "Realized P&L",
  1873. # "amount": "-0.00000006",
  1874. # "tx_id": "",
  1875. # "address": "BTCUSD",
  1876. # "wallet_balance": "0.03000330",
  1877. # "exec_time": "2019-12-09T00:00:25.000Z",
  1878. # "cross_seq": 0
  1879. # }
  1880. # ]
  1881. # },
  1882. # "ext_info": null,
  1883. # "time_now": "1577481867.115552",
  1884. # "rate_limit_status": 119,
  1885. # "rate_limit_reset_ms": 1577481867122,
  1886. # "rate_limit": 120
  1887. # }
  1888. #
  1889. result = self.safe_value(response, 'result', {})
  1890. data = self.safe_value(result, 'data', [])
  1891. return self.parse_ledger(data, currency, since, limit)
  1892. def parse_ledger_entry(self, item, currency=None):
  1893. #
  1894. # {
  1895. # "id": 234467,
  1896. # "user_id": 1,
  1897. # "coin": "BTC",
  1898. # "wallet_id": 27913,
  1899. # "type": "Realized P&L",
  1900. # "amount": "-0.00000006",
  1901. # "tx_id": "",
  1902. # "address": "BTCUSD",
  1903. # "wallet_balance": "0.03000330",
  1904. # "exec_time": "2019-12-09T00:00:25.000Z",
  1905. # "cross_seq": 0
  1906. # }
  1907. #
  1908. currencyId = self.safe_string(item, 'coin')
  1909. code = self.safe_currency_code(currencyId, currency)
  1910. amount = self.safe_float(item, 'amount')
  1911. after = self.safe_float(item, 'wallet_balance')
  1912. direction = 'out' if (amount < 0) else 'in'
  1913. before = None
  1914. if after is not None and amount is not None:
  1915. difference = amount if (direction == 'out') else -amount
  1916. before = self.sum(after, difference)
  1917. timestamp = self.parse8601(self.safe_string(item, 'exec_time'))
  1918. type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
  1919. id = self.safe_string(item, 'id')
  1920. referenceId = self.safe_string(item, 'tx_id')
  1921. return {
  1922. 'id': id,
  1923. 'currency': code,
  1924. 'account': self.safe_string(item, 'wallet_id'),
  1925. 'referenceAccount': None,
  1926. 'referenceId': referenceId,
  1927. 'status': None,
  1928. 'amount': amount,
  1929. 'before': before,
  1930. 'after': after,
  1931. 'fee': None,
  1932. 'direction': direction,
  1933. 'timestamp': timestamp,
  1934. 'datetime': self.iso8601(timestamp),
  1935. 'type': type,
  1936. 'info': item,
  1937. }
  1938. def parse_ledger_entry_type(self, type):
  1939. types = {
  1940. 'Deposit': 'transaction',
  1941. 'Withdraw': 'transaction',
  1942. 'RealisedPNL': 'trade',
  1943. 'Commission': 'fee',
  1944. 'Refund': 'cashback',
  1945. 'Prize': 'prize', # ?
  1946. 'ExchangeOrderWithdraw': 'transaction',
  1947. 'ExchangeOrderDeposit': 'transaction',
  1948. }
  1949. return self.safe_string(types, type, type)
  1950. def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
  1951. url = self.urls['api']
  1952. request = path
  1953. # public v2
  1954. if api == 'public':
  1955. request = '/' + self.version + '/' + api + '/' + request
  1956. if params:
  1957. request += '?' + self.rawencode(params)
  1958. elif api == 'publicLinear':
  1959. request = '/public/linear/' + request
  1960. if params:
  1961. request += '?' + self.rawencode(params)
  1962. else:
  1963. self.check_required_credentials()
  1964. if api == 'openapi':
  1965. request = '/open-api/' + request
  1966. elif api == 'private':
  1967. # private v2
  1968. request = '/' + self.version + '/' + api + '/' + request
  1969. elif api == 'privateLinear':
  1970. request = '/private/linear/' + request
  1971. else:
  1972. # position, user
  1973. request = '/' + api + '/' + request
  1974. timestamp = self.nonce()
  1975. query = self.extend(params, {
  1976. 'api_key': self.apiKey,
  1977. 'recv_window': self.options['recvWindow'],
  1978. 'timestamp': timestamp,
  1979. })
  1980. auth = self.rawencode(self.keysort(query))
  1981. signature = self.hmac(self.encode(auth), self.encode(self.secret))
  1982. if method == 'POST':
  1983. body = self.json(self.extend(query, {
  1984. 'sign': signature,
  1985. }))
  1986. headers = {
  1987. 'Content-Type': 'application/json',
  1988. }
  1989. else:
  1990. request += '?' + auth + '&sign=' + signature
  1991. url += request
  1992. return {'url': url, 'method': method, 'body': body, 'headers': headers}
  1993. def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
  1994. if not response:
  1995. return # fallback to default error handler
  1996. #
  1997. # {
  1998. # ret_code: 10001,
  1999. # ret_msg: 'ReadMapCB: expect {or n, but found \u0000, error ' +
  2000. # 'found in #0 byte of ...||..., bigger context ' +
  2001. # '...||...',
  2002. # ext_code: '',
  2003. # ext_info: '',
  2004. # result: null,
  2005. # time_now: '1583934106.590436'
  2006. # }
  2007. #
  2008. errorCode = self.safe_value(response, 'ret_code')
  2009. if errorCode != 0:
  2010. feedback = self.id + ' ' + body
  2011. self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
  2012. self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
  2013. raise ExchangeError(feedback) # unknown message