/python/ccxt/bybit.py

https://github.com/kroitor/ccxt · Python · 2053 lines · 1069 code · 40 blank · 944 comment · 143 complexity · da22c282b4e26a40469107db0765476d MD5 · raw file

Large files are truncated click here to view the full 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. # "res…