UNPKG

sfccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

1,230 lines (1,203 loc) 60.7 kB
'use strict'; // --------------------------------------------------------------------------- const bybitRest = require ('../bybit.js'); const { AuthenticationError, ExchangeError } = require ('../base/errors'); const { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } = require ('./base/Cache'); // --------------------------------------------------------------------------- module.exports = class bybit extends bybitRest { describe () { return this.deepExtend (super.describe (), { 'has': { 'ws': true, 'watchBalance': true, 'watchMyTrades': true, 'watchOHLCV': true, 'watchOrderBook': true, 'watchOrders': true, 'watchTicker': true, 'watchTickers': false, // for now 'watchTrades': true, 'watchPosition': undefined, }, 'urls': { 'api': { 'ws': { 'public': { 'spot': 'wss://stream.{hostname}/spot/public/v3', 'inverse': 'wss://stream.{hostname}/contract/inverse/public/v3', 'usdt': 'wss://stream.{hostname}/contract/usdt/public/v3', 'usdc': { 'option': 'wss://stream.{hostname}/option/usdc/public/v3', 'swap': 'wss://stream.{hostname}/contract/usdc/public/v3', }, }, 'private': { 'spot': 'wss://stream.{hostname}/spot/private/v3', 'contract': { 'unified': 'wss://stream.{hostname}/unified/private/v3', 'nonUnified': 'wss://stream.{hostname}/contract/private/v3', }, }, }, }, 'test': { 'ws': { 'public': { 'spot': 'wss://stream-testnet.{hostname}/spot/public/v3', 'inverse': 'wss://stream-testnet.{hostname}/contract/inverse/public/v3', 'usdt': 'wss://stream-testnet.{hostname}/contract/usdt/public/v3', 'usdc': { 'option': 'wss://stream-testnet.{hostname}/option/usdc/public/v3', 'swap': 'wss://stream-testnet.{hostname}/contract/usdc/public/v3', }, }, 'private': { 'spot': 'wss://stream-testnet.{hostname}/spot/private/v3', 'contract': { 'unified': 'wss://stream-testnet.{hostname}/unified/private/v3', 'nonUnified': 'wss://stream-testnet.{hostname}/contract/private/v3', }, }, }, }, }, 'options': { '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', }, 'watchTickerTopic': 'tickers', // 'tickers' for 24hr statistical ticker or 'bookticker' for Best bid price and best ask price }, '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': 20000, }, 'exceptions': { 'ws': { 'exact': { }, }, }, }); } requestId () { const requestId = this.sum (this.safeInteger (this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; return requestId; } getUrlByMarketType (symbol = undefined, isPrivate = false, isUnifiedMargin = false, method = undefined, params = {}) { const accessibility = isPrivate ? 'private' : 'public'; let isUsdcSettled = undefined; let isSpot = undefined; let type = undefined; let isUsdtSettled = undefined; let market = undefined; let url = this.urls['api']['ws']; if (symbol !== undefined) { market = this.market (symbol); isUsdcSettled = market['settle'] === 'USDC'; isUsdtSettled = market['settle'] === 'USDT'; isSpot = market['spot']; 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'); isUsdtSettled = (defaultSettle === 'USDT'); isSpot = (type === 'spot'); } if (isPrivate) { if (isSpot) { url = url[accessibility]['spot']; } else { const margin = isUnifiedMargin ? 'unified' : 'nonUnified'; url = url[accessibility]['contract'][margin]; } } else { if (isSpot) { url = url[accessibility]['spot']; } else if (isUsdcSettled) { url = url[accessibility]['usdc'][type]; } else if (isUsdtSettled) { url = url[accessibility]['usdt']; } else { // inverse url = url[accessibility]['inverse']; } } url = this.implodeHostname (url); return url; } cleanParams (params) { params = this.omit (params, [ 'type', 'subType', 'settle', 'defaultSettle', 'unifiedMargin' ]); return params; } async watchTicker (symbol, params = {}) { /** * @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 * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the bybit api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const market = this.market (symbol); const messageHash = 'ticker:' + market['symbol']; const url = this.getUrlByMarketType (symbol, false, false, params); params = this.cleanParams (params); let topic = 'tickers'; if (market['spot']) { const spotOptions = this.safeValue (this.options, 'spot', {}); topic = this.safeString (spotOptions, 'watchTickerTopic', 'tickers'); } topic += '.' + market['id']; const topics = [ topic ]; return await this.watchTopics (url, messageHash, topics, params); } handleTicker (client, message) { // // spot - tickers // { // "data": { // "t": 1661742216005, // "s": "BTCUSDT", // "o": "19820", // "h": "20071.93", // "l": "19365.85", // "c": "19694.27", // "v": "9997.246174", // "qv": "197357775.97621786", // "m": "-0.0063" // }, // "type": "delta", // "topic": "tickers.BTCUSDT", // "ts": 1661742216011 // } // spot - bookticker // { // "data": { // "s": "BTCUSDT", // "bp": "19693.04", // "bq": "0.913957", // "ap": "19694.27", // "aq": "0.705447", // "t": 1661742216108 // }, // "type": "delta", // "topic": "bookticker.BTCUSDT", // "ts": 1661742216109 // } // swap // { // "topic":"tickers.BTCUSDT", // "type":"snapshot", // "data":{ // "symbol":"BTCUSDT", // "tickDirection":"ZeroMinusTick", // "price24hPcnt":"0.032786", // "lastPrice":"22019.00", // "prevPrice24h":"21320.00", // "highPrice24h":"22522.00", // "lowPrice24h":"20745.00", // "prevPrice1h":"22186.50", // "markPrice":"22010.11", // "indexPrice":"22009.01", // "openInterest":"44334.438", // "turnover24h":"4609010554.786498", // "volume24h":"213532.606", // "fundingRate":"0.0001", // "nextFundingTime":"2022-07-18T16:00:00Z", // "bid1Price":"22019.00", // "bid1Size":"41.530", // "ask1Price":"22019.50", // "ask1Size":"7.041", // "basisRate":"0", // "deliveryFeeRate":"0" // }, // "cs":14236992078, // "ts":1663203915102 // } // const topic = this.safeString (message, 'topic', ''); const updateType = this.safeString (message, 'type', ''); const data = this.safeValue (message, 'data', {}); const isSpot = this.safeString (data, 's') !== undefined; let symbol = undefined; let parsed = undefined; if ((updateType === 'snapshot') || isSpot) { 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.market (marketId); symbol = market['symbol']; // update the info in place const ticker = this.safeValue (this.tickers, symbol, {}); const rawTicker = this.safeValue (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); } async watchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name bybit#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @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|undefined} since timestamp in ms of the earliest candle to fetch * @param {int|undefined} limit the maximum amount of candles to fetch * @param {object} params extra parameters specific to the bybit api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ await this.loadMarkets (); const market = this.market (symbol); symbol = market['symbol']; const url = this.getUrlByMarketType (symbol, false, false, params); params = this.cleanParams (params); let ohlcv = undefined; const marketType = market['spot'] ? 'spot' : 'contract'; const marketOptions = this.safeValue (this.options, marketType, {}); const timeframes = this.safeValue (marketOptions, 'timeframes', {}); const timeframeId = this.safeString (timeframes, timeframe, timeframe); const topics = [ 'kline.' + timeframeId + '.' + market['id'] ]; const messageHash = 'kline' + ':' + timeframeId + ':' + symbol; ohlcv = await this.watchTopics (url, messageHash, topics, params); if (this.newUpdates) { limit = ohlcv.getLimit (symbol, limit); } return this.filterBySinceLimit (ohlcv, since, limit, 0, true); } handleOHLCV (client, message) { // // swap // { // "topic":"kline.1.BTCUSDT", // "data":[ // { // "start":1658150220000, // "end":1658150279999, // "interval":"1", // "open":"22212", // "close":"22214", // "high":"22214.5", // "low":"22212", // "volume":"5.456", // "turnover":"121193.36", // "confirm":false, // "timestamp":1658150224542 // } // ], // "ts":1658150224542, // "type":"snapshot" // } // // spot // { // "data": { // "t": 1661742000000, // "s": "BTCUSDT", // "c": "19685.55", // "h": "19756.95", // "l": "19674.61", // "o": "19705.38", // "v": "0.232154" // }, // "type": "delta", // "topic": "kline.1h.BTCUSDT", // "ts": 1661745259605 // } // 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 marketId = this.safeString (topicParts, topicLength - 1); const market = this.safeMarket (marketId); const symbol = market['symbol']; const ohlcvsByTimeframe = this.safeValue (this.ohlcvs, symbol); if (ohlcvsByTimeframe === undefined) { this.ohlcvs[symbol] = {}; } let stored = this.safeValue (ohlcvsByTimeframe, timeframeId); if (stored === undefined) { const limit = this.safeInteger (this.options, 'OHLCVLimit', 1000); stored = new ArrayCacheByTimestamp (limit); this.ohlcvs[symbol][timeframeId] = stored; } if (Array.isArray (data)) { for (let i = 0; i < data.length; i++) { const parsed = this.parseWsContractOHLCV (data[i]); stored.append (parsed); } } else { const parsed = this.parseSpotOHLCV (data); stored.append (parsed); } const messageHash = 'kline' + ':' + timeframeId + ':' + symbol; client.resolve (stored, messageHash); } parseWsContractOHLCV (ohlcv) { // // { // "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 // } // return [ this.safeInteger (ohlcv, 'timestamp'), this.safeNumber (ohlcv, 'open'), this.safeNumber (ohlcv, 'high'), this.safeNumber (ohlcv, 'low'), this.safeNumber (ohlcv, 'close'), this.safeNumber2 (ohlcv, 'volume', 'turnover'), ]; } async watchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name bybit#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int|undefined} limit the maximum amount of order book entries to return. * @param {object} params extra parameters specific to the bybit api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols */ await this.loadMarkets (); const market = this.market (symbol); symbol = market['symbol']; const url = this.getUrlByMarketType (symbol, false, false, params); params = this.cleanParams (params); const messageHash = 'orderbook' + ':' + symbol; if (limit === undefined) { if (market['spot']) { limit = 40; } else { limit = 200; } } const topics = [ 'orderbook.' + limit.toString () + '.' + market['id'] ]; const orderbook = await this.watchTopics (url, messageHash, topics, params); return orderbook.limit (); } handleOrderBook (client, message) { // // spot snapshot // { // "data": { // "s": "BTCUSDT", // "t": 1661743689733, // "b": [ // [ // "19721.9", // "0.784806" // ], // ... // ], // "a": [ // [ // "19721.91", // "0.192687" // ], // ... // ] // }, // "type": "delta", // docs say to ignore, always snapshot // "topic": "orderbook.40.BTCUSDT", // "ts": 1661743689735 // } // // contract // { // "topic": "orderbook.50.BTCUSDT", // "type": "snapshot", // "ts": 1668748553479, // "data": { // "s": "BTCUSDT", // "b": [ // [ // "17053.00", //price // "0.021" //size // ], // .... // ], // "a": [ // [ // "17054.00", // "6.288" // ], // .... // ], // "u": 3083181, // "seq": 7545268447 // } // } // const isSpot = client.url.indexOf ('spot') >= 0; const type = this.safeString (message, 'type'); let isSnapshot = (type === 'snapshot'); if (isSpot) { isSnapshot = true; } const data = this.safeValue (message, 'data', {}); const marketId = this.safeString (data, 's'); const market = this.safeMarket (marketId); const symbol = this.safeSymbol (marketId, market); const timestamp = this.safeInteger (message, 'ts'); let orderbook = this.safeValue (this.orderbooks, symbol); if (orderbook === undefined) { orderbook = this.orderBook (); } if (isSnapshot) { const snapshot = this.parseOrderBook (data, symbol, timestamp, 'b', 'a'); orderbook.reset (snapshot); } else { const asks = this.safeValue (orderbook, 'a', []); const bids = this.safeValue (orderbook, '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); } handleDelta (bookside, delta) { const bidAsk = this.parseBidAsk (delta, 0, 1); bookside.storeArray (bidAsk); } handleDeltas (bookside, deltas) { for (let i = 0; i < deltas.length; i++) { this.handleDelta (bookside, deltas[i]); } } async watchTrades (symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name bybit#watchTrades * @description watches information on multiple trades made in a market * @param {string} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the bybit api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure */ await this.loadMarkets (); const market = this.market (symbol); symbol = market['symbol']; const url = this.getUrlByMarketType (symbol, false, false, params); params = this.cleanParams (params); const messageHash = 'trade:' + symbol; let topic = undefined; if (market['spot']) { topic = 'trade.' + market['id']; } else { topic = 'publicTrade.'; if (market['option']) { topic += market['baseId']; } else { topic += market['id']; } } const trades = await this.watchTopics (url, messageHash, [ topic ], params); if (this.newUpdates) { limit = trades.getLimit (symbol, limit); } return this.filterBySinceLimit (trades, since, limit, 'timestamp', true); } handleTrades (client, message) { // // swap // { // "topic": "publicTrade.BTCUSDT", // "type": "snapshot", // "ts": 1662694953823, // "data": [ // { // "T": 1662694953819, // "s": "BTCUSDT", // "S": "Buy", // "v": "0.010", // "p": "19792.50", // "L": "PlusTick", // "i": "5c9ab13e-6010-522c-aecd-02c4d9c8db3d", // "BT": false // } // ] // } // // spot // { // "data": { // "v": "2100000000001992601", // "t": 1661742109857, // "p": "19706.87", // "q": "0.000158", // "m": true // }, // "type": "delta", // "topic": "trade.BTCUSDT", // "ts": 1661742109863 // } // const data = this.safeValue (message, 'data', {}); const topic = this.safeString (message, 'topic'); let trades = undefined; const parts = topic.split ('.'); const marketId = this.safeString (parts, 1); const market = this.safeMarket (marketId); if (Array.isArray (data)) { // contract markets trades = data; } else { // spot markets trades = [ data ]; } const symbol = market['symbol']; let stored = this.safeValue (this.trades, symbol); if (stored === undefined) { const limit = this.safeInteger (this.options, 'tradesLimit', 1000); stored = new ArrayCache (limit); this.trades[symbol] = stored; } for (let j = 0; j < trades.length; j++) { const parsed = this.parseWsTrade (trades[j], market); stored.append (parsed); } const messageHash = 'trade' + ':' + symbol; client.resolve (stored, messageHash); } parseWsTrade (trade, market = undefined) { // // contract public // { // T: 1670198879981, // s: 'BTCUSDT', // S: 'Buy', // v: '0.001', // p: '17130.00', // L: 'ZeroPlusTick', // i: 'a807f4ee-2e8b-5f21-a02a-3e258ddbfdc9', // BT: false // } // contract private // // parsed by rest implementation // { // "symbol": "BITUSDT", // "execFee": "0.02022", // "execId": "beba036f-9fb4-59a7-84b7-2620e5d13e1c", // "execPrice": "0.674", // "execQty": "50", // "execType": "Trade", // "execValue": "33.7", // "feeRate": "0.0006", // "lastLiquidityInd": "RemovedLiquidity", // "leavesQty": "0", // "orderId": "ddbea432-2bd7-45dd-ab42-52d920b8136d", // "orderLinkId": "b001", // "orderPrice": "0.707", // "orderQty": "50", // "orderType": "Market", // "stopOrderType": "UNKNOWN", // "side": "Buy", // "execTime": "1659057535081", // "closedSize": "0" // } // // spot public // // { // 'symbol': 'BTCUSDT', // artificially added // v: '2290000000003002848', // trade id // t: 1652967602261, // p: '29698.82', // q: '0.189531', // m: true // } // // spot private // { // "e": "ticketInfo", // "E": "1662348310386", // "s": "BTCUSDT", // "q": "0.001007", // "t": "1662348310373", // "p": "19842.02", // "T": "2100000000002220938", // "o": "1238261807653647872", // "c": "spotx008", // "O": "1238225004531834368", // "a": "533287", // "A": "642908", // "m": false, // "S": "BUY" // } // const id = this.safeStringN (trade, [ 'i', 'T', 'v' ]); const marketId = this.safeString (trade, 's'); market = this.safeMarket (marketId, market); const symbol = market['symbol']; const timestamp = this.safeInteger2 (trade, 't', 'T'); let side = this.safeStringLower (trade, 'S'); let takerOrMaker = undefined; const m = this.safeValue (trade, 'm'); if (side === undefined) { side = m ? 'buy' : 'sell'; } else { // spot private takerOrMaker = m; } const price = this.safeString (trade, 'p'); const amount = this.safeString2 (trade, 'q', 'v'); const orderId = this.safeString (trade, 'o'); return this.safeTrade ({ 'id': id, 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'order': orderId, 'type': undefined, 'side': side, 'takerOrMaker': takerOrMaker, 'price': price, 'amount': amount, 'cost': undefined, 'fee': undefined, }, market); } getPrivateType (url) { if (url.indexOf ('spot') >= 0) { return 'spot'; } else if (url.indexOf ('unified') >= 0) { return 'unified'; } else if (url.indexOf ('contract') >= 0) { return 'contract'; } } async watchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bybit#watchMyTrades * @description watches information on multiple trades made by the user * @param {string} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the bybit api endpoint * @param {boolean} params.unifiedMargin use unified margin account * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure */ const method = 'watchMyTrades'; let messageHash = 'myTrades'; await this.loadMarkets (); if (symbol !== undefined) { symbol = this.symbol (symbol); messageHash += ':' + symbol; } const isUnifiedMargin = await this.isUnifiedMarginEnabled (); const url = this.getUrlByMarketType (symbol, true, isUnifiedMargin, method, params); await this.authenticate (url); const topicByMarket = { 'spot': 'ticketInfo', 'contract': 'user.execution.contractAccount', 'unified': 'user.execution.unifiedAccount', }; const topic = this.safeValue (topicByMarket, this.getPrivateType (url)); const trades = await this.watchTopics (url, messageHash, [ topic ], params); if (this.newUpdates) { limit = trades.getLimit (symbol, limit); } return this.filterBySinceLimit (trades, since, limit, 'timestamp', true); } handleMyTrades (client, message) { // // { // "type": "snapshot", // "topic": "ticketInfo", // "ts": "1662348310388", // "data": [ // { // "e": "ticketInfo", // "E": "1662348310386", // "s": "BTCUSDT", // "q": "0.001007", // "t": "1662348310373", // "p": "19842.02", // "T": "2100000000002220938", // "o": "1238261807653647872", // "c": "spotx008", // "O": "1238225004531834368", // "a": "533287", // "A": "642908", // "m": false, // "S": "BUY" // } // ] // } // contract // { // topic: 'user.execution.contractAccount', // data: [ // { // symbol: 'BTCUSD', // execFee: '0.00000004', // execId: '7d0f66da-8312-52a9-959b-9fba58a90af0', // execPrice: '17228.00', // execQty: '1', // execType: 'Trade', // execValue: '0.00005804', // feeRate: '0.0006', // lastLiquidityInd: 'RemovedLiquidity', // leavesQty: '0', // orderId: '6111f83d-2c8c-463a-b9a8-77885eae2f57', // orderLinkId: '', // orderPrice: '17744.50', // orderQty: '1', // orderType: 'Market', // stopOrderType: 'UNKNOWN', // side: 'Buy', // execTime: '1670210101997', // closedSize: '0' // } // ] // } // const topic = this.safeString (message, 'topic'); const spot = topic === 'ticketInfo'; let data = this.safeValue (message, 'data', []); // unified margin data = this.safeValue (data, 'result', data); if (this.myTrades === undefined) { const limit = this.safeInteger (this.options, 'tradesLimit', 1000); this.myTrades = new ArrayCacheBySymbolById (limit); } const trades = this.myTrades; const symbols = {}; const method = spot ? 'parseWsTrade' : 'parseTrade'; for (let i = 0; i < data.length; i++) { const rawTrade = data[i]; const parsed = this[method] (rawTrade); const symbol = parsed['symbol']; symbols[symbol] = true; trades.append (parsed); } const keys = Object.keys (symbols); for (let i = 0; i < keys.length; i++) { const messageHash = 'myTrades:' + keys[i]; client.resolve (trades, messageHash); } // non-symbol specific const messageHash = 'myTrades'; client.resolve (trades, messageHash); } async watchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bybit#watchOrders * @description watches information on multiple orders made by the user * @param {string|undefined} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the bybit api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure */ await this.loadMarkets (); const method = 'watchOrders'; let messageHash = 'orders'; if (symbol !== undefined) { symbol = this.symbol (symbol); messageHash += ':' + symbol; } const isUnifiedMargin = await this.isUnifiedMarginEnabled (); const url = this.getUrlByMarketType (undefined, true, isUnifiedMargin, method, params); await this.authenticate (url); const topicsByMarket = { 'spot': [ 'order', 'stopOrder' ], 'contract': [ 'user.order.contractAccount' ], 'unified': [ 'user.order.unifiedAccount' ], }; const topics = this.safeValue (topicsByMarket, this.getPrivateType (url)); const orders = await this.watchTopics (url, messageHash, topics, params); if (this.newUpdates) { limit = orders.getLimit (symbol, limit); } return this.filterBySymbolSinceLimit (orders, symbol, since, limit, true); } handleOrder (client, message, subscription = undefined) { // // spot // { // "type": "snapshot", // "topic": "order", // "ts": "1662348310441", // "data": [ // { // "e": "order", // "E": "1662348310441", // "s": "BTCUSDT", // "c": "spotx008", // "S": "BUY", // "o": "MARKET_OF_QUOTE", // "f": "GTC", // "q": "20", // "p": "0", // "X": "CANCELED", // "i": "1238261807653647872", // "M": "1238225004531834368", // "l": "0.001007", // "z": "0.001007", // "L": "19842.02", // "n": "0", // "N": "BTC", // "u": true, // "w": true, // "m": false, // "O": "1662348310368", // "Z": "19.98091414", // "A": "0", // "C": false, // "v": "0", // "d": "NO_LIQ", // "t": "2100000000002220938" // } // ] // } // contract // { // "topic": "user.order.contractAccount", // "data": [ // { // "symbol": "BTCUSD", // "orderId": "ee013d82-fafc-4504-97b1-d92aca21eedd", // "side": "Buy", // "orderType": "Market", // "stopOrderType": "UNKNOWN", // "price": "21920.00", // "qty": "200", // "timeInForce": "ImmediateOrCancel", // "orderStatus": "Filled", // "triggerPrice": "0.00", // "orderLinkId": "inv001", // "createdTime": "1661338622771", // "updatedTime": "1661338622775", // "takeProfit": "0.00", // "stopLoss": "0.00", // "tpTriggerBy": "UNKNOWN", // "slTriggerBy": "UNKNOWN", // "triggerBy": "UNKNOWN", // "reduceOnly": false, // "closeOnTrigger": false, // "triggerDirection": 0, // "leavesQty": "0", // "lastExecQty": "200", // "lastExecPrice": "21282.00", // "cumExecQty": "200", // "cumExecValue": "0.00939761" // } // ] // } // unified // { // "id": "f91080af-5187-4261-a802-7604419771aa", // "topic": "user.order.unifiedAccount", // "ts": 1661932033707, // "data": { // "result": [ // { // "orderId": "415f8961-4073-4d74-bc3e-df2830e52843", // "orderLinkId": "", // "symbol": "BTCUSDT", // "side": "Buy", // "orderType": "Limit", // "price": "17000.00000000", // "qty": "0.0100", // "timeInForce": "GoodTillCancel", // "orderStatus": "New", // "cumExecQty": "0.0000", // "cumExecValue": "0.00000000", // "cumExecFee": "0.00000000", // "stopOrderType": "UNKNOWN", // "triggerBy": "UNKNOWN", // "triggerPrice": "", // "reduceOnly": true, // "closeOnTrigger": true, // "createdTime": 1661932033636, // "updatedTime": 1661932033644, // "iv": "", // "orderIM": "", // "takeProfit": "", // "stopLoss": "", // "tpTriggerBy": "UNKNOWN", // "slTriggerBy": "UNKNOWN", // "basePrice": "", // "blockTradeId": "", // "leavesQty": "0.0100" // } // ], // "version": 284 // }, // "type": "snapshot" // } // const topic = this.safeString (message, 'topic', ''); if (this.orders === undefined) { const limit = this.safeInteger (this.options, 'ordersLimit', 1000); this.orders = new ArrayCacheBySymbolById (limit); } const orders = this.orders; let rawOrders = []; let parser = undefined; if (topic === 'order') { rawOrders = this.safeValue (message, 'data', []); parser = 'parseWsSpotOrder'; } else { parser = 'parseContractOrder'; rawOrders = this.safeValue (message, 'data', []); rawOrders = this.safeValue (rawOrders, 'result', rawOrders); } const symbols = {}; for (let i = 0; i < rawOrders.length; i++) { const parsed = this[parser] (rawOrders[i]); const symbol = parsed['symbol']; symbols[symbol] = true; orders.append (parsed); } const symbolsArray = Object.keys (symbols); for (let i = 0; i < symbolsArray.length; i++) { const messageHash = 'orders:' + symbolsArray[i]; client.resolve (orders, messageHash); } const messageHash = 'orders'; client.resolve (orders, messageHash); } parseWsSpotOrder (order, market = undefined) { // // { // e: 'executionReport', // E: '1653297251061', // timestamp // s: 'LTCUSDT', // symbol // c: '1653297250740', // user id // S: 'SELL', // side // o: 'MARKET_OF_BASE', // order type // f: 'GTC', // time in force // q: '0.16233', // quantity // p: '0', // price // X: 'NEW', // status // i: '1162336018974750208', // order id // M: '0', // l: '0', // last filled // z: '0', // total filled // L: '0', // last traded price // n: '0', // trading fee // N: '', // fee asset // u: true, // w: true, // m: false, // is limit_maker // O: '1653297251042', // order creation // Z: '0', // total filled // A: '0', // account id // C: false, // is close // v: '0', // leverage // d: 'NO_LIQ' // } // const id = this.safeString (order, 'i'); const marketId = this.safeString (order, 's'); const symbol = this.safeSymbol (marketId, market); const timestamp = this.safeInteger (order, 'O'); let price = this.safeString (order, 'p'); if (price === '0') { price = undefined; // market orders } const filled = this.safeString (order, 'z'); const status = this.parseOrderStatus (this.safeString (order, 'X')); const side = this.safeStringLower (order, 'S'); const lastTradeTimestamp = this.safeString (order, 'E'); const timeInForce = this.safeString (order, 'f'); let amount = undefined; const cost = this.safeString (order, 'Z'); const q = this.safeString (order, 'q'); let type = this.safeStringLower (order, 'o'); if (type.indexOf ('quote') >= 0) { amount = filled; } else { amount = q; } if (type.indexOf ('market') >= 0) { type = 'market'; } let fee = undefined; const feeCost = this.safeString (order, 'n'); if (feeCost !== undefined && feeCost !== '0') { const feeCurrencyId = this.safeString (order, 'N'); const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId); fee = { 'cost': feeCost, 'currency': feeCurrencyCode, }; } return this.safeOrder ({ 'info': order, 'id': id, 'clientOrderId': this.safeString (order, 'c'), 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': lastTradeTimestamp, 'symbol': symbol, 'type': type, 'timeInForce': timeInForce, 'postOnly': undefined, 'side': side, 'price': price, 'stopPrice': undefined, 'amount': amount, 'cost': cost, 'average': undefined, 'filled': filled, 'remaining': undefined, 'status': status, 'fee': fee, }, market); } async watchBalance (params = {}) { /** * @method * @name bybit#watchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @param {object} params extra parameters specific to the bybit api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ const method = 'watchBalance'; const messageHash = 'balances'; const isUnifiedMargin = await this.isUnifiedMarginEnabled (); const url = this.getUrlByMarketType (undefined, true, isUnifiedMargin, method, params); await this.authenticate (url); const topicByMarket = { 'spot': 'outboundAccountInfo', 'contract': 'user.wallet.contractAccount', 'unified': 'user.wallet.unifiedAccount', }; const topics = [ this.safeValue (topicByMarket, this.getPrivateType (url)) ]; return await this.watchTopics (url, messageHash, topics, params); } handleBalance (client, message) { // // spot // { // "type": "snapshot", // "topic": "outboundAccountInfo", // "ts": "1662107217641", // "data": [ // { // "e": "outboundAccountInfo", // "E": "1662107217640", // "T": true, // "W": true, // "D": true, // "B": [ // { // "a": "USDT", // "f": "176.81254174", // "l": "201.575" // } // ] // } // ] // } // contract // { // "topic": "user.wallet.contractAccount", // "data": [ // { // "coin": "USDT", // "equity": "610.3984319", // "walletBalance": "609.7384319", // "positionMargin": "4.7582882", // "availableBalance": "604.9801437", // "orderMargin": "0", // "unrealisedPnl": "0.66", // "cumRealisedPnl": "-0.2615681" // } // ] // } // unified // { // "id": "46bd0430-1d03-48f7-a503-c6c020d07536", // "topic": "user.wallet.unifiedAccount", // "ts": 1649150852199, // "data": { // "result": { // "accountIMRate": "0.0002", // "accountMMRate": "0.0000", // "totalEquity": "510444.50000000", // "totalWalletBalance": "510444.50000000", // "totalMarginBalance": "510444.50000000", // "totalAvailableBalance": "510333.52491801", // "totalPerpUPL": "0.00000000", // "totalInitialMargin": "110.97508199", // "totalMaintenanceMargin": "9.13733489", // "coin": [{ // "currencyCoin": "USDC", // "equity": "0.00000000", // "usdValue": "0.00000000", // "walletBalance": "0.00000000", // "marginBalance": "510444.50000000", // "availableBalance": "510333.52491801", // "marginBalanceWithoutConvert": "0.00000000", // "availableBalanceWithoutConvert": "0.00000000", // "borrowSize": "0.00000000", // "availableToBorrow": "200000.00000000", // "accruedInterest": "0.00000000", //