UNPKG

ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges

1,013 lines (1,011 loc) • 113 kB
// ---------------------------------------------------------------------------- // 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 // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import bybitRest from '../bybit.js'; import { ArgumentsRequired, AuthenticationError, ExchangeError, BadRequest, NotSupported } from '../base/errors.js'; import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheBySymbolBySide, ArrayCacheByTimestamp } from '../base/ws/Cache.js'; import { sha256 } from '../static_dependencies/noble-hashes/sha256.js'; // --------------------------------------------------------------------------- export default class bybit extends bybitRest { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'createOrderWs': true, 'editOrderWs': true, 'fetchOpenOrdersWs': false, 'fetchOrderWs': false, 'cancelOrderWs': true, 'cancelOrdersWs': false, 'cancelAllOrdersWs': false, 'fetchTradesWs': false, 'fetchBalanceWs': false, 'watchBalance': true, 'watchBidsAsks': true, 'watchLiquidations': true, 'watchLiquidationsForSymbols': false, 'watchMyLiquidations': false, 'watchMyLiquidationsForSymbols': false, 'watchMyTrades': true, 'watchOHLCV': true, 'watchOHLCVForSymbols': true, 'watchOrderBook': true, 'watchOrderBookForSymbols': true, 'watchOrders': true, 'watchTicker': true, 'watchTickers': true, 'watchTrades': true, 'watchPositions': true, 'watchTradesForSymbols': true, }, 'urls': { 'api': { 'ws': { 'public': { 'spot': 'wss://stream.{hostname}/v5/public/spot', 'inverse': 'wss://stream.{hostname}/v5/public/inverse', 'option': 'wss://stream.{hostname}/v5/public/option', 'linear': 'wss://stream.{hostname}/v5/public/linear', }, 'private': { 'spot': { 'unified': 'wss://stream.{hostname}/v5/private', 'nonUnified': 'wss://stream.{hostname}/spot/private/v3', }, 'contract': 'wss://stream.{hostname}/v5/private', 'usdc': 'wss://stream.{hostname}/trade/option/usdc/private/v1', 'trade': 'wss://stream.bybit.com/v5/trade', }, }, }, 'test': { 'ws': { 'public': { 'spot': 'wss://stream-testnet.{hostname}/v5/public/spot', 'inverse': 'wss://stream-testnet.{hostname}/v5/public/inverse', 'linear': 'wss://stream-testnet.{hostname}/v5/public/linear', 'option': 'wss://stream-testnet.{hostname}/v5/public/option', }, 'private': { 'spot': { 'unified': 'wss://stream-testnet.{hostname}/v5/private', 'nonUnified': 'wss://stream-testnet.{hostname}/spot/private/v3', }, 'contract': 'wss://stream-testnet.{hostname}/v5/private', 'usdc': 'wss://stream-testnet.{hostname}/trade/option/usdc/private/v1', 'trade': 'wss://stream-testnet.bybit.com/v5/trade', }, }, }, 'demotrading': { 'ws': { 'public': { 'spot': 'wss://stream.{hostname}/v5/public/spot', 'inverse': 'wss://stream.{hostname}/v5/public/inverse', 'option': 'wss://stream.{hostname}/v5/public/option', 'linear': 'wss://stream.{hostname}/v5/public/linear', }, 'private': { 'spot': { 'unified': 'wss://stream-demo.{hostname}/v5/private', 'nonUnified': 'wss://stream-demo.{hostname}/spot/private/v3', }, 'contract': 'wss://stream-demo.{hostname}/v5/private', 'usdc': 'wss://stream-demo.{hostname}/trade/option/usdc/private/v1', 'trade': 'wss://stream-demo.bybit.com/v5/trade', }, }, }, }, 'options': { 'watchTicker': { 'name': 'tickers', // 'tickers' for 24hr statistical ticker or 'tickers_lt' for leverage token ticker }, 'watchPositions': { 'fetchPositionsSnapshot': true, 'awaitPositionsSnapshot': true, // whether to wait for the positions snapshot before providing updates }, 'watchMyTrades': { // filter execType: https://bybit-exchange.github.io/docs/api-explorer/v5/position/execution 'filterExecTypes': [ 'Trade', 'AdlTrade', 'BustTrade', 'Settle', ], }, 'spot': { 'timeframes': { '1m': '1m', '3m': '3m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '2h': '2h', '4h': '4h', '6h': '6h', '12h': '12h', '1d': '1d', '1w': '1w', '1M': '1M', }, }, 'contract': { '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', }, }, }, 'streaming': { 'ping': this.ping, 'keepAlive': 18000, }, }); } requestId() { const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; return requestId; } async getUrlByMarketType(symbol = undefined, isPrivate = false, method = undefined, params = {}) { const accessibility = isPrivate ? 'private' : 'public'; let isUsdcSettled = undefined; let isSpot = undefined; let type = undefined; let market = undefined; let url = this.urls['api']['ws']; if (symbol !== undefined) { market = this.market(symbol); isUsdcSettled = market['settle'] === 'USDC'; type = market['type']; } else { [type, params] = this.handleMarketTypeAndParams(method, undefined, params); let defaultSettle = this.safeString(this.options, 'defaultSettle'); defaultSettle = this.safeString2(params, 'settle', 'defaultSettle', defaultSettle); isUsdcSettled = (defaultSettle === 'USDC'); } isSpot = (type === 'spot'); if (isPrivate) { const unified = await this.isUnifiedEnabled(); const isUnifiedMargin = this.safeBool(unified, 0, false); const isUnifiedAccount = this.safeBool(unified, 1, false); if (isUsdcSettled && !isUnifiedMargin && !isUnifiedAccount) { url = url[accessibility]['usdc']; } else { url = url[accessibility]['contract']; } } else { if (isSpot) { url = url[accessibility]['spot']; } else if ((type === 'swap') || (type === 'future')) { let subType = undefined; [subType, params] = this.handleSubTypeAndParams(method, market, params, 'linear'); url = url[accessibility][subType]; } else { // option url = url[accessibility]['option']; } } url = this.implodeHostname(url); return url; } cleanParams(params) { params = this.omit(params, ['type', 'subType', 'settle', 'defaultSettle', 'unifiedMargin']); return params; } /** * @method * @name bybit#createOrderWs * @description create a trade order * @see https://bybit-exchange.github.io/docs/v5/order/create-order * @see https://bybit-exchange.github.io/docs/v5/websocket/trade/guideline#createamendcancel-order * @param {string} symbol unified symbol of the market to create an order in * @param {string} type 'market' or 'limit' * @param {string} side 'buy' or 'sell' * @param {float} amount how much of currency you want to trade in units of base currency * @param {float} [price] the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {string} [params.timeInForce] "GTC", "IOC", "FOK" * @param {bool} [params.postOnly] true or false whether the order is post-only * @param {bool} [params.reduceOnly] true or false whether the order is reduce-only * @param {string} [params.positionIdx] *contracts only* 0 for one-way mode, 1 buy side of hedged mode, 2 sell side of hedged mode * @param {boolean} [params.isLeverage] *unified spot only* false then spot trading true then margin trading * @param {string} [params.tpslMode] *contract only* 'full' or 'partial' * @param {string} [params.mmp] *option only* market maker protection * @param {string} [params.triggerDirection] *contract only* the direction for trigger orders, 'above' or 'below' * @param {float} [params.triggerPrice] The price at which a trigger order is triggered at * @param {float} [params.stopLossPrice] The price at which a stop loss order is triggered at * @param {float} [params.takeProfitPrice] The price at which a take profit order is triggered at * @param {object} [params.takeProfit] *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered * @param {float} [params.takeProfit.triggerPrice] take profit trigger price * @param {object} [params.stopLoss] *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered * @param {float} [params.stopLoss.triggerPrice] stop loss trigger price * @param {string} [params.trailingAmount] the quote amount to trail away from the current market price * @param {string} [params.trailingTriggerPrice] the price to trigger a trailing order, default uses the price argument * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ async createOrderWs(symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets(); const orderRequest = this.createOrderRequest(symbol, type, side, amount, price, params, true); const url = this.urls['api']['ws']['private']['trade']; await this.authenticate(url); const requestId = this.requestId().toString(); const request = { 'op': 'order.create', 'reqId': requestId, 'args': [ orderRequest, ], 'header': { 'X-BAPI-TIMESTAMP': this.milliseconds().toString(), 'X-BAPI-RECV-WINDOW': this.options['recvWindow'].toString(), }, }; return await this.watch(url, requestId, request, requestId, true); } /** * @method * @name bybit#editOrderWs * @description edit a trade order * @see https://bybit-exchange.github.io/docs/v5/order/amend-order * @see https://bybit-exchange.github.io/docs/v5/websocket/trade/guideline#createamendcancel-order * @param {string} id cancel order id * @param {string} symbol unified symbol of the market to create an order in * @param {string} type 'market' or 'limit' * @param {string} side 'buy' or 'sell' * @param {float} amount how much of currency you want to trade in units of base currency * @param {float} price the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {float} [params.triggerPrice] The price that a trigger order is triggered at * @param {float} [params.stopLossPrice] The price that a stop loss order is triggered at * @param {float} [params.takeProfitPrice] The price that a take profit order is triggered at * @param {object} [params.takeProfit] *takeProfit object in params* containing the triggerPrice that the attached take profit order will be triggered * @param {float} [params.takeProfit.triggerPrice] take profit trigger price * @param {object} [params.stopLoss] *stopLoss object in params* containing the triggerPrice that the attached stop loss order will be triggered * @param {float} [params.stopLoss.triggerPrice] stop loss trigger price * @param {string} [params.triggerBy] 'IndexPrice', 'MarkPrice' or 'LastPrice', default is 'LastPrice', required if no initial value for triggerPrice * @param {string} [params.slTriggerBy] 'IndexPrice', 'MarkPrice' or 'LastPrice', default is 'LastPrice', required if no initial value for stopLoss * @param {string} [params.tpTriggerby] 'IndexPrice', 'MarkPrice' or 'LastPrice', default is 'LastPrice', required if no initial value for takeProfit * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ async editOrderWs(id, symbol, type, side, amount = undefined, price = undefined, params = {}) { await this.loadMarkets(); const orderRequest = this.editOrderRequest(id, symbol, type, side, amount, price, params); const url = this.urls['api']['ws']['private']['trade']; await this.authenticate(url); const requestId = this.requestId().toString(); const request = { 'op': 'order.amend', 'reqId': requestId, 'args': [ orderRequest, ], 'header': { 'X-BAPI-TIMESTAMP': this.milliseconds().toString(), 'X-BAPI-RECV-WINDOW': this.options['recvWindow'].toString(), }, }; return await this.watch(url, requestId, request, requestId, true); } /** * @method * @name bybit#cancelOrderWs * @description cancels an open order * @see https://bybit-exchange.github.io/docs/v5/order/cancel-order * @see https://bybit-exchange.github.io/docs/v5/websocket/trade/guideline#createamendcancel-order * @param {string} id order id * @param {string} symbol unified symbol of the market the order was made in * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {boolean} [params.trigger] *spot only* whether the order is a trigger order * @param {string} [params.orderFilter] *spot only* 'Order' or 'StopOrder' or 'tpslOrder' * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ async cancelOrderWs(id, symbol = undefined, params = {}) { await this.loadMarkets(); const orderRequest = this.cancelOrderRequest(id, symbol, params); const url = this.urls['api']['ws']['private']['trade']; await this.authenticate(url); const requestId = this.requestId().toString(); if ('orderFilter' in orderRequest) { delete orderRequest['orderFilter']; } const request = { 'op': 'order.cancel', 'reqId': requestId, 'args': [ orderRequest, ], 'header': { 'X-BAPI-TIMESTAMP': this.milliseconds().toString(), 'X-BAPI-RECV-WINDOW': this.options['recvWindow'].toString(), }, }; return await this.watch(url, requestId, request, requestId, true); } /** * @method * @name bybit#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://bybit-exchange.github.io/docs/v5/websocket/public/ticker * @see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-ticker * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchTicker(symbol, params = {}) { await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const messageHash = 'ticker:' + symbol; const url = await this.getUrlByMarketType(symbol, false, 'watchTicker', params); params = this.cleanParams(params); const options = this.safeValue(this.options, 'watchTicker', {}); let topic = this.safeString(options, 'name', 'tickers'); if (!market['spot'] && topic !== 'tickers') { throw new BadRequest(this.id + ' watchTicker() only supports name tickers for contract markets'); } topic += '.' + market['id']; const topics = [topic]; return await this.watchTopics(url, [messageHash], topics, params); } /** * @method * @name bybit#watchTickers * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list * @see https://bybit-exchange.github.io/docs/v5/websocket/public/ticker * @see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-ticker * @param {string[]} symbols unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchTickers(symbols = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false); const messageHashes = []; const url = await this.getUrlByMarketType(symbols[0], false, 'watchTickers', params); params = this.cleanParams(params); const options = this.safeValue(this.options, 'watchTickers', {}); const topic = this.safeString(options, 'name', 'tickers'); const marketIds = this.marketIds(symbols); const topics = []; for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; topics.push(topic + '.' + marketId); messageHashes.push('ticker:' + symbols[i]); } const ticker = await this.watchTopics(url, messageHashes, topics, params); if (this.newUpdates) { const result = {}; result[ticker['symbol']] = ticker; return result; } return this.filterByArray(this.tickers, 'symbol', symbols); } /** * @method * @name bybit#unWatchTickers * @description unWatches a price ticker * @see https://bybit-exchange.github.io/docs/v5/websocket/public/ticker * @see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-ticker * @param {string[]} symbols unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async unWatchTickers(symbols = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false); const options = this.safeValue(this.options, 'watchTickers', {}); const topic = this.safeString(options, 'name', 'tickers'); const messageHashes = []; const subMessageHashes = []; const marketIds = this.marketIds(symbols); const topics = []; for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; const symbol = symbols[i]; topics.push(topic + '.' + marketId); subMessageHashes.push('ticker:' + symbol); messageHashes.push('unsubscribe:ticker:' + symbol); } const url = await this.getUrlByMarketType(symbols[0], false, 'watchTickers', params); return await this.unWatchTopics(url, 'ticker', symbols, messageHashes, subMessageHashes, topics, params); } /** * @method * @name bybit#unWatchTicker * @description unWatches a price ticker * @see https://bybit-exchange.github.io/docs/v5/websocket/public/ticker * @see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-ticker * @param {string[]} symbols unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async unWatchTicker(symbols, params = {}) { await this.loadMarkets(); return await this.unWatchTickers([symbols], params); } handleTicker(client, message) { // // linear // { // "topic": "tickers.BTCUSDT", // "type": "snapshot", // "data": { // "symbol": "BTCUSDT", // "tickDirection": "PlusTick", // "price24hPcnt": "0.017103", // "lastPrice": "17216.00", // "prevPrice24h": "16926.50", // "highPrice24h": "17281.50", // "lowPrice24h": "16915.00", // "prevPrice1h": "17238.00", // "markPrice": "17217.33", // "indexPrice": "17227.36", // "openInterest": "68744.761", // "openInterestValue": "1183601235.91", // "turnover24h": "1570383121.943499", // "volume24h": "91705.276", // "nextFundingTime": "1673280000000", // "fundingRate": "-0.000212", // "bid1Price": "17215.50", // "bid1Size": "84.489", // "ask1Price": "17216.00", // "ask1Size": "83.020" // }, // "cs": 24987956059, // "ts": 1673272861686 // } // // option // { // "id": "tickers.BTC-6JAN23-17500-C-2480334983-1672917511074", // "topic": "tickers.BTC-6JAN23-17500-C", // "ts": 1672917511074, // "data": { // "symbol": "BTC-6JAN23-17500-C", // "bidPrice": "0", // "bidSize": "0", // "bidIv": "0", // "askPrice": "10", // "askSize": "5.1", // "askIv": "0.514", // "lastPrice": "10", // "highPrice24h": "25", // "lowPrice24h": "5", // "markPrice": "7.86976724", // "indexPrice": "16823.73", // "markPriceIv": "0.4896", // "underlyingPrice": "16815.1", // "openInterest": "49.85", // "turnover24h": "446802.8473", // "volume24h": "26.55", // "totalVolume": "86", // "totalTurnover": "1437431", // "delta": "0.047831", // "gamma": "0.00021453", // "vega": "0.81351067", // "theta": "-19.9115368", // "predictedDeliveryPrice": "0", // "change24h": "-0.33333334" // }, // "type": "snapshot" // } // // spot // { // "topic": "tickers.BTCUSDT", // "ts": 1673853746003, // "type": "snapshot", // "cs": 2588407389, // "data": { // "symbol": "BTCUSDT", // "lastPrice": "21109.77", // "highPrice24h": "21426.99", // "lowPrice24h": "20575", // "prevPrice24h": "20704.93", // "volume24h": "6780.866843", // "turnover24h": "141946527.22907118", // "price24hPcnt": "0.0196", // "usdIndexPrice": "21120.2400136" // } // } // // lt ticker // { // "topic": "tickers_lt.EOS3LUSDT", // "ts": 1672325446847, // "type": "snapshot", // "data": { // "symbol": "EOS3LUSDT", // "lastPrice": "0.41477848043290448", // "highPrice24h": "0.435285472510871305", // "lowPrice24h": "0.394601507960931382", // "prevPrice24h": "0.431502290172376349", // "price24hPcnt": "-0.0388" // } // } // swap delta // { // "topic":"tickers.AAVEUSDT", // "type":"delta", // "data":{ // "symbol":"AAVEUSDT", // "bid1Price":"112.89", // "bid1Size":"2.12", // "ask1Price":"112.90", // "ask1Size":"5.02" // }, // "cs":78039939929, // "ts":1709210212704 // } // const topic = this.safeString(message, 'topic', ''); const updateType = this.safeString(message, 'type', ''); const data = this.safeDict(message, 'data', {}); const isSpot = this.safeString(data, 'usdIndexPrice') !== undefined; const type = isSpot ? 'spot' : 'contract'; let symbol = undefined; let parsed = undefined; if ((updateType === 'snapshot')) { parsed = this.parseTicker(data); symbol = parsed['symbol']; } else if (updateType === 'delta') { const topicParts = topic.split('.'); const topicLength = topicParts.length; const marketId = this.safeString(topicParts, topicLength - 1); const market = this.safeMarket(marketId, undefined, undefined, type); symbol = market['symbol']; // update the info in place const ticker = this.safeDict(this.tickers, symbol, {}); const rawTicker = this.safeDict(ticker, 'info', {}); const merged = this.extend(rawTicker, data); parsed = this.parseTicker(merged); } const timestamp = this.safeInteger(message, 'ts'); parsed['timestamp'] = timestamp; parsed['datetime'] = this.iso8601(timestamp); this.tickers[symbol] = parsed; const messageHash = 'ticker:' + symbol; client.resolve(this.tickers[symbol], messageHash); } /** * @method * @name bybit#watchBidsAsks * @description watches best bid & ask for symbols * @see https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook * @param {string[]} symbols unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchBidsAsks(symbols = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false); const messageHashes = []; const url = await this.getUrlByMarketType(symbols[0], false, 'watchBidsAsks', params); params = this.cleanParams(params); const marketIds = this.marketIds(symbols); const topics = []; for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; const topic = 'orderbook.1.' + marketId; topics.push(topic); messageHashes.push('bidask:' + symbols[i]); } const ticker = await this.watchTopics(url, messageHashes, topics, params); if (this.newUpdates) { return ticker; } return this.filterByArray(this.bidsasks, 'symbol', symbols); } parseWsBidAsk(orderbook, market = undefined) { const timestamp = this.safeInteger(orderbook, 'timestamp'); const bids = this.sortBy(this.aggregate(orderbook['bids']), 0); const asks = this.sortBy(this.aggregate(orderbook['asks']), 0); const bestBid = this.safeList(bids, 0, []); const bestAsk = this.safeList(asks, 0, []); return this.safeTicker({ 'symbol': market['symbol'], 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'ask': this.safeNumber(bestAsk, 0), 'askVolume': this.safeNumber(bestAsk, 1), 'bid': this.safeNumber(bestBid, 0), 'bidVolume': this.safeNumber(bestBid, 1), 'info': orderbook, }, market); } /** * @method * @name bybit#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://bybit-exchange.github.io/docs/v5/websocket/public/kline * @see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-kline * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @param {int} [since] timestamp in ms of the earliest candle to fetch * @param {int} [limit] the maximum amount of candles to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { params['callerMethodName'] = 'watchOHLCV'; const result = await this.watchOHLCVForSymbols([[symbol, timeframe]], since, limit, params); return result[symbol][timeframe]; } /** * @method * @name bybit#watchOHLCVForSymbols * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://bybit-exchange.github.io/docs/v5/websocket/public/kline * @see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-kline * @param {string[][]} symbolsAndTimeframes array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']] * @param {int} [since] timestamp in ms of the earliest candle to fetch * @param {int} [limit] the maximum amount of candles to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A list of candles ordered as timestamp, open, high, low, close, volume */ async watchOHLCVForSymbols(symbolsAndTimeframes, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const symbols = this.getListFromObjectValues(symbolsAndTimeframes, 0); const marketSymbols = this.marketSymbols(symbols, undefined, false, true, true); const firstSymbol = marketSymbols[0]; const url = await this.getUrlByMarketType(firstSymbol, false, 'watchOHLCVForSymbols', params); const rawHashes = []; const messageHashes = []; for (let i = 0; i < symbolsAndTimeframes.length; i++) { const data = symbolsAndTimeframes[i]; let symbolString = this.safeString(data, 0); const market = this.market(symbolString); symbolString = market['symbol']; const unfiedTimeframe = this.safeString(data, 1); const timeframeId = this.safeString(this.timeframes, unfiedTimeframe, unfiedTimeframe); rawHashes.push('kline.' + timeframeId + '.' + market['id']); messageHashes.push('ohlcv::' + symbolString + '::' + unfiedTimeframe); } const [symbol, timeframe, stored] = await this.watchTopics(url, messageHashes, rawHashes, params); if (this.newUpdates) { limit = stored.getLimit(symbol, limit); } const filtered = this.filterBySinceLimit(stored, since, limit, 0, true); return this.createOHLCVObject(symbol, timeframe, filtered); } /** * @method * @name bybit#unWatchOHLCVForSymbols * @description unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://bybit-exchange.github.io/docs/v5/websocket/public/kline * @see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-kline * @param {string[][]} symbolsAndTimeframes array of arrays containing unified symbols and timeframes to fetch OHLCV data for, example [['BTC/USDT', '1m'], ['LTC/USDT', '5m']] * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A list of candles ordered as timestamp, open, high, low, close, volume */ async unWatchOHLCVForSymbols(symbolsAndTimeframes, params = {}) { await this.loadMarkets(); const symbols = this.getListFromObjectValues(symbolsAndTimeframes, 0); const marketSymbols = this.marketSymbols(symbols, undefined, false, true, true); const firstSymbol = marketSymbols[0]; const url = await this.getUrlByMarketType(firstSymbol, false, 'watchOHLCVForSymbols', params); const rawHashes = []; const subMessageHashes = []; const messageHashes = []; for (let i = 0; i < symbolsAndTimeframes.length; i++) { const data = symbolsAndTimeframes[i]; let symbolString = this.safeString(data, 0); const market = this.market(symbolString); symbolString = market['symbol']; const unfiedTimeframe = this.safeString(data, 1); const timeframeId = this.safeString(this.timeframes, unfiedTimeframe, unfiedTimeframe); rawHashes.push('kline.' + timeframeId + '.' + market['id']); subMessageHashes.push('ohlcv::' + symbolString + '::' + unfiedTimeframe); messageHashes.push('unsubscribe::ohlcv::' + symbolString + '::' + unfiedTimeframe); } const subExtension = { 'symbolsAndTimeframes': symbolsAndTimeframes, }; return await this.unWatchTopics(url, 'ohlcv', symbols, messageHashes, subMessageHashes, rawHashes, params, subExtension); } /** * @method * @name bybit#unWatchOHLCV * @description unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://bybit-exchange.github.io/docs/v5/websocket/public/kline * @see https://bybit-exchange.github.io/docs/v5/websocket/public/etp-kline * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async unWatchOHLCV(symbol, timeframe = '1m', params = {}) { params['callerMethodName'] = 'watchOHLCV'; return await this.unWatchOHLCVForSymbols([[symbol, timeframe]], params); } handleOHLCV(client, message) { // // { // "topic": "kline.5.BTCUSDT", // "data": [ // { // "start": 1672324800000, // "end": 1672325099999, // "interval": "5", // "open": "16649.5", // "close": "16677", // "high": "16677", // "low": "16608", // "volume": "2.081", // "turnover": "34666.4005", // "confirm": false, // "timestamp": 1672324988882 // } // ], // "ts": 1672324988882, // "type": "snapshot" // } // const data = this.safeValue(message, 'data', {}); const topic = this.safeString(message, 'topic'); const topicParts = topic.split('.'); const topicLength = topicParts.length; const timeframeId = this.safeString(topicParts, 1); const timeframe = this.findTimeframe(timeframeId); const marketId = this.safeString(topicParts, topicLength - 1); const isSpot = client.url.indexOf('spot') > -1; const marketType = isSpot ? 'spot' : 'contract'; const market = this.safeMarket(marketId, undefined, undefined, marketType); const symbol = market['symbol']; const ohlcvsByTimeframe = this.safeValue(this.ohlcvs, symbol); if (ohlcvsByTimeframe === undefined) { this.ohlcvs[symbol] = {}; } if (this.safeValue(ohlcvsByTimeframe, timeframe) === undefined) { const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000); this.ohlcvs[symbol][timeframe] = new ArrayCacheByTimestamp(limit); } const stored = this.ohlcvs[symbol][timeframe]; for (let i = 0; i < data.length; i++) { const parsed = this.parseWsOHLCV(data[i], market); stored.append(parsed); } const messageHash = 'ohlcv::' + symbol + '::' + timeframe; const resolveData = [symbol, timeframe, stored]; client.resolve(resolveData, messageHash); } parseWsOHLCV(ohlcv, market = undefined) { // // { // "start": 1670363160000, // "end": 1670363219999, // "interval": "1", // "open": "16987.5", // "close": "16987.5", // "high": "16988", // "low": "16987.5", // "volume": "23.511", // "turnover": "399396.344", // "confirm": false, // "timestamp": 1670363219614 // } // const volumeIndex = (market['inverse']) ? 'turnover' : 'volume'; return [ this.safeInteger(ohlcv, 'start'), this.safeNumber(ohlcv, 'open'), this.safeNumber(ohlcv, 'high'), this.safeNumber(ohlcv, 'low'), this.safeNumber(ohlcv, 'close'), this.safeNumber(ohlcv, volumeIndex), ]; } /** * @method * @name bybit#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int} [limit] the maximum amount of order book entries to return. * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async watchOrderBook(symbol, limit = undefined, params = {}) { return await this.watchOrderBookForSymbols([symbol], limit, params); } /** * @method * @name bybit#watchOrderBookForSymbols * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook * @param {string[]} symbols unified array of symbols * @param {int} [limit] the maximum amount of order book entries to return. * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async watchOrderBookForSymbols(symbols, limit = undefined, params = {}) { await this.loadMarkets(); const symbolsLength = symbols.length; if (symbolsLength === 0) { throw new ArgumentsRequired(this.id + ' watchOrderBookForSymbols() requires a non-empty array of symbols'); } symbols = this.marketSymbols(symbols); const url = await this.getUrlByMarketType(symbols[0], false, 'watchOrderBook', params); params = this.cleanParams(params); const market = this.market(symbols[0]); if (limit === undefined) { limit = (market['spot']) ? 50 : 500; if (market['option']) { limit = 100; } } else { if (!market['spot']) { if (market['option']) { if ((limit !== 25) && (limit !== 100)) { throw new BadRequest(this.id + ' watchOrderBookForSymbols() can only use limit 25 and 100 for option markets.'); } } else if ((limit !== 1) && (limit !== 50) && (limit !== 200) && (limit !== 500)) { // bybit only support limit 1, 50, 200, 500 for contract throw new BadRequest(this.id + ' watchOrderBookForSymbols() can only use limit 1, 50, 200 and 500 for swap and future markets.'); } } else { if ((limit !== 1) && (limit !== 50) && (limit !== 200)) { throw new BadRequest(this.id + ' watchOrderBookForSymbols() can only use limit 1,50, and 200 for spot markets.'); } } } const topics = []; const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const marketId = this.marketId(symbol); const topic = 'orderbook.' + limit.toString() + '.' + marketId; topics.push(topic); const messageHash = 'orderbook:' + symbol; messageHashes.push(messageHash); } const orderbook = await this.watchTopics(url, messageHashes, topics, params); return orderbook.limit(); } /** * @method * @name bybit#unWatchOrderBookForSymbols * @description unsubscribe from the orderbook channel * @see https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook * @param {string[]} symbols unified symbol of the market to unwatch the trades for * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {int} [params.limit] orderbook limit, default is undefined * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async unWatchOrderBookForSymbols(symbols, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false); let channel = 'orderbook.'; let limit = this.safeInteger(params, 'limit'); if (limit !== undefined) { params = this.omit(params, 'limit'); } else { const firstMarket = this.market(symbols[0]); limit = firstMarket['spot'] ? 50 : 500; } channel += limit.toString(); const subMessageHashes = []; const messageHashes = []; const topics = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.market(symbol); const marketId = market['id']; const topic = channel + '.' + marketId; messageHashes.push('unsubscribe:orderbook:' + symbol); subMessageHashes.push('orderbook:' + symbol); topics.push(topic); } const url = await this.getUrlByMarketType(symbols[0], false, 'watchOrderBook', params); return await this.unWatchTopics(url, 'orderbook', symbols, messageHashes, subMessageHashes, topics, params); } /** * @method * @name bybit#unWatchOrderBook * @description unsubscribe from the orderbook channel * @see https://bybit-exchange.github.io/docs/v5/websocket/public/orderbook * @param {string} symbol symbol of the market to unwatch the trades for * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {int} [params.limit] orderbook limit, default is undefined * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async unWatchOrderBook(symbol, params = {}) { await this.loadMarkets(); return await this.unWatchOrderBookForSymbols([symbol], params); } handleOrderBook(client, message) { // // { // "topic": "orderbook.50.BTCUSDT", // "type": "snapshot", // "ts": 1672304484978, // "data": { // "s": "BTCUSDT", // "b": [ // ..., // [ // "16493.50", // "0.006" // ], // [ // "16493.00", // "0.100" // ] // ], // "a": [ // [ // "16611.00", // "0.029" // ], // [ // "16612.00", // "0.213" // ], // ], // "u": 18521288, // "seq": 7961638724 // } // } // const topic = this.safeString(message, 'topic'); const limit = topic.split('.')[1]; const isSpot = client.url.indexOf('spot') >= 0; const type = this.safeString(message, 'type'); const isSnapshot = (type === 'snapshot'); const data = this.safeDict(message, 'data', {}); const marketId = this.safeString(data, 's'); const marketType = isSpot ? 'spot' : 'contract'; const market = this.safeMarket(marketId, undefined, undefined, marketType); const symbol = market['symbol']; const timestamp = this.safeInteger(message, 'ts'); if (!(symbol in this.orderbooks)) { this.orderbooks[symbol] = this.orderBook(); } const orderbook = this.orderbooks[symbol]; if (isSnapshot) { const snapshot = this.parseOrderBook(data, symbol, timestamp, 'b', 'a'); orderbook.reset(snapshot); } else { const asks = this.safeList(data, 'a', []); const bids = this.safeList(data, 'b', []); this.handleDeltas(orderbook['asks'], asks); this.handleDeltas(orderbook['bids'], bids); orderbook['timestamp'] = timestamp; orderbook['datetime'] = this.iso8601(timestamp); } const messageHash = 'orderbook' + ':' + symbol; this.orderbooks[symbol] = orderbook; client.resolve(orderbook, messageHash); if (limit === '1') { const bidask = this.parseWsBidAsk(this.orderbooks[symbol], market); const newBidsAsks = {}; newBidsAsks[symbol] = bidask; this.bidsasks[symbol] = bidask; client.resolve(newBidsAsks, 'bidask:' + symbol); } } handleD