ccxt
Version:
1,013 lines (1,011 loc) • 113 kB
JavaScript
// ----------------------------------------------------------------------------
// 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