'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { TICK_SIZE } = require ('./base/functions/number'); const { AuthenticationError, ExchangeError, ArgumentsRequired, PermissionDenied, InvalidOrder, OrderNotFound, InsufficientFunds, BadRequest, RateLimitExceeded, InvalidNonce, NotSupported } = require ('./base/errors'); // --------------------------------------------------------------------------- module.exports = class bybit extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'bybit', 'name': 'Bybit', 'countries': [ 'VG' ], // British Virgin Islands 'version': 'v2', 'userAgent': undefined, 'rateLimit': 100, 'has': { 'CORS': true, 'fetchMarkets': true, 'fetchBalance': true, 'fetchOHLCV': true, 'editOrder': true, 'fetchOrder': true, 'fetchOrders': true, 'fetchOpenOrders': true, 'fetchClosedOrders': true, 'fetchMyTrades': true, 'fetchTicker': true, 'fetchTickers': true, 'fetchOrderTrades': true, 'createOrder': true, 'cancelOrder': true, 'cancelAllOrders': true, 'fetchTime': true, 'fetchWithdrawals': true, 'fetchDeposits': false, 'fetchTransactions': false, 'fetchLedger': 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', 'position/set-auto-add-margin', 'position/set-leverage', 'position/switch-isolated', 'position/trading-stop', 'position/add-margin', 'trade/execution/list', 'trade/closed-pnl/list', 'risk-limit', 'funding/prev-funding', 'funding/predicted-funding', ], 'post': [ 'order/create', 'order/cancel', 'order/cancelAll', 'stop-order/create', 'stop-order/cancel', 'stop-order/cancelAll', ], }, '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} }, }, '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': {}, }, }, }); } nonce () { return this.milliseconds () - this.options['timeDifference']; } async loadTimeDifference () { const serverTime = await this.fetchTime (); const after = this.milliseconds (); this.options['timeDifference'] = after - serverTime; return this.options['timeDifference']; } async fetchTime (params = {}) { const response = await this.publicGetTime (params); // // { // ret_code: 0, // ret_msg: 'OK', // ext_code: '', // ext_info: '', // result: {}, // time_now: '1583933682.448826' // } // return this.safeTimestamp (response, 'time_now'); } async fetchMarkets (params = {}) { if (this.options['adjustForTimeDifference']) { await this.loadTimeDifference (); } const response = await this.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' // } // const markets = this.safeValue (response, 'result', []); const options = this.safeValue (this.options, 'fetchMarkets', {}); const linearQuoteCurrencies = this.safeValue (options, 'linear', { 'USDT': true }); const result = []; for (let i = 0; i < markets.length; i++) { const market = markets[i]; const id = this.safeString (market, 'name'); const baseId = this.safeString (market, 'base_currency'); const quoteId = this.safeString (market, 'quote_currency'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const linear = (quote in linearQuoteCurrencies); const inverse = !linear; const symbol = base + '/' + quote; const lotSizeFilter = this.safeValue (market, 'lot_size_filter', {}); const priceFilter = this.safeValue (market, 'price_filter', {}); const precision = { 'amount': this.safeFloat (lotSizeFilter, 'qty_step'), 'price': this.safeFloat (priceFilter, 'tick_size'), }; result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'active': undefined, 'precision': precision, 'taker': this.safeFloat (market, 'taker_fee'), 'maker': this.safeFloat (market, 'maker_fee'), 'type': 'future', 'spot': false, 'future': true, 'option': false, 'linear': linear, 'inverse': inverse, 'limits': { 'amount': { 'min': this.safeFloat (lotSizeFilter, 'min_trading_qty'), 'max': this.safeFloat (lotSizeFilter, 'max_trading_qty'), }, 'price': { 'min': this.safeFloat (priceFilter, 'min_price'), 'max': this.safeFloat (priceFilter, 'max_price'), }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'info': market, }); } return result; } async fetchBalance (params = {}) { await this.loadMarkets (); const defaultCode = this.safeValue (this.options, 'code', 'BTC'); const options = this.safeValue (this.options, 'fetchBalance', {}); const code = this.safeValue (options, 'code', defaultCode); const currency = this.currency (code); const request = { 'coin': currency['id'], }; const response = await this.privateGetWalletBalance (this.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 // } // const result = { 'info': response, }; const balances = this.safeValue (response, 'result', {}); const currencyIds = Object.keys (balances); for (let i = 0; i < currencyIds.length; i++) { const currencyId = currencyIds[i]; const balance = balances[currencyId]; const code = this.safeCurrencyCode (currencyId); const account = this.account (); account['free'] = this.safeFloat (balance, 'available_balance'); account['used'] = this.safeFloat (balance, 'used_margin'); account['total'] = this.safeFloat (balance, 'equity'); result[code] = account; } return this.parseBalance (result); } parseTicker (ticker, market = undefined) { // // 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 // } // const timestamp = undefined; const marketId = this.safeString (ticker, 'symbol'); let symbol = marketId; if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; } if ((symbol === undefined) && (market !== undefined)) { symbol = market['symbol']; } const last = this.safeFloat (ticker, 'last_price'); const open = this.safeFloat (ticker, 'prev_price_24h'); let percentage = this.safeFloat (ticker, 'price_24h_pcnt'); if (percentage !== undefined) { percentage *= 100; } let change = undefined; let average = undefined; if ((last !== undefined) && (open !== undefined)) { change = last - open; average = this.sum (open, last) / 2; } const baseVolume = this.safeFloat (ticker, 'turnover_24h'); const quoteVolume = this.safeFloat (ticker, 'volume_24h'); let vwap = undefined; if (quoteVolume !== undefined && baseVolume !== undefined) { vwap = quoteVolume / baseVolume; } return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeFloat (ticker, 'high_price_24h'), 'low': this.safeFloat (ticker, 'low_price_24h'), 'bid': this.safeFloat (ticker, 'bid_price'), 'bidVolume': undefined, 'ask': this.safeFloat (ticker, 'ask_price'), 'askVolume': undefined, 'vwap': vwap, 'open': open, 'close': last, 'last': last, 'previousClose': undefined, 'change': change, 'percentage': percentage, 'average': average, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const response = await this.publicGetTickers (this.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' // } // const result = this.safeValue (response, 'result', []); const first = this.safeValue (result, 0); const timestamp = this.safeTimestamp (response, 'time_now'); const ticker = this.parseTicker (first, market); ticker['timestamp'] = timestamp; ticker['datetime'] = this.iso8601 (timestamp); return ticker; } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); const response = await this.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' // } // const result = this.safeValue (response, 'result', []); const tickers = {}; for (let i = 0; i < result.length; i++) { const ticker = this.parseTicker (result[i]); const symbol = ticker['symbol']; tickers[symbol] = ticker; } return this.filterByArray (tickers, 'symbol', symbols); } parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { // // { // symbol: 'BTCUSD', // interval: '1', // open_time: 1583952540, // open: '7760.5', // high: '7764', // low: '7757', // close: '7763.5', // volume: '1259766', // turnover: '162.32773718999994' // }, // // { // "id":143536, // "symbol":"BTCUSDT", // "period":"15", // "start_at":1587883500, // "volume":1.035, // "open":7540.5, // "high":7541, // "low":7540.5, // "close":7541 // } // return [ this.safeTimestamp2 (ohlcv, 'open_time', 'start_at'), this.safeFloat (ohlcv, 'open'), this.safeFloat (ohlcv, 'high'), this.safeFloat (ohlcv, 'low'), this.safeFloat (ohlcv, 'close'), this.safeFloat2 (ohlcv, 'turnover', 'volume'), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'interval': this.timeframes[timeframe], }; const duration = this.parseTimeframe (timeframe); const now = this.seconds (); if (since === undefined) { if (limit === undefined) { throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument or a limit argument'); } else { request['from'] = now - limit * duration; } } else { request['from'] = parseInt (since / 1000); } if (limit !== undefined) { request['limit'] = limit; // max 200, default 200 } const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeString (marketTypes, symbol); const method = (marketType === 'linear') ? 'publicLinearGetKline' : 'publicGetKlineList'; const response = await this[method] (this.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" // } // const result = this.safeValue (response, 'result', {}); return this.parseOHLCVs (result, market, timeframe, since, limit); } parseTrade (trade, market = undefined) { // // 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 // } // const id = this.safeString2 (trade, 'id', 'exec_id'); let symbol = undefined; let base = undefined; const marketId = this.safeString (trade, 'symbol'); let amount = this.safeFloat2 (trade, 'qty', 'exec_qty'); let cost = this.safeFloat (trade, 'exec_value'); const price = this.safeFloat2 (trade, 'price', 'exec_price'); if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; symbol = market['symbol']; base = market['base']; } if (market !== undefined) { if (symbol === undefined) { symbol = market['symbol']; base = market['base']; } // if private trade if ('exec_fee' in trade) { if (market['inverse']) { amount = this.safeFloat (trade, 'exec_value'); cost = this.safeFloat (trade, 'exec_qty'); } } } if (cost === undefined) { if (amount !== undefined) { if (price !== undefined) { cost = amount * price; } } } let timestamp = this.parse8601 (this.safeString (trade, 'time')); if (timestamp === undefined) { timestamp = this.safeInteger (trade, 'trade_time_ms'); } const side = this.safeStringLower (trade, 'side'); const lastLiquidityInd = this.safeString (trade, 'last_liquidity_ind'); const takerOrMaker = (lastLiquidityInd === 'AddedLiquidity') ? 'maker' : 'taker'; const feeCost = this.safeFloat (trade, 'exec_fee'); let fee = undefined; if (feeCost !== undefined) { fee = { 'cost': feeCost, 'currency': base, 'rate': this.safeFloat (trade, 'fee_rate'), }; } return { 'id': id, 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'order': this.safeString (trade, 'order_id'), 'type': this.safeStringLower (trade, 'order_type'), 'side': side, 'takerOrMaker': takerOrMaker, 'price': price, 'amount': amount, 'cost': cost, 'fee': fee, }; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], // 'from': 123, // from id }; if (limit !== undefined) { request['count'] = limit; // default 500, max 1000 } const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeString (marketTypes, symbol); const method = (marketType === 'linear') ? 'publicLinearGetRecentTradingRecords' : 'publicGetTradingRecords'; const response = await this[method] (this.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' // } // const result = this.safeValue (response, 'result', {}); return this.parseTrades (result, market, since, limit); } parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'Buy', asksKey = 'Sell', priceKey = 'price', amountKey = 'size') { const bids = []; const asks = []; for (let i = 0; i < orderbook.length; i++) { const bidask = orderbook[i]; const side = this.safeString (bidask, 'side'); if (side === 'Buy') { bids.push (this.parseBidAsk (bidask, priceKey, amountKey)); } else if (side === 'Sell') { asks.push (this.parseBidAsk (bidask, priceKey, amountKey)); } else { throw new ExchangeError (this.id + ' parseOrderBook encountered an unrecognized bidask format: ' + this.json (bidask)); } } return { 'bids': this.sortBy (bids, 0, true), 'asks': this.sortBy (asks, 0), 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'nonce': undefined, }; } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const response = await this.publicGetOrderBookL2 (this.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' // } // const result = this.safeValue (response, 'result', []); const timestamp = this.safeTimestamp (response, 'time_now'); return this.parseOrderBook (result, timestamp, 'Buy', 'Sell', 'price', 'size'); } parseOrderStatus (status) { const statuses = { // basic orders 'Created': 'open', 'Rejected': 'rejected', // order is triggered but failed upon being placed 'New': 'open', 'PartiallyFilled': 'open', 'Filled': 'filled', 'Cancelled': 'canceled', 'PendingCancel': 'canceling', // the engine has received the cancellation but there is no guarantee that it will be successful // conditional orders 'Active': 'closed', // 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 this.safeString (statuses, status, status); } parseOrder (order, market = undefined) { // // 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" // } // const marketId = this.safeString (order, 'symbol'); let symbol = undefined; let base = undefined; if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; } const timestamp = this.parse8601 (this.safeString (order, 'created_at')); const id = this.safeString (order, 'order_id'); const price = this.safeFloat (order, 'price'); const average = this.safeFloat (order, 'average_price'); let amount = undefined; let cost = undefined; let filled = undefined; let remaining = undefined; if (market !== undefined) { symbol = market['symbol']; base = market['base']; if (market['inverse']) { cost = this.safeFloat (order, 'cum_exec_qty'); filled = this.safeFloat (order, 'cum_exec_value'); remaining = this.safeFloat (order, 'leaves_value'); amount = this.sum (filled, remaining); } else { amount = this.safeFloat (order, 'qty'); cost = this.safeFloat (order, 'cum_exec_value'); filled = this.safeFloat (order, 'cum_exec_qty'); remaining = this.safeFloat (order, 'leaves_qty'); } } let lastTradeTimestamp = this.safeTimestamp (order, 'last_exec_time'); if (lastTradeTimestamp === 0) { lastTradeTimestamp = undefined; } if ((filled === undefined) && (amount !== undefined) && (remaining !== undefined)) { filled = amount - remaining; } if (filled !== undefined) { if ((remaining === undefined) && (amount !== undefined)) { remaining = amount - filled; } if (cost === undefined) { if (price !== undefined) { cost = price * filled; } } } const status = this.parseOrderStatus (this.safeString (order, 'order_status')); const side = this.safeStringLower (order, 'side'); let feeCost = this.safeFloat (order, 'cum_exec_fee'); let fee = undefined; if (feeCost !== undefined) { feeCost = Math.abs (feeCost); fee = { 'cost': feeCost, 'currency': base, }; } const type = this.safeStringLower (order, 'order_type'); let clientOrderId = this.safeString (order, 'order_link_id'); if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) { clientOrderId = undefined; } return { 'info': order, 'id': id, 'clientOrderId': clientOrderId, 'timestamp': timestamp, 'datetime': this.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': undefined, }; } async fetchOrder (id, symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const 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 }; const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeString (marketTypes, symbol); let method = (marketType === 'linear') ? 'privateLinearGetOrderSearch' : 'privateGetOrder'; const stopOrderId = this.safeString (params, 'stop_order_id'); if (stopOrderId === undefined) { const orderLinkId = this.safeString (params, 'order_link_id'); if (orderLinkId === undefined) { request['order_id'] = id; } } else { method = (marketType === 'linear') ? 'privateLinearGetStopOrderSearch' : 'privateGetStopOrder'; } const response = await this[method] (this.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": "", // "result": { // "user_id": 1, // "symbol": "BTCUSD", // "side": "Buy", // "order_type": "Limit", // "price": "8000", // "qty": 1, // "time_in_force": "GoodTillCancel", // "order_status": "Untriggered", // "ext_fields": {}, // "leaves_qty": 1, // "leaves_value": "0.00013333", // "cum_exec_qty": 0, // "cum_exec_value": null, // "cum_exec_fee": null, // "reject_reason": "", // "order_link_id": "", // "created_at": "2019-12-27T19:56:24.052194Z", // "updated_at": "2019-12-27T19:56:24.052194Z", // "order_id": "378a1bbc-a93a-4e75-87f4-502ea754ba36" // }, // "time_now": "1577476584.386958", // "rate_limit_status": 99, // "rate_limit_reset_ms": 1580885703683, // "rate_limit": 100 // } // const result = this.safeValue (response, 'result'); return this.parseOrder (result, market); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { // orders --------------------------------------------------------- 'side': this.capitalize (side), 'symbol': market['id'], 'order_type': this.capitalize (type), 'qty': this.amountToPrecision (symbol, amount), // order quantity in USD, integer only // 'price': this.priceToPrecision (symbol, price), // required for limit orders 'time_in_force': 'GoodTillCancel', // ImmediateOrCancel, FillOrKill, PostOnly // 'take_profit': 123.45, // take profit price, only take effect upon opening the position // 'stop_loss': 123.45, // stop loss price, only take effect upon opening the position // 'reduce_only': false, // reduce only // when creating a closing order, bybit recommends a True value for // close_on_trigger to avoid failing due to insufficient available margin // 'close_on_trigger': false, // 'order_link_id': 'string', // unique client order id, max 36 characters // conditional orders --------------------------------------------- // base_price is used to compare with the value of stop_px, to decide // whether your conditional order will be triggered by crossing trigger // price from upper side or lower side, mainly used to identify the // expected direction of the current conditional order // 'base_price': 123.45, // required for conditional orders // 'stop_px': 123.45, // trigger price, required for conditional orders // 'trigger_by': 'LastPrice', // IndexPrice, MarkPrice }; let priceIsRequired = false; if (type === 'limit') { priceIsRequired = true; } if (priceIsRequired) { if (price !== undefined) { request['price'] = this.priceToPrecision (symbol, price); } else { throw new ArgumentsRequired (this.id + ' createOrder requires a price argument for a ' + type + ' order'); } } const stopPx = this.safeValue (params, 'stop_px'); const basePrice = this.safeValue (params, 'base_price'); const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeString (marketTypes, symbol); let method = (marketType === 'linear') ? 'privateLinearPostOrderCreate' : 'privatePostOrderCreate'; if (stopPx !== undefined) { if (basePrice === undefined) { throw new ArgumentsRequired (this.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order'); } else { method = (marketType === 'linear') ? 'privateLinearPostStopOrderCreate' : 'openapiPostStopOrderCreate'; request['stop_px'] = this.priceToPrecision (symbol, stopPx); request['base_price'] = this.priceToPrecision (symbol, basePrice); params = this.omit (params, [ 'stop_px', 'base_price' ]); } } else if (basePrice !== undefined) { throw new ArgumentsRequired (this.id + ' createOrder requires both the stop_px and base_price params for a conditional ' + type + ' order'); } const response = await this[method] (this.extend (request, params)); // // { // "ret_code": 0, // "ret_msg": "OK", // "ext_code": "", // "ext_info": "", // "result": { // "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, // "cum_exec_value": 0, // "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" // }, // "time_now": "1575111823.458705", // "rate_limit_status": 98, // "rate_limit_reset_ms": 1580885703683, // "rate_limit": 100 // } // // conditional orders // // { // "ret_code": 0, // "ret_msg": "ok", // "ext_code": "", // "result": { // "user_id": 1, // "symbol": "BTCUSD", // "side": "Buy", // "order_type": "Limit", // "price": 8000, // "qty": 1, // "time_in_force": "GoodTillCancel", // "stop_order_type": "Stop", // "trigger_by": "LastPrice", // "base_price": 7000, // "order_status": "Untriggered", // "ext_fields": { // "stop_order_type": "Stop", // "trigger_by": "LastPrice", // "base_price": 7000, // "expected_direction": "Rising", // "trigger_price": 7500, // "op_from": "api", // "remark": "127.0.01", // "o_req_num": 0 // }, // "leaves_qty": 1, // "leaves_value": 0.00013333, // "reject_reason": null, // "cross_seq": -1, // "created_at": "2019-12-27T12:48:24.000Z", // "updated_at": "2019-12-27T12:48:24.000Z", // "stop_px": 7500, // "stop_order_id": "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5" // }, // "ext_info": null, // "time_now": "1577450904.327654", // "rate_limit_status": 99, // "rate_limit_reset_ms": 1577450904335, // "rate_limit": "100" // } // const result = this.safeValue (response, 'result'); return this.parseOrder (result, market); } async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' editOrder requires an symbol argument'); } const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeString (marketTypes, symbol); if (marketType === 'linear') { throw new NotSupported (this.id + ' does not support editOrder for ' + marketType + ' ' + symbol + ' market type'); } await this.loadMarkets (); const market = this.market (symbol); const request = { // 'order_id': id, // only for non-conditional orders 'symbol': market['id'], // 'p_r_qty': this.amountToPrecision (symbol, amount), // new order quantity, optional // 'p_r_price' this.priceToprecision (symbol, price), // new order price, optional // ---------------------------------------------------------------- // conditional orders // 'stop_order_id': id, // only for conditional orders // 'p_r_trigger_price': 123.45, // new trigger price also known as stop_px }; const stopOrderId = this.safeString (params, 'stop_order_id'); let method = 'openapiPostOrderReplace'; if (stopOrderId !== undefined) { method = 'openapiPostStopOrderReplace'; request['stop_order_id'] = stopOrderId; params = this.omit (params, [ 'stop_order_id' ]); } else { request['order_id'] = id; } if (amount !== undefined) { request['p_r_qty'] = this.amountToPrecision (symbol, amount); } if (price !== undefined) { request['p_r_price'] = this.priceToPrecision (symbol, price); } const response = await this[method] (this.extend (request, params)); // // { // "ret_code": 0, // "ret_msg": "ok", // "ext_code": "", // "result": { "order_id": "efa44157-c355-4a98-b6d6-1d846a936b93" }, // "time_now": "1539778407.210858", // "rate_limit_status": 99, // 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": "", // "result": { "stop_order_id": "378a1bbc-a93a-4e75-87f4-502ea754ba36" }, // "ext_info": null, // "time_now": "1577475760.604942", // "rate_limit_status": 96, // "rate_limit_reset_ms": 1577475760612, // "rate_limit": "100" // } // const result = this.safeValue (response, 'result', {}); return { 'info': response, 'id': this.safeString2 (result, 'order_id', 'stop_order_id'), 'order_id': this.safeString (result, 'order_id'), 'stop_order_id': this.safeString (result, 'stop_order_id'), }; } async cancelOrder (id, symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const 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 }; const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeValue (marketTypes, symbol); let method = (marketType === 'linear') ? 'privateLinearPostOrderCancel' : 'privatePostOrderCancel'; const stopOrderId = this.safeString (params, 'stop_order_id'); if (stopOrderId === undefined) { const orderLinkId = this.safeString (params, 'order_link_id'); if (orderLinkId === undefined) { request['order_id'] = id; } } else { method = (marketType === 'linear') ? 'privateLinearPostStopOrderCancel' : 'openapiPostStopOrderCancel'; } const response = await this[method] (this.extend (request, params)); const result = this.safeValue (response, 'result', {}); return this.parseOrder (result, market); } async cancelAllOrders (symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const options = this.safeValue (this.options, 'cancelAllOrders'); const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeString (marketTypes, symbol); const defaultMethod = (marketType === 'linear') ? 'privateLinearPostOrderCancelAll' : 'privatePostOrderCancelAll'; const method = this.safeString (options, 'method', defaultMethod); const response = await this[method] (this.extend (request, params)); const result = this.safeValue (response, 'result', []); return this.parseOrders (result, market); } async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = { // 'order_id': 'string' // 'order_link_id': 'string', // unique client order id, max 36 characters // 'symbol': market['id'], // default BTCUSD // 'order': 'desc', // asc // 'page': 1, // 'limit': 20, // max 50 // 'order_status': 'Created,New' // conditional orders --------------------------------------------- // 'stop_order_id': 'string', // 'stop_order_status': 'Untriggered', }; let market = undefined; if (symbol !== undefined) { market = this.market (symbol); request['symbol'] = market['id']; } if (limit !== undefined) { request['limit'] = limit; } const options = this.safeValue (this.options, 'fetchOrders', {}); const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeString (marketTypes, symbol); let defaultMethod = (marketType === 'linear') ? 'privateLinearGetOrderList' : 'openapiGetOrderList'; let query = params; if (('stop_order_id' in params) || ('stop_order_status' in params)) { let stopOrderStatus = this.safeValue (params, 'stopOrderStatus'); if (stopOrderStatus !== undefined) { if (Array.isArray (stopOrderStatus)) { stopOrderStatus = stopOrderStatus.join (','); } request['stop_order_status'] = stopOrderStatus; query = this.omit (params, 'stop_order_status'); } defaultMethod = (marketType === 'linear') ? 'privateLinearGetStopOrderList' : 'openapiGetStopOrderList'; } const method = this.safeString (options, 'method', defaultMethod); const response = await this[method] (this.extend (request, query)); // // { // "ret_code": 0, // "ret_msg": "ok", // "ext_code": "", // "result": { // "current_page": 1, // "last_page": 6, // "data": [ // { // "user_id": 1, // "symbol": "BTCUSD", // "side": "Sell", // "order_type": "Market", // "price": 7074, // "qty": 2, // "time_in_force": "ImmediateOrCancel", // "order_status": "Filled", // "ext_fields": { // "close_on_trigger": true, // "orig_order_type": "BLimit", // "prior_x_req_price": 5898.5, // "op_from": "pc", // "remark": "127.0.0.1", // "o_req_num": -34799032763, // "xreq_type": "x_create" // }, // "last_exec_time": "1577448481.696421", // "last_exec_price": 7070.5, // "leaves_qty": 0, // "leaves_value": 0, // "cum_exec_qty": 2, // "cum_exec_value": 0.00028283, // "cum_exec_fee": 0.00002, // "reject_reason": "NoError", // "order_link_id": "", // "created_at": "2019-12-27T12:08:01.000Z", // "updated_at": "2019-12-27T12:08:01.000Z", // "order_id": "f185806b-b801-40ff-adec-52289370ed62" // } // ] // }, // "ext_info": null, // "time_now": "1577448922.437871", // "rate_limit_status": 98, // "rate_limit_reset_ms": 1580885703683, // "rate_limit": 100 // } // // conditional orders // // { // "ret_code": 0, // "ret_msg": "ok", // "ext_code": "", // "result": { // "current_page": 1, // "last_page": 1, // "data": [ // { // "user_id": 1, // "stop_order_status": "Untriggered", // "symbol": "BTCUSD", // "side": "Buy", // "order_type": "Limit", // "price": 8000, // "qty": 1, // "time_in_force": "GoodTillCancel", // "stop_order_type": "Stop", // "trigger_by": "LastPrice", // "base_price": 7000, // "order_link_id": "", // "created_at": "2019-12-27T12:48:24.000Z", // "updated_at": "2019-12-27T12:48:24.000Z", // "stop_px": 7500, // "stop_order_id": "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5" // }, // ] // }, // "ext_info": null, // "time_now": "1577451658.755468", // "rate_limit_status": 599, // "rate_limit_reset_ms": 1577451658762, // "rate_limit": 600 // } // const result = this.safeValue (response, 'result', {}); const data = this.safeValue (result, 'data', []); return this.parseOrders (data, market, since, limit); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { const defaultStatuses = [ 'Rejected', 'Filled', 'Cancelled', // conditional orders // 'Active', // 'Triggered', // 'Cancelled', // 'Rejected', // 'Deactivated', ]; const options = this.safeValue (this.options, 'fetchClosedOrders', {}); let status = this.safeValue (options, 'order_status', defaultStatuses); if (Array.isArray (status)) { status = status.join (','); } const request = {}; const stopOrderStatus = this.safeValue (params, 'stop_order_status'); if (stopOrderStatus === undefined) { request['order_status'] = status; } else { request['stop_order_status'] = stopOrderStatus; } return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { const defaultStatuses = [ 'Created', 'New', 'PartiallyFilled', 'PendingCancel', // conditional orders // 'Untriggered', ]; const options = this.safeValue (this.options, 'fetchOpenOrders', {}); let status = this.safeValue (options, 'order_status', defaultStatuses); if (Array.isArray (status)) { status = status.join (','); } const request = {}; const stopOrderStatus = this.safeValue (params, 'stop_order_status'); if (stopOrderStatus === undefined) { request['order_status'] = status; } else { request['stop_order_status'] = stopOrderStatus; } return await this.fetchOrders (symbol, since, limit, this.extend (request, params)); } async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { const request = { 'order_id': id, }; return await this.fetchMyTrades (symbol, since, limit, this.extend (request, params)); } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = { // 'order_id': 'f185806b-b801-40ff-adec-52289370ed62', // if not provided will return user's trading records // 'symbol': market['id'], // 'start_time': parseInt (since / 1000), // 'page': 1, // 'limit' 20, // max 50 }; let market = undefined; if (symbol === undefined) { const orderId = this.safeString (params, 'order_id'); if (orderId === undefined) { throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument or an order_id param'); } else { request['order_id'] = orderId; params = this.omit (params, 'order_id'); } } else { market = this.market (symbol); request['symbol'] = market['id']; } if (since !== undefined) { request['start_time'] = since; } if (limit !== undefined) { request['limit'] = limit; // default 20, max 50 } const marketTypes = this.safeValue (this.options, 'marketTypes', {}); const marketType = this.safeString (marketTypes, symbol); const method = (marketType === 'linear') ? 'privateLinearGetTradeExecutionList' : 'privateGetExecutionList'; const response = await this[method] (this.extend (request, params)); // // { // "ret_code": 0, // "ret_msg": "OK", // "ext_code": "", // "ext_info": "", // "result": { // "order_id": "Abandoned!!", // Abandoned!! // "trade_list": [ // { // "closed_size": 0, // "cross_seq": 277136382, // "exec_fee": "0.0000001", // "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68", // "exec_price": "8178.5", // "exec_qty": 1, // "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 // } // ] // }, // "time_now": "1577483699.281488", // "rate_limit_status": 118, // "rate_limit_reset_ms": 1577483699244737, // "rate_limit": 120 // } // const result = this.safeValue (response, 'result', {}); const trades = this.safeValue (result, 'trade_list', []); return this.parseTrades (trades, market, since, limit); } async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { if (code === undefined) { throw new ArgumentsRequired (this.id + ' fetchWithdrawals() requires a currency code argument'); } await this.loadMarkets (); const currency = this.currency (code); const request = { 'currency': currency['id'], }; if (limit !== undefined) { request['count'] = limit; } const response = await this.privateGetGetDeposits (this.extend (request, params)); // // { // "jsonrpc": "2.0", // "id": 5611, // "result": { // "count": 1, // "data": [ // { // "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax", // "amount": 5, // "currency": "BTC", // "received_timestamp": 1549295017670, // "state": "completed", // "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda", // "updated_timestamp": 1549295130159 // } // ] // } // } // const result = this.safeValue (response, 'result', {}); const data = this.safeValue (result, 'data', []); return this.parseTransactions (data, currency, since, limit, params); } async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = { // 'coin': currency['id'], // 'start_date': this.iso8601 (since), // 'end_date': this.iso8601 (till), // 'status': 'Pending', // ToBeConfirmed, UnderReview, Pending, Success, CancelByUser, Reject, Expire // 'page': 1, // 'limit': 20, // max 50 }; let currency = undefined; if (code !== undefined) { currency = this.currency (code); request['coin'] = currency['id']; } if (since !== undefined) { request['start_date'] = this.iso8601 (since); } if (limit !== undefined) { request['limit'] = limit; } const response = await this.openapiGetWalletWithdrawList (this.extend (request, params)); // // { // "ret_code": 0, // "ret_msg": "ok", // "ext_code": "", // "result": { // "data": [ // { // "id": 137, // "user_id": 1, // "coin": "XRP", // Coin Enum // "status": "Pending", // Withdraw Status Enum // "amount": "20.00000000", // "fee": "0.25000000", // "address": "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM", // "tx_id": "", // "submited_at": "2019-06-11T02:20:24.000Z", // "updated_at": "2019-06-11T02:20:24.000Z" // }, // ], // "current_page": 1, // "last_page": 1 // }, // "ext_info": null, // "time_now": "1577482295.125488", // "rate_limit_status": 119, // "rate_limit_reset_ms": 1577482295132, // "rate_limit": 120 // } // const result = this.safeValue (response, 'result', {}); const data = this.safeValue (result, 'data', []); return this.parseTransactions (data, currency, since, limit, params); } parseTransactionStatus (status) { const statuses = { 'ToBeConfirmed': 'pending', 'UnderReview': 'pending', 'Pending': 'pending', 'Success': 'ok', 'CancelByUser': 'canceled', 'Reject': 'rejected', 'Expire': 'expired', }; return this.safeString (statuses, status, status); } parseTransaction (transaction, currency = undefined) { // // fetchWithdrawals // // { // "id": 137, // "user_id": 1, // "coin": "XRP", // Coin Enum // "status": "Pending", // Withdraw Status Enum // "amount": "20.00000000", // "fee": "0.25000000", // "address": "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM", // "tx_id": "", // "submited_at": "2019-06-11T02:20:24.000Z", // "updated_at": "2019-06-11T02:20:24.000Z" // } // const currencyId = this.safeString (transaction, 'coin'); const code = this.safeCurrencyCode (currencyId, currency); const timestamp = this.parse8601 (this.safeString (transaction, 'submited_at')); const updated = this.parse8601 (this.safeString (transaction, 'updated_at')); const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); const address = this.safeString (transaction, 'address'); const feeCost = this.safeFloat (transaction, 'fee'); let fee = undefined; if (feeCost !== undefined) { fee = { 'cost': feeCost, 'currency': code, }; } return { 'info': transaction, 'id': this.safeString (transaction, 'id'), 'txid': this.safeString (transaction, 'tx_id'), 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'address': address, 'addressTo': undefined, 'addressFrom': undefined, 'tag': undefined, 'tagTo': undefined, 'tagFrom': undefined, 'type': 'withdrawal', 'amount': this.safeFloat (transaction, 'amount'), 'currency': code, 'status': status, 'updated': updated, 'fee': fee, }; } async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = { // 'coin': currency['id'], // 'currency': currency['id'], // alias // 'start_date': this.iso8601 (since), // 'end_date': this.iso8601 (till), // 'wallet_fund_type': 'Deposit', // Withdraw, RealisedPNL, Commission, Refund, Prize, ExchangeOrderWithdraw, ExchangeOrderDeposit // 'page': 1, // 'limit': 20, // max 50 }; let currency = undefined; if (code !== undefined) { currency = this.currency (code); request['coin'] = currency['id']; } if (since !== undefined) { request['start_date'] = this.iso8601 (since); } if (limit !== undefined) { request['limit'] = limit; } const response = await this.openapiGetWalletFundRecords (this.extend (request, params)); // // { // "ret_code": 0, // "ret_msg": "ok", // "ext_code": "", // "result": { // "data": [ // { // "id": 234467, // "user_id": 1, // "coin": "BTC", // "wallet_id": 27913, // "type": "Realized P&L", // "amount": "-0.00000006", // "tx_id": "", // "address": "BTCUSD", // "wallet_balance": "0.03000330", // "exec_time": "2019-12-09T00:00:25.000Z", // "cross_seq": 0 // } // ] // }, // "ext_info": null, // "time_now": "1577481867.115552", // "rate_limit_status": 119, // "rate_limit_reset_ms": 1577481867122, // "rate_limit": 120 // } // const result = this.safeValue (response, 'result', {}); const data = this.safeValue (result, 'data', []); return this.parseLedger (data, currency, since, limit); } parseLedgerEntry (item, currency = undefined) { // // { // "id": 234467, // "user_id": 1, // "coin": "BTC", // "wallet_id": 27913, // "type": "Realized P&L", // "amount": "-0.00000006", // "tx_id": "", // "address": "BTCUSD", // "wallet_balance": "0.03000330", // "exec_time": "2019-12-09T00:00:25.000Z", // "cross_seq": 0 // } // const currencyId = this.safeString (item, 'coin'); const code = this.safeCurrencyCode (currencyId, currency); const amount = this.safeFloat (item, 'amount'); const after = this.safeFloat (item, 'wallet_balance'); const direction = (amount < 0) ? 'out' : 'in'; let before = undefined; if (after !== undefined && amount !== undefined) { const difference = (direction === 'out') ? amount : -amount; before = this.sum (after, difference); } const timestamp = this.parse8601 (this.safeString (item, 'exec_time')); const type = this.parseLedgerEntryType (this.safeString (item, 'type')); const id = this.safeString (item, 'id'); const referenceId = this.safeString (item, 'tx_id'); return { 'id': id, 'currency': code, 'account': this.safeString (item, 'wallet_id'), 'referenceAccount': undefined, 'referenceId': referenceId, 'status': undefined, 'amount': amount, 'before': before, 'after': after, 'fee': undefined, 'direction': direction, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'type': type, 'info': item, }; } parseLedgerEntryType (type) { const types = { 'Deposit': 'transaction', 'Withdraw': 'transaction', 'RealisedPNL': 'trade', 'Commission': 'fee', 'Refund': 'cashback', 'Prize': 'prize', // ? 'ExchangeOrderWithdraw': 'transaction', 'ExchangeOrderDeposit': 'transaction', }; return this.safeString (types, type, type); } sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { let url = this.urls['api']; let request = path; // public v2 if (api === 'public') { request = '/' + this.version + '/' + api + '/' + request; if (Object.keys (params).length) { request += '?' + this.rawencode (params); } } else if (api === 'publicLinear') { request = '/public/linear/' + request; if (Object.keys (params).length) { request += '?' + this.rawencode (params); } } else { this.checkRequiredCredentials (); if (api === 'openapi') { request = '/open-api/' + request; } else if (api === 'private') { // private v2 request = '/' + this.version + '/' + api + '/' + request; } else if (api === 'privateLinear') { request = '/private/linear/' + request; } else { // position, user request = '/' + api + '/' + request; } const timestamp = this.nonce (); const query = this.extend (params, { 'api_key': this.apiKey, 'recv_window': this.options['recvWindow'], 'timestamp': timestamp, }); const auth = this.rawencode (this.keysort (query)); const signature = this.hmac (this.encode (auth), this.encode (this.secret)); if (method === 'POST') { body = this.json (this.extend (query, { 'sign': signature, })); headers = { 'Content-Type': 'application/json', }; } else { request += '?' + auth + '&sign=' + signature; } } url += request; return { 'url': url, 'method': method, 'body': body, 'headers': headers }; } handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) { if (!response) { return; // fallback to default error handler } // // { // ret_code: 10001, // ret_msg: 'ReadMapCB: expect { or n, but found \u0000, error ' + // 'found in #0 byte of ...||..., bigger context ' + // '...||...', // ext_code: '', // ext_info: '', // result: null, // time_now: '1583934106.590436' // } // const errorCode = this.safeValue (response, 'ret_code'); if (errorCode !== 0) { const feedback = this.id + ' ' + body; this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback); throw new ExchangeError (feedback); // unknown message } } };