/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
- # -*- coding: utf-8 -*-
- # PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
- # https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
- from ccxt.base.exchange import Exchange
- from ccxt.base.errors import ExchangeError
- from ccxt.base.errors import AuthenticationError
- from ccxt.base.errors import PermissionDenied
- from ccxt.base.errors import ArgumentsRequired
- from ccxt.base.errors import BadRequest
- from ccxt.base.errors import InsufficientFunds
- from ccxt.base.errors import InvalidOrder
- from ccxt.base.errors import OrderNotFound
- from ccxt.base.errors import RateLimitExceeded
- from ccxt.base.errors import InvalidNonce
- from ccxt.base.decimal_to_precision import TICK_SIZE
- class bybit(Exchange):
- def describe(self):
- return self.deep_extend(super(bybit, self).describe(), {
- 'id': 'bybit',
- 'name': 'Bybit',
- 'countries': ['VG'], # British Virgin Islands
- 'version': 'v2',
- 'userAgent': None,
- 'rateLimit': 100,
- 'has': {
- 'cancelOrder': True,
- 'CORS': True,
- 'cancelAllOrders': True,
- 'createOrder': True,
- 'editOrder': True,
- 'fetchBalance': True,
- 'fetchClosedOrders': True,
- 'fetchDeposits': True,
- 'fetchLedger': True,
- 'fetchMarkets': True,
- 'fetchMyTrades': True,
- 'fetchOHLCV': True,
- 'fetchOpenOrders': True,
- 'fetchOrder': True,
- 'fetchOrderBook': True,
- 'fetchOrders': True,
- 'fetchOrderTrades': True,
- 'fetchTicker': True,
- 'fetchTickers': True,
- 'fetchTime': True,
- 'fetchTrades': True,
- 'fetchTransactions': False,
- 'fetchWithdrawals': True,
- },
- 'timeframes': {
- '1m': '1',
- '3m': '3',
- '5m': '5',
- '15m': '15',
- '30m': '30',
- '1h': '60',
- '2h': '120',
- '4h': '240',
- '6h': '360',
- '12h': '720',
- '1d': 'D',
- '1w': 'W',
- '1M': 'M',
- '1y': 'Y',
- },
- 'urls': {
- 'test': 'https://api-testnet.bybit.com',
- 'logo': 'https://user-images.githubusercontent.com/51840849/76547799-daff5b80-649e-11ea-87fb-3be9bac08954.jpg',
- 'api': 'https://api.bybit.com',
- 'www': 'https://www.bybit.com',
- 'doc': [
- 'https://bybit-exchange.github.io/docs/inverse/',
- 'https://bybit-exchange.github.io/docs/linear/',
- 'https://github.com/bybit-exchange',
- ],
- 'fees': 'https://help.bybit.com/hc/en-us/articles/360039261154',
- 'referral': 'https://www.bybit.com/app/register?ref=X7Prm',
- },
- 'api': {
- 'public': {
- 'get': [
- 'orderBook/L2',
- 'kline/list',
- 'tickers',
- 'trading-records',
- 'symbols',
- 'time',
- 'announcement',
- ],
- },
- 'private': {
- 'get': [
- 'order',
- 'stop-order',
- 'position/list',
- 'wallet/balance',
- 'execution/list',
- ],
- 'post': [
- 'order/create',
- 'order/cancel',
- 'order/cancelAll',
- 'stop-order/cancelAll',
- ],
- },
- 'openapi': {
- 'get': [
- 'order/list',
- 'stop-order/list',
- 'wallet/risk-limit/list',
- 'wallet/risk-limit',
- 'funding/prev-funding-rate',
- 'funding/prev-funding',
- 'funding/predicted-funding',
- 'api-key',
- 'wallet/fund/records',
- 'wallet/withdraw/list',
- ],
- 'post': [
- 'order/replace',
- 'stop-order/create',
- 'stop-order/cancel',
- 'stop-order/replace',
- 'position/trading-stop',
- ],
- },
- 'publicLinear': {
- 'get': [
- 'kline',
- 'recent-trading-records',
- 'funding/prev-funding-rate',
- 'mark-price-kline',
- ],
- },
- 'privateLinear': {
- 'get': [
- 'order/list',
- 'order/search',
- 'stop-order/list',
- 'stop-order/search',
- 'position/list',
- 'trade/execution/list',
- 'trade/closed-pnl/list',
- 'risk-limit',
- 'funding/prev-funding',
- 'funding/predicted-funding',
- ],
- 'post': [
- 'order/create',
- 'order/cancel',
- 'order/cancelAll',
- 'order/replace',
- 'stop-order/create',
- 'stop-order/cancel',
- 'stop-order/cancelAll',
- 'stop-order/replace',
- 'position/switch-isolated',
- 'position/set-auto-add-margin',
- 'position/set-leverage',
- 'position/trading-stop',
- 'position/add-margin',
- ],
- },
- 'position': {
- 'post': [
- 'change-position-margin',
- ],
- },
- 'user': {
- 'get': [
- 'leverage',
- ],
- 'post': [
- 'leverage/save',
- ],
- },
- },
- 'httpExceptions': {
- '403': RateLimitExceeded, # Forbidden -- You request too many times
- },
- 'exceptions': {
- 'exact': {
- '10001': BadRequest, # parameter error
- '10002': InvalidNonce, # request expired, check your timestamp and recv_window
- '10003': AuthenticationError, # Invalid apikey
- '10004': AuthenticationError, # invalid sign
- '10005': PermissionDenied, # permission denied for current apikey
- '10006': RateLimitExceeded, # too many requests
- '10007': AuthenticationError, # api_key not found in your request parameters
- '10010': PermissionDenied, # request ip mismatch
- '10017': BadRequest, # request path not found or request method is invalid
- '20001': OrderNotFound, # Order not exists
- '20003': InvalidOrder, # missing parameter side
- '20004': InvalidOrder, # invalid parameter side
- '20005': InvalidOrder, # missing parameter symbol
- '20006': InvalidOrder, # invalid parameter symbol
- '20007': InvalidOrder, # missing parameter order_type
- '20008': InvalidOrder, # invalid parameter order_type
- '20009': InvalidOrder, # missing parameter qty
- '20010': InvalidOrder, # qty must be greater than 0
- '20011': InvalidOrder, # qty must be an integer
- '20012': InvalidOrder, # qty must be greater than zero and less than 1 million
- '20013': InvalidOrder, # missing parameter price
- '20014': InvalidOrder, # price must be greater than 0
- '20015': InvalidOrder, # missing parameter time_in_force
- '20016': InvalidOrder, # invalid value for parameter time_in_force
- '20017': InvalidOrder, # missing parameter order_id
- '20018': InvalidOrder, # invalid date format
- '20019': InvalidOrder, # missing parameter stop_px
- '20020': InvalidOrder, # missing parameter base_price
- '20021': InvalidOrder, # missing parameter stop_order_id
- '20022': BadRequest, # missing parameter leverage
- '20023': BadRequest, # leverage must be a number
- '20031': BadRequest, # leverage must be greater than zero
- '20070': BadRequest, # missing parameter margin
- '20071': BadRequest, # margin must be greater than zero
- '20084': BadRequest, # order_id or order_link_id is required
- '30001': BadRequest, # order_link_id is repeated
- '30003': InvalidOrder, # qty must be more than the minimum allowed
- '30004': InvalidOrder, # qty must be less than the maximum allowed
- '30005': InvalidOrder, # price exceeds maximum allowed
- '30007': InvalidOrder, # price exceeds minimum allowed
- '30008': InvalidOrder, # invalid order_type
- '30009': ExchangeError, # no position found
- '30010': InsufficientFunds, # insufficient wallet balance
- '30011': PermissionDenied, # operation not allowed as position is undergoing liquidation
- '30012': PermissionDenied, # operation not allowed as position is undergoing ADL
- '30013': PermissionDenied, # position is in liq or adl status
- '30014': InvalidOrder, # invalid closing order, qty should not greater than size
- '30015': InvalidOrder, # invalid closing order, side should be opposite
- '30016': ExchangeError, # TS and SL must be cancelled first while closing position
- '30017': InvalidOrder, # estimated fill price cannot be lower than current Buy liq_price
- '30018': InvalidOrder, # estimated fill price cannot be higher than current Sell liq_price
- '30019': InvalidOrder, # cannot attach TP/SL params for non-zero position when placing non-opening position order
- '30020': InvalidOrder, # position already has TP/SL params
- '30021': InvalidOrder, # cannot afford estimated position_margin
- '30022': InvalidOrder, # estimated buy liq_price cannot be higher than current mark_price
- '30023': InvalidOrder, # estimated sell liq_price cannot be lower than current mark_price
- '30024': InvalidOrder, # cannot set TP/SL/TS for zero-position
- '30025': InvalidOrder, # trigger price should bigger than 10% of last price
- '30026': InvalidOrder, # price too high
- '30027': InvalidOrder, # price set for Take profit should be higher than Last Traded Price
- '30028': InvalidOrder, # price set for Stop loss should be between Liquidation price and Last Traded Price
- '30029': InvalidOrder, # price set for Stop loss should be between Last Traded Price and Liquidation price
- '30030': InvalidOrder, # price set for Take profit should be lower than Last Traded Price
- '30031': InsufficientFunds, # insufficient available balance for order cost
- '30032': InvalidOrder, # order has been filled or cancelled
- '30033': RateLimitExceeded, # The number of stop orders exceeds maximum limit allowed
- '30034': OrderNotFound, # no order found
- '30035': RateLimitExceeded, # too fast to cancel
- '30036': ExchangeError, # the expected position value after order execution exceeds the current risk limit
- '30037': InvalidOrder, # order already cancelled
- '30041': ExchangeError, # no position found
- '30042': InsufficientFunds, # insufficient wallet balance
- '30043': PermissionDenied, # operation not allowed as position is undergoing liquidation
- '30044': PermissionDenied, # operation not allowed as position is undergoing AD
- '30045': PermissionDenied, # operation not allowed as position is not normal status
- '30049': InsufficientFunds, # insufficient available balance
- '30050': ExchangeError, # any adjustments made will trigger immediate liquidation
- '30051': ExchangeError, # due to risk limit, cannot adjust leverage
- '30052': ExchangeError, # leverage can not less than 1
- '30054': ExchangeError, # position margin is invalid
- '30057': ExchangeError, # requested quantity of contracts exceeds risk limit
- '30063': ExchangeError, # reduce-only rule not satisfied
- '30067': InsufficientFunds, # insufficient available balance
- '30068': ExchangeError, # exit value must be positive
- '34026': ExchangeError, # the limit is no change
- },
- 'broad': {
- '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}
- 'invalid api_key': AuthenticationError, # {"ret_code":10003,"ret_msg":"invalid api_key","ext_code":"","ext_info":"","result":null,"time_now":"1599547085.415797"}
- },
- },
- 'precisionMode': TICK_SIZE,
- 'options': {
- 'marketTypes': {
- 'BTC/USDT': 'linear',
- },
- 'code': 'BTC',
- 'fetchBalance': {
- 'code': 'BTC',
- },
- 'cancelAllOrders': {
- 'method': 'privatePostOrderCancelAll', # privatePostStopOrderCancelAll
- },
- 'recvWindow': 5 * 1000, # 5 sec default
- 'timeDifference': 0, # the difference between system clock and Binance clock
- 'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
- },
- 'fees': {
- 'trading': {
- 'tierBased': False,
- 'percentage': True,
- 'taker': 0.00075,
- 'maker': -0.00025,
- },
- 'funding': {
- 'tierBased': False,
- 'percentage': False,
- 'withdraw': {},
- 'deposit': {},
- },
- },
- })
- def nonce(self):
- return self.milliseconds() - self.options['timeDifference']
- def load_time_difference(self, params={}):
- serverTime = self.fetch_time(params)
- after = self.milliseconds()
- self.options['timeDifference'] = after - serverTime
- return self.options['timeDifference']
- def fetch_time(self, params={}):
- response = self.publicGetTime(params)
- #
- # {
- # ret_code: 0,
- # ret_msg: 'OK',
- # ext_code: '',
- # ext_info: '',
- # result: {},
- # time_now: '1583933682.448826'
- # }
- #
- return self.safe_timestamp(response, 'time_now')
- def fetch_markets(self, params={}):
- if self.options['adjustForTimeDifference']:
- self.load_time_difference()
- response = self.publicGetSymbols(params)
- #
- # {
- # ret_code: 0,
- # ret_msg: 'OK',
- # ext_code: '',
- # ext_info: '',
- # result: [
- # {
- # name: 'BTCUSD',
- # base_currency: 'BTC',
- # quote_currency: 'USD',
- # price_scale: 2,
- # taker_fee: '0.00075',
- # maker_fee: '-0.00025',
- # leverage_filter: {min_leverage: 1, max_leverage: 100, leverage_step: '0.01'},
- # price_filter: {min_price: '0.5', max_price: '999999.5', tick_size: '0.5'},
- # lot_size_filter: {max_trading_qty: 1000000, min_trading_qty: 1, qty_step: 1}
- # },
- # ],
- # time_now: '1583930495.454196'
- # }
- #
- markets = self.safe_value(response, 'result', [])
- options = self.safe_value(self.options, 'fetchMarkets', {})
- linearQuoteCurrencies = self.safe_value(options, 'linear', {'USDT': True})
- result = []
- for i in range(0, len(markets)):
- market = markets[i]
- id = self.safe_string(market, 'name')
- baseId = self.safe_string(market, 'base_currency')
- quoteId = self.safe_string(market, 'quote_currency')
- base = self.safe_currency_code(baseId)
- quote = self.safe_currency_code(quoteId)
- linear = (quote in linearQuoteCurrencies)
- inverse = not linear
- symbol = base + '/' + quote
- baseQuote = base + quote
- if baseQuote != id:
- symbol = id
- lotSizeFilter = self.safe_value(market, 'lot_size_filter', {})
- priceFilter = self.safe_value(market, 'price_filter', {})
- precision = {
- 'amount': self.safe_float(lotSizeFilter, 'qty_step'),
- 'price': self.safe_float(priceFilter, 'tick_size'),
- }
- result.append({
- 'id': id,
- 'symbol': symbol,
- 'base': base,
- 'quote': quote,
- 'active': None,
- 'precision': precision,
- 'taker': self.safe_float(market, 'taker_fee'),
- 'maker': self.safe_float(market, 'maker_fee'),
- 'type': 'future',
- 'spot': False,
- 'future': True,
- 'option': False,
- 'linear': linear,
- 'inverse': inverse,
- 'limits': {
- 'amount': {
- 'min': self.safe_float(lotSizeFilter, 'min_trading_qty'),
- 'max': self.safe_float(lotSizeFilter, 'max_trading_qty'),
- },
- 'price': {
- 'min': self.safe_float(priceFilter, 'min_price'),
- 'max': self.safe_float(priceFilter, 'max_price'),
- },
- 'cost': {
- 'min': None,
- 'max': None,
- },
- },
- 'info': market,
- })
- return result
- def fetch_balance(self, params={}):
- self.load_markets()
- defaultCode = self.safe_value(self.options, 'code', 'BTC')
- options = self.safe_value(self.options, 'fetchBalance', {})
- code = self.safe_value(options, 'code', defaultCode)
- currency = self.currency(code)
- request = {
- 'coin': currency['id'],
- }
- response = self.privateGetWalletBalance(self.extend(request, params))
- #
- # {
- # ret_code: 0,
- # ret_msg: 'OK',
- # ext_code: '',
- # ext_info: '',
- # result: {
- # BTC: {
- # equity: 0,
- # available_balance: 0,
- # used_margin: 0,
- # order_margin: 0,
- # position_margin: 0,
- # occ_closing_fee: 0,
- # occ_funding_fee: 0,
- # wallet_balance: 0,
- # realised_pnl: 0,
- # unrealised_pnl: 0,
- # cum_realised_pnl: 0,
- # given_cash: 0,
- # service_cash: 0
- # }
- # },
- # time_now: '1583937810.370020',
- # rate_limit_status: 119,
- # rate_limit_reset_ms: 1583937810367,
- # rate_limit: 120
- # }
- #
- result = {
- 'info': response,
- }
- balances = self.safe_value(response, 'result', {})
- currencyIds = list(balances.keys())
- for i in range(0, len(currencyIds)):
- currencyId = currencyIds[i]
- balance = balances[currencyId]
- code = self.safe_currency_code(currencyId)
- account = self.account()
- account['free'] = self.safe_float(balance, 'available_balance')
- account['used'] = self.safe_float(balance, 'used_margin')
- account['total'] = self.safe_float(balance, 'equity')
- result[code] = account
- return self.parse_balance(result)
- def parse_ticker(self, ticker, market=None):
- #
- # fetchTicker
- #
- # {
- # symbol: 'BTCUSD',
- # bid_price: '7680',
- # ask_price: '7680.5',
- # last_price: '7680.00',
- # last_tick_direction: 'MinusTick',
- # prev_price_24h: '7870.50',
- # price_24h_pcnt: '-0.024204',
- # high_price_24h: '8035.00',
- # low_price_24h: '7671.00',
- # prev_price_1h: '7780.00',
- # price_1h_pcnt: '-0.012853',
- # mark_price: '7683.27',
- # index_price: '7682.74',
- # open_interest: 188829147,
- # open_value: '23670.06',
- # total_turnover: '25744224.90',
- # turnover_24h: '102997.83',
- # total_volume: 225448878806,
- # volume_24h: 809919408,
- # funding_rate: '0.0001',
- # predicted_funding_rate: '0.0001',
- # next_funding_time: '2020-03-12T00:00:00Z',
- # countdown_hour: 7
- # }
- #
- timestamp = None
- marketId = self.safe_string(ticker, 'symbol')
- symbol = marketId
- if marketId in self.markets_by_id:
- market = self.markets_by_id[marketId]
- if (symbol is None) and (market is not None):
- symbol = market['symbol']
- last = self.safe_float(ticker, 'last_price')
- open = self.safe_float(ticker, 'prev_price_24h')
- percentage = self.safe_float(ticker, 'price_24h_pcnt')
- if percentage is not None:
- percentage *= 100
- change = None
- average = None
- if (last is not None) and (open is not None):
- change = last - open
- average = self.sum(open, last) / 2
- baseVolume = self.safe_float(ticker, 'turnover_24h')
- quoteVolume = self.safe_float(ticker, 'volume_24h')
- vwap = self.vwap(baseVolume, quoteVolume)
- return {
- 'symbol': symbol,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'high': self.safe_float(ticker, 'high_price_24h'),
- 'low': self.safe_float(ticker, 'low_price_24h'),
- 'bid': self.safe_float(ticker, 'bid_price'),
- 'bidVolume': None,
- 'ask': self.safe_float(ticker, 'ask_price'),
- 'askVolume': None,
- 'vwap': vwap,
- 'open': open,
- 'close': last,
- 'last': last,
- 'previousClose': None,
- 'change': change,
- 'percentage': percentage,
- 'average': average,
- 'baseVolume': baseVolume,
- 'quoteVolume': quoteVolume,
- 'info': ticker,
- }
- def fetch_ticker(self, symbol, params={}):
- self.load_markets()
- market = self.market(symbol)
- request = {
- 'symbol': market['id'],
- }
- response = self.publicGetTickers(self.extend(request, params))
- #
- # {
- # ret_code: 0,
- # ret_msg: 'OK',
- # ext_code: '',
- # ext_info: '',
- # result: [
- # {
- # symbol: 'BTCUSD',
- # bid_price: '7680',
- # ask_price: '7680.5',
- # last_price: '7680.00',
- # last_tick_direction: 'MinusTick',
- # prev_price_24h: '7870.50',
- # price_24h_pcnt: '-0.024204',
- # high_price_24h: '8035.00',
- # low_price_24h: '7671.00',
- # prev_price_1h: '7780.00',
- # price_1h_pcnt: '-0.012853',
- # mark_price: '7683.27',
- # index_price: '7682.74',
- # open_interest: 188829147,
- # open_value: '23670.06',
- # total_turnover: '25744224.90',
- # turnover_24h: '102997.83',
- # total_volume: 225448878806,
- # volume_24h: 809919408,
- # funding_rate: '0.0001',
- # predicted_funding_rate: '0.0001',
- # next_funding_time: '2020-03-12T00:00:00Z',
- # countdown_hour: 7
- # }
- # ],
- # time_now: '1583948195.818255'
- # }
- #
- result = self.safe_value(response, 'result', [])
- first = self.safe_value(result, 0)
- timestamp = self.safe_timestamp(response, 'time_now')
- ticker = self.parse_ticker(first, market)
- ticker['timestamp'] = timestamp
- ticker['datetime'] = self.iso8601(timestamp)
- return ticker
- def fetch_tickers(self, symbols=None, params={}):
- self.load_markets()
- response = self.publicGetTickers(params)
- #
- # {
- # ret_code: 0,
- # ret_msg: 'OK',
- # ext_code: '',
- # ext_info: '',
- # result: [
- # {
- # symbol: 'BTCUSD',
- # bid_price: '7680',
- # ask_price: '7680.5',
- # last_price: '7680.00',
- # last_tick_direction: 'MinusTick',
- # prev_price_24h: '7870.50',
- # price_24h_pcnt: '-0.024204',
- # high_price_24h: '8035.00',
- # low_price_24h: '7671.00',
- # prev_price_1h: '7780.00',
- # price_1h_pcnt: '-0.012853',
- # mark_price: '7683.27',
- # index_price: '7682.74',
- # open_interest: 188829147,
- # open_value: '23670.06',
- # total_turnover: '25744224.90',
- # turnover_24h: '102997.83',
- # total_volume: 225448878806,
- # volume_24h: 809919408,
- # funding_rate: '0.0001',
- # predicted_funding_rate: '0.0001',
- # next_funding_time: '2020-03-12T00:00:00Z',
- # countdown_hour: 7
- # }
- # ],
- # time_now: '1583948195.818255'
- # }
- #
- result = self.safe_value(response, 'result', [])
- tickers = {}
- for i in range(0, len(result)):
- ticker = self.parse_ticker(result[i])
- symbol = ticker['symbol']
- tickers[symbol] = ticker
- return self.filter_by_array(tickers, 'symbol', symbols)
- def parse_ohlcv(self, ohlcv, market=None):
- #
- # inverse perpetual BTC/USD
- #
- # {
- # symbol: 'BTCUSD',
- # interval: '1',
- # open_time: 1583952540,
- # open: '7760.5',
- # high: '7764',
- # low: '7757',
- # close: '7763.5',
- # volume: '1259766',
- # turnover: '162.32773718999994'
- # }
- #
- # linear perpetual BTC/USDT
- #
- # {
- # "id":143536,
- # "symbol":"BTCUSDT",
- # "period":"15",
- # "start_at":1587883500,
- # "volume":1.035,
- # "open":7540.5,
- # "high":7541,
- # "low":7540.5,
- # "close":7541
- # }
- #
- return [
- self.safe_timestamp_2(ohlcv, 'open_time', 'start_at'),
- self.safe_float(ohlcv, 'open'),
- self.safe_float(ohlcv, 'high'),
- self.safe_float(ohlcv, 'low'),
- self.safe_float(ohlcv, 'close'),
- self.safe_float_2(ohlcv, 'turnover', 'volume'),
- ]
- def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
- self.load_markets()
- market = self.market(symbol)
- request = {
- 'symbol': market['id'],
- 'interval': self.timeframes[timeframe],
- }
- duration = self.parse_timeframe(timeframe)
- now = self.seconds()
- if since is None:
- if limit is None:
- raise ArgumentsRequired(self.id + ' fetchOHLCV requires a since argument or a limit argument')
- else:
- request['from'] = now - limit * duration
- else:
- request['from'] = int(since / 1000)
- if limit is not None:
- request['limit'] = limit # max 200, default 200
- marketTypes = self.safe_value(self.options, 'marketTypes', {})
- marketType = self.safe_string(marketTypes, symbol)
- method = 'publicLinearGetKline' if (marketType == 'linear') else 'publicGetKlineList'
- response = getattr(self, method)(self.extend(request, params))
- #
- # inverse perpetual BTC/USD
- #
- # {
- # ret_code: 0,
- # ret_msg: 'OK',
- # ext_code: '',
- # ext_info: '',
- # result: [
- # {
- # symbol: 'BTCUSD',
- # interval: '1',
- # open_time: 1583952540,
- # open: '7760.5',
- # high: '7764',
- # low: '7757',
- # close: '7763.5',
- # volume: '1259766',
- # turnover: '162.32773718999994'
- # },
- # ],
- # time_now: '1583953082.397330'
- # }
- #
- # linear perpetual BTC/USDT
- #
- # {
- # "ret_code":0,
- # "ret_msg":"OK",
- # "ext_code":"",
- # "ext_info":"",
- # "result":[
- # {
- # "id":143536,
- # "symbol":"BTCUSDT",
- # "period":"15",
- # "start_at":1587883500,
- # "volume":1.035,
- # "open":7540.5,
- # "high":7541,
- # "low":7540.5,
- # "close":7541
- # }
- # ],
- # "time_now":"1587884120.168077"
- # }
- #
- result = self.safe_value(response, 'result', {})
- return self.parse_ohlcvs(result, market, timeframe, since, limit)
- def parse_trade(self, trade, market=None):
- #
- # fetchTrades(public)
- #
- # {
- # id: 43785688,
- # symbol: 'BTCUSD',
- # price: 7786,
- # qty: 67,
- # side: 'Sell',
- # time: '2020-03-11T19:18:30.123Z'
- # }
- #
- # fetchMyTrades, fetchOrderTrades(private)
- #
- # {
- # "closed_size": 0,
- # "cross_seq": 277136382,
- # "exec_fee": "0.0000001",
- # "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68",
- # "exec_price": "8178.5",
- # "exec_qty": 1,
- # # the docs say the exec_time field is "abandoned" now
- # # the user should use "trade_time_ms"
- # "exec_time": "1571676941.70682",
- # "exec_type": "Trade", #Exec Type Enum
- # "exec_value": "0.00012227",
- # "fee_rate": "0.00075",
- # "last_liquidity_ind": "RemovedLiquidity", #Liquidity Enum
- # "leaves_qty": 0,
- # "nth_fill": 2,
- # "order_id": "7ad50cb1-9ad0-4f74-804b-d82a516e1029",
- # "order_link_id": "",
- # "order_price": "8178",
- # "order_qty": 1,
- # "order_type": "Market", #Order Type Enum
- # "side": "Buy", #Side Enum
- # "symbol": "BTCUSD", #Symbol Enum
- # "user_id": 1,
- # "trade_time_ms": 1577480599000
- # }
- #
- id = self.safe_string_2(trade, 'id', 'exec_id')
- symbol = None
- base = None
- marketId = self.safe_string(trade, 'symbol')
- amount = self.safe_float_2(trade, 'qty', 'exec_qty')
- cost = self.safe_float(trade, 'exec_value')
- price = self.safe_float_2(trade, 'price', 'exec_price')
- if marketId in self.markets_by_id:
- market = self.markets_by_id[marketId]
- symbol = market['symbol']
- base = market['base']
- if market is not None:
- if symbol is None:
- symbol = market['symbol']
- base = market['base']
- if cost is None:
- if amount is not None:
- if price is not None:
- cost = amount * price
- timestamp = self.parse8601(self.safe_string(trade, 'time'))
- if timestamp is None:
- timestamp = self.safe_integer(trade, 'trade_time_ms')
- side = self.safe_string_lower(trade, 'side')
- lastLiquidityInd = self.safe_string(trade, 'last_liquidity_ind')
- takerOrMaker = 'maker' if (lastLiquidityInd == 'AddedLiquidity') else 'taker'
- feeCost = self.safe_float(trade, 'exec_fee')
- fee = None
- if feeCost is not None:
- fee = {
- 'cost': feeCost,
- 'currency': base,
- 'rate': self.safe_float(trade, 'fee_rate'),
- }
- return {
- 'id': id,
- 'info': trade,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'symbol': symbol,
- 'order': self.safe_string(trade, 'order_id'),
- 'type': self.safe_string_lower(trade, 'order_type'),
- 'side': side,
- 'takerOrMaker': takerOrMaker,
- 'price': price,
- 'amount': amount,
- 'cost': cost,
- 'fee': fee,
- }
- def fetch_trades(self, symbol, since=None, limit=None, params={}):
- self.load_markets()
- market = self.market(symbol)
- request = {
- 'symbol': market['id'],
- # 'from': 123, # from id
- }
- if limit is not None:
- request['count'] = limit # default 500, max 1000
- marketTypes = self.safe_value(self.options, 'marketTypes', {})
- marketType = self.safe_string(marketTypes, symbol)
- method = 'publicLinearGetRecentTradingRecords' if (marketType == 'linear') else 'publicGetTradingRecords'
- response = getattr(self, method)(self.extend(request, params))
- #
- # {
- # ret_code: 0,
- # ret_msg: 'OK',
- # ext_code: '',
- # ext_info: '',
- # result: [
- # {
- # id: 43785688,
- # symbol: 'BTCUSD',
- # price: 7786,
- # qty: 67,
- # side: 'Sell',
- # time: '2020-03-11T19:18:30.123Z'
- # },
- # ],
- # time_now: '1583954313.393362'
- # }
- #
- result = self.safe_value(response, 'result', {})
- return self.parse_trades(result, market, since, limit)
- def parse_order_book(self, orderbook, timestamp=None, bidsKey='Buy', asksKey='Sell', priceKey='price', amountKey='size'):
- bids = []
- asks = []
- for i in range(0, len(orderbook)):
- bidask = orderbook[i]
- side = self.safe_string(bidask, 'side')
- if side == 'Buy':
- bids.append(self.parse_bid_ask(bidask, priceKey, amountKey))
- elif side == 'Sell':
- asks.append(self.parse_bid_ask(bidask, priceKey, amountKey))
- else:
- raise ExchangeError(self.id + ' parseOrderBook encountered an unrecognized bidask format: ' + self.json(bidask))
- return {
- 'bids': self.sort_by(bids, 0, True),
- 'asks': self.sort_by(asks, 0),
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'nonce': None,
- }
- def fetch_order_book(self, symbol, limit=None, params={}):
- self.load_markets()
- market = self.market(symbol)
- request = {
- 'symbol': market['id'],
- }
- response = self.publicGetOrderBookL2(self.extend(request, params))
- #
- # {
- # ret_code: 0,
- # ret_msg: 'OK',
- # ext_code: '',
- # ext_info: '',
- # result: [
- # {symbol: 'BTCUSD', price: '7767.5', size: 677956, side: 'Buy'},
- # {symbol: 'BTCUSD', price: '7767', size: 580690, side: 'Buy'},
- # {symbol: 'BTCUSD', price: '7766.5', size: 475252, side: 'Buy'},
- # {symbol: 'BTCUSD', price: '7768', size: 330847, side: 'Sell'},
- # {symbol: 'BTCUSD', price: '7768.5', size: 97159, side: 'Sell'},
- # {symbol: 'BTCUSD', price: '7769', size: 6508, side: 'Sell'},
- # ],
- # time_now: '1583954829.874823'
- # }
- #
- result = self.safe_value(response, 'result', [])
- timestamp = self.safe_timestamp(response, 'time_now')
- return self.parse_order_book(result, timestamp, 'Buy', 'Sell', 'price', 'size')
- def parse_order_status(self, status):
- statuses = {
- # basic orders
- 'Created': 'open',
- 'Rejected': 'rejected', # order is triggered but failed upon being placed
- 'New': 'open',
- 'PartiallyFilled': 'open',
- 'Filled': 'closed',
- 'Cancelled': 'canceled',
- 'PendingCancel': 'canceling', # the engine has received the cancellation but there is no guarantee that it will be successful
- # conditional orders
- 'Active': 'open', # order is triggered and placed successfully
- 'Untriggered': 'open', # order waits to be triggered
- 'Triggered': 'closed', # order is triggered
- # 'Cancelled': 'canceled', # order is cancelled
- # 'Rejected': 'rejected', # order is triggered but fail to be placed
- 'Deactivated': 'canceled', # conditional order was cancelled before triggering
- }
- return self.safe_string(statuses, status, status)
- def parse_order(self, order, market=None):
- #
- # createOrder
- #
- # {
- # "user_id": 1,
- # "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b",
- # "symbol": "BTCUSD",
- # "side": "Buy",
- # "order_type": "Limit",
- # "price": 8800,
- # "qty": 1,
- # "time_in_force": "GoodTillCancel",
- # "order_status": "Created",
- # "last_exec_time": 0,
- # "last_exec_price": 0,
- # "leaves_qty": 1,
- # "cum_exec_qty": 0, # in contracts, where 1 contract = 1 quote currency unit(USD for inverse contracts)
- # "cum_exec_value": 0, # in contract's underlying currency(BTC for inverse contracts)
- # "cum_exec_fee": 0,
- # "reject_reason": "",
- # "order_link_id": "",
- # "created_at": "2019-11-30T11:03:43.452Z",
- # "updated_at": "2019-11-30T11:03:43.455Z"
- # }
- #
- # fetchOrder
- #
- # {
- # "user_id" : 599946,
- # "symbol" : "BTCUSD",
- # "side" : "Buy",
- # "order_type" : "Limit",
- # "price" : "7948",
- # "qty" : 10,
- # "time_in_force" : "GoodTillCancel",
- # "order_status" : "Filled",
- # "ext_fields" : {
- # "o_req_num" : -1600687220498,
- # "xreq_type" : "x_create"
- # },
- # "last_exec_time" : "1588150113.968422",
- # "last_exec_price" : "7948",
- # "leaves_qty" : 0,
- # "leaves_value" : "0",
- # "cum_exec_qty" : 10,
- # "cum_exec_value" : "0.00125817",
- # "cum_exec_fee" : "-0.00000031",
- # "reject_reason" : "",
- # "cancel_type" : "",
- # "order_link_id" : "",
- # "created_at" : "2020-04-29T08:45:24.399146Z",
- # "updated_at" : "2020-04-29T08:48:33.968422Z",
- # "order_id" : "dd2504b9-0157-406a-99e1-efa522373944"
- # }
- #
- # conditional order
- #
- # {
- # "user_id":##,
- # "symbol":"BTCUSD",
- # "side":"Buy",
- # "order_type":"Market",
- # "price":0,
- # "qty":10,
- # "time_in_force":"GoodTillCancel",
- # "stop_order_type":"Stop",
- # "trigger_by":"LastPrice",
- # "base_price":11833,
- # "order_status":"Untriggered",
- # "ext_fields":{
- # "stop_order_type":"Stop",
- # "trigger_by":"LastPrice",
- # "base_price":11833,
- # "expected_direction":"Rising",
- # "trigger_price":12400,
- # "close_on_trigger":true,
- # "op_from":"api",
- # "remark":"145.53.159.48",
- # "o_req_num":0
- # },
- # "leaves_qty":10,
- # "leaves_value":0.00080645,
- # "reject_reason":null,
- # "cross_seq":-1,
- # "created_at":"2020-08-21T09:18:48.000Z",
- # "updated_at":"2020-08-21T09:18:48.000Z",
- # "stop_px":12400,
- # "stop_order_id":"3f3b54b1-3379-42c7-8510-44f4d9915be0"
- # }
- #
- marketId = self.safe_string(order, 'symbol')
- symbol = None
- base = None
- if marketId in self.markets_by_id:
- market = self.markets_by_id[marketId]
- timestamp = self.parse8601(self.safe_string(order, 'created_at'))
- id = self.safe_string_2(order, 'order_id', 'stop_order_id')
- price = self.safe_float(order, 'price')
- average = self.safe_float(order, 'average_price')
- amount = self.safe_float(order, 'qty')
- cost = self.safe_float(order, 'cum_exec_value')
- filled = self.safe_float(order, 'cum_exec_qty')
- remaining = self.safe_float(order, 'leaves_qty')
- if market is not None:
- symbol = market['symbol']
- base = market['base']
- lastTradeTimestamp = self.safe_timestamp(order, 'last_exec_time')
- if lastTradeTimestamp == 0:
- lastTradeTimestamp = None
- if (filled is None) and (amount is not None) and (remaining is not None):
- filled = amount - remaining
- if filled is not None:
- if (remaining is None) and (amount is not None):
- remaining = amount - filled
- if cost is None:
- if price is not None:
- cost = price * filled
- status = self.parse_order_status(self.safe_string_2(order, 'order_status', 'stop_order_status'))
- side = self.safe_string_lower(order, 'side')
- feeCost = self.safe_float(order, 'cum_exec_fee')
- fee = None
- if feeCost is not None:
- feeCost = abs(feeCost)
- fee = {
- 'cost': feeCost,
- 'currency': base,
- }
- type = self.safe_string_lower(order, 'order_type')
- clientOrderId = self.safe_string(order, 'order_link_id')
- if (clientOrderId is not None) and (len(clientOrderId) < 1):
- clientOrderId = None
- return {
- 'info': order,
- 'id': id,
- 'clientOrderId': clientOrderId,
- 'timestamp': timestamp,
- 'datetime': self.iso8601(timestamp),
- 'lastTradeTimestamp': lastTradeTimestamp,
- 'symbol': symbol,
- 'type': type,
- 'side': side,
- 'price': price,
- 'amount': amount,
- 'cost': cost,
- 'average': average,
- 'filled': filled,
- 'remaining': remaining,
- 'status': status,
- 'fee': fee,
- 'trades': None,
- }
- def fetch_order(self, id, symbol=None, params={}):
- if symbol is None:
- raise ArgumentsRequired(self.id + ' fetchOrder requires a symbol argument')
- self.load_markets()
- market = self.market(symbol)
- request = {
- 'symbol': market['id'],
- # 'order_link_id': 'string', # one of order_id, stop_order_id or order_link_id is required
- # regular orders ---------------------------------------------
- # 'order_id': id, # one of order_id or order_link_id is required for regular orders
- # conditional orders ---------------------------------------------
- # 'stop_order_id': id, # one of stop_order_id or order_link_id is required for conditional orders
- }
- marketTypes = self.safe_value(self.options, 'marketTypes', {})
- marketType = self.safe_string(marketTypes, symbol)
- method = 'privateLinearGetOrderSearch' if (marketType == 'linear') else 'privateGetOrder'
- stopOrderId = self.safe_string(params, 'stop_order_id')
- if stopOrderId is None:
- orderLinkId = self.safe_string(params, 'order_link_id')
- if orderLinkId is None:
- request['order_id'] = id
- else:
- method = 'privateLinearGetStopOrderSearch' if (marketType == 'linear') else 'privateGetStopOrder'
- response = getattr(self, method)(self.extend(request, params))
- #
- # {
- # "ret_code": 0,
- # "ret_msg": "OK",
- # "ext_code": "",
- # "ext_info": "",
- # "result": {
- # "user_id": 1,
- # "symbol": "BTCUSD",
- # "side": "Sell",
- # "order_type": "Limit",
- # "price": "8083",
- # "qty": 10,
- # "time_in_force": "GoodTillCancel",
- # "order_status": "New",
- # "ext_fields": {"o_req_num": -308787, "xreq_type": "x_create", "xreq_offset": 4154640},
- # "leaves_qty": 10,
- # "leaves_value": "0.00123716",
- # "cum_exec_qty": 0,
- # "reject_reason": "",
- # "order_link_id": "",
- # "created_at": "2019-10-21T07:28:19.396246Z",
- # "updated_at": "2019-10-21T07:28:19.396246Z",
- # "order_id": "efa44157-c355-4a98-b6d6-1d846a936b93"
- # },
- # "time_now": "1571651135.291930",
- # "rate_limit_status": 99, # The remaining number of accesses in one minute
- # "rate_limit_reset_ms": 1580885703683,
- # "rate_limit": 100
- # }
- #
- # conditional orders
- #
- # {
- # "ret_code": 0,
- # "ret_msg": "OK",
- # "ext_code": "",
- # "ext_info": "",
- # "res…