UNPKG

@kraken-crypto/ccxt

Version:

A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go

1,165 lines (1,160 loc) 49.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var toobit$1 = require('../toobit.js'); var errors = require('../base/errors.js'); var Cache = require('../base/ws/Cache.js'); // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- class toobit extends toobit$1["default"] { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'watchBalance': true, 'watchMyTrades': true, 'watchOHLCV': true, 'watchOHLCVForSymbols': true, 'watchOrderBook': true, 'watchOrderBookForSymbols': true, 'watchOrders': true, 'watchTicker': true, 'watchTickers': true, 'watchTrades': true, 'watchTradesForSymbols': true, // 'watchPosition': false, }, 'urls': { 'api': { 'ws': { 'common': 'wss://stream.toobit.com', }, }, }, 'options': { 'ws': { 'timeframes': { '1m': '1m', '3m': '3m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '2h': '2h', '4h': '4h', '6h': '6h', '8h': '8h', '12h': '12h', '1d': '1d', '1w': '1w', '1M': '1M', }, 'watchOrderBook': { 'channel': 'depth', // depth, diffDepth }, 'listenKeyRefreshRate': 1200000, // 20 mins }, }, 'streaming': { 'keepAlive': (60 - 1) * 5 * 1000, 'ping': this.ping, }, 'exceptions': { 'ws': { 'exact': {}, }, }, }); } ping(client) { return { 'ping': this.milliseconds(), }; } handleMessage(client, message) { // // public // // { // topic: "trade", // symbol: "DOGEUSDT", // symbolName: "DOGEUSDT", // params: { // realtimeInterval: "24h", // binary: "false", // }, // data: [ // { // v: "4864732022868004630", // t: 1757243788405, // p: "0.21804", // q: "80", // m: true, // }, // ], // f: true, // initial first snapshot or not // sendTime: 1757244002117, // shared: false, // } // // private // // [ // { // e: 'outboundContractAccountInfo', // E: '1758228398234', // T: true, // W: true, // D: true, // B: [ [Object] ] // } // ] // const topic = this.safeString(message, 'topic'); if (this.handleErrorMessage(client, message)) { return; } // // handle ping-pong: { ping: 1758540450000 } // const pongTimestamp = this.safeInteger(message, 'pong'); if (pongTimestamp !== undefined) { this.handleIncomingPong(client, pongTimestamp); return; } const methods = { 'trade': this.handleTrades, 'kline': this.handleOHLCV, 'realtimes': this.handleTickers, 'depth': this.handleOrderBookPartialSnapshot, 'diffDepth': this.handleOrderBook, 'outboundAccountInfo': this.handleBalance, 'outboundContractAccountInfo': this.handleBalance, 'executionReport': this.handleOrder, 'contractExecutionReport': this.handleOrder, 'ticketInfo': this.handleMyTrade, 'outboundContractPositionInfo': this.handlePositions, }; const method = this.safeValue(methods, topic); if (method !== undefined) { method.call(this, client, message); } else { // check private streams for (let i = 0; i < message.length; i++) { const item = message[i]; const event = this.safeString(item, 'e'); const method2 = this.safeValue(methods, event); if (method2 !== undefined) { method2.call(this, client, item); } } } } handleIncomingPong(client, pongTimestamp) { client.lastPong = pongTimestamp; } /** * @method * @name toobit#watchTrades * @description watches information on multiple trades made in a market * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#trade-streams * @param {string} symbol unified market symbol of the market trades were made in * @param {int} [since] the earliest time in ms to fetch trades for * @param {int} [limit] the maximum number of trade structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure} */ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { return await this.watchTradesForSymbols([symbol], since, limit, params); } /** * @method * @name toobit#watchTradesForSymbols * @description get the list of most recent trades for a list of symbols * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#trade-streams * @param {string[]} symbols unified symbol of the market to fetch trades for * @param {int} [since] timestamp in ms of the earliest trade to fetch * @param {int} [limit] the maximum amount of trades to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {string} [params.name] the name of the method to call, 'trade' or 'aggTrade', default is 'trade' * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades} */ async watchTradesForSymbols(symbols, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false); const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.market(symbol); messageHashes.push('trade::' + symbol); market['id']; } const marketIds = this.marketIds(symbols); const url = this.urls['api']['ws']['common'] + '/quote/ws/v1'; const request = { 'symbol': marketIds.join(','), 'topic': 'trade', 'event': 'sub', }; const trades = await this.watchMultiple(url, messageHashes, this.extend(request, params), messageHashes); if (this.newUpdates) { const first = this.safeValue(trades, 0); const tradeSymbol = this.safeString(first, 'symbol'); limit = trades.getLimit(tradeSymbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } handleTrades(client, message) { // // { // symbol: "DOGEUSDT", // symbolName: "DOGEUSDT", // topic: "trade", // params: { // realtimeInterval: "24h", // binary: "false", // }, // data: [ // { // v: "4864732022868004630", // t: 1757243788405, // p: "0.21804", // q: "80", // m: true, // }, // ], // f: true, // initial first snapshot or not // sendTime: 1757244002117, // shared: false, // } // const marketId = this.safeString(message, 'symbol'); const market = this.safeMarket(marketId); const symbol = market['symbol']; if (!(symbol in this.trades)) { const limit = this.safeInteger(this.options, 'tradesLimit', 1000); this.trades[symbol] = new Cache.ArrayCache(limit); } const stored = this.trades[symbol]; const data = this.safeList(message, 'data', []); const parsed = this.parseWsTrades(data, market); for (let i = 0; i < parsed.length; i++) { const trade = parsed[i]; trade['symbol'] = symbol; stored.append(trade); } const messageHash = 'trade::' + symbol; client.resolve(stored, messageHash); } parseWsTrade(trade, market = undefined) { return this.parseTrade(trade, market); } /** * @method * @name toobit#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#kline-candlestick-streams * @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 toobit#watchOHLCVForSymbols * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#kline-candlestick-streams * @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 url = this.urls['api']['ws']['common'] + '/quote/ws/v1'; const messageHashes = []; const timeframes = this.safeDict(this.options['ws'], 'timeframes', {}); const marketIds = []; let selectedTimeframe = undefined; for (let i = 0; i < symbolsAndTimeframes.length; i++) { const data = symbolsAndTimeframes[i]; const symbolStr = this.safeString(data, 0); const market = this.market(symbolStr); const marketId = market['id']; const unfiedTimeframe = this.safeString(data, 1, '1m'); const rawTimeframe = this.safeString(timeframes, unfiedTimeframe, unfiedTimeframe); if (selectedTimeframe !== undefined && selectedTimeframe !== rawTimeframe) { throw new errors.NotSupported(this.id + ' watchOHLCVForSymbols() only supports a single timeframe for all symbols'); } else { selectedTimeframe = rawTimeframe; } marketIds.push(marketId); messageHashes.push('ohlcv::' + symbolStr + '::' + unfiedTimeframe); } const request = { 'symbol': marketIds.join(','), 'topic': 'kline_' + selectedTimeframe, 'event': 'sub', }; const [symbol, timeframe, stored] = await this.watchMultiple(url, messageHashes, this.extend(request, params), messageHashes); if (this.newUpdates) { limit = stored.getLimit(symbol, limit); } const filtered = this.filterBySinceLimit(stored, since, limit, 0, true); return this.createOHLCVObject(symbol, timeframe, filtered); } handleOHLCV(client, message) { // // { // symbol: 'DOGEUSDT', // symbolName: 'DOGEUSDT', // klineType: '1m', // topic: 'kline', // params: { realtimeInterval: '24h', klineType: '1m', binary: 'false' }, // data: [ // { // t: 1757251200000, // s: 'DOGEUSDT', // sn: 'DOGEUSDT', // c: '0.21889', // h: '0.21898', // l: '0.21889', // o: '0.21897', // v: '5247', // st: 0 // } // ], // f: true, // sendTime: 1757251217643, // shared: false // } // const marketId = this.safeString(message, 'symbol'); const market = this.market(marketId); const symbol = market['symbol']; const params = this.safeDict(message, 'params', {}); const timeframeId = this.safeString(params, 'klineType'); const timeframe = this.findTimeframe(timeframeId); if (!(symbol in this.ohlcvs)) { this.ohlcvs[symbol] = {}; } if (!(timeframe in this.ohlcvs[symbol])) { const limit = this.safeInteger(this.options['ws'], 'OHLCVLimit', 1000); this.ohlcvs[symbol][timeframe] = new Cache.ArrayCacheByTimestamp(limit); } const stored = this.ohlcvs[symbol][timeframe]; const data = this.safeList(message, 'data', []); 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) { // // { // t: 1757251200000, // o: '0.21897', // h: '0.21898', // l: '0.21889', // c: '0.21889', // v: '5247', // s: 'DOGEUSDT', // sn: 'DOGEUSDT', // st: 0 // } // const parsed = this.parseOHLCV(ohlcv, market); return parsed; } /** * @method * @name toobit#watchTicker * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#individual-symbol-ticker-streams * @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 exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchTicker(symbol, params = {}) { await this.loadMarkets(); symbol = this.symbol(symbol); const tickers = await this.watchTickers([symbol], params); return tickers[symbol]; } /** * @method * @name toobit#watchTickers * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#individual-symbol-ticker-streams * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list * @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 = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.market(symbol); messageHashes.push('ticker::' + symbol); market['id']; } const marketIds = this.marketIds(symbols); const url = this.urls['api']['ws']['common'] + '/quote/ws/v1'; const request = { 'symbol': marketIds.join(','), 'topic': 'realtimes', 'event': 'sub', }; const ticker = await this.watchMultiple(url, messageHashes, this.extend(request, params), messageHashes); if (this.newUpdates) { const result = {}; result[ticker['symbol']] = ticker; return result; } return this.filterByArray(this.tickers, 'symbol', symbols); } handleTickers(client, message) { // // { // "symbol": "DOGEUSDT", // "symbolName": "DOGEUSDT", // "topic": "realtimes", // "params": { // "realtimeInterval": "24h" // }, // "data": [ // { // "t": 1757257643683, // "s": "DOGEUSDT", // "o": "0.21462", // "h": "0.22518", // "l": "0.21229", // "c": "0.2232", // "v": "283337017", // "qv": "62063771.42702", // "sn": "DOGEUSDT", // "m": "0.04", // "e": 301, // "c24h": "0.2232", // "h24h": "0.22518", // "l24h": "0.21229", // "o24h": "0.21462", // "v24h": "283337017", // "qv24h": "62063771.42702", // "m24h": "0.04" // } // ], // "f": false, // "sendTime": 1757257643751, // "shared": false // } // const data = this.safeList(message, 'data'); const newTickers = {}; for (let i = 0; i < data.length; i++) { const ticker = data[i]; const parsed = this.parseWsTicker(ticker); const symbol = parsed['symbol']; this.tickers[symbol] = parsed; newTickers[symbol] = parsed; const messageHash = 'ticker::' + symbol; client.resolve(parsed, messageHash); } client.resolve(newTickers, 'tickers'); } parseWsTicker(ticker, market = undefined) { return this.parseTicker(ticker, market); } /** * @method * @name toobit#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#partial-book-depth-streams * @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 toobit#watchOrderBookForSymbols * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#partial-book-depth-streams * @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(); symbols = this.marketSymbols(symbols, undefined, false); let channel = undefined; [channel, params] = this.handleOptionAndParams(params, 'watchOrderBook', 'channel', 'depth'); const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.market(symbol); messageHashes.push('orderBook::' + symbol + '::' + channel); market['id']; } const marketIds = this.marketIds(symbols); const url = this.urls['api']['ws']['common'] + '/quote/ws/v1'; const request = { 'symbol': marketIds.join(','), 'topic': channel, 'event': 'sub', }; const orderbook = await this.watchMultiple(url, messageHashes, this.extend(request, params), messageHashes); return orderbook.limit(); } handleOrderBook(client, message) { // // { // symbol: 'DOGEUSDT', // symbolName: 'DOGEUSDT', // topic: 'depth', // params: { realtimeInterval: '24h' }, // data: [ // { // e: 301, // t: 1757304842860, // v: '9814355_1E-18', // b: [Array], // a: [Array], // o: 0 // } // ], // f: false, // sendTime: 1757304843047, // shared: false // } // const isSnapshot = this.safeBool(message, 'f', false); if (isSnapshot) { this.setOrderBookSnapshot(client, message, 'diffDepth'); return; } const marketId = this.safeString(message, 'symbol'); const market = this.safeMarket(marketId); const symbol = market['symbol']; const data = this.safeList(message, 'data', []); for (let i = 0; i < data.length; i++) { const entry = data[i]; const messageHash = 'orderBook::' + symbol + '::' + 'diffDepth'; if (!(symbol in this.orderbooks)) { const limit = this.safeInteger(this.options['ws'], 'orderBookLimit', 1000); this.orderbooks[symbol] = this.orderBook({}, limit); } const orderBook = this.orderbooks[symbol]; const timestamp = this.safeInteger(entry, 't'); const bids = this.safeList(entry, 'b', []); const asks = this.safeList(entry, 'a', []); this.handleDeltas(orderBook['asks'], asks); this.handleDeltas(orderBook['bids'], bids); orderBook['timestamp'] = timestamp; this.orderbooks[symbol] = orderBook; client.resolve(orderBook, messageHash); } } handleDelta(bookside, delta) { const bidAsk = this.parseBidAsk(delta); bookside.storeArray(bidAsk); } handleOrderBookPartialSnapshot(client, message) { // // { // symbol: 'DOGEUSDT', // symbolName: 'DOGEUSDT', // topic: 'depth', // params: { realtimeInterval: '24h' }, // data: [ // { // e: 301, // s: 'DOGEUSDT', // t: 1757304842860, // v: '9814355_1E-18', // b: [Array], // a: [Array], // o: 0 // } // ], // f: false, // sendTime: 1757304843047, // shared: false // } // this.setOrderBookSnapshot(client, message, 'depth'); } setOrderBookSnapshot(client, message, channel) { const data = this.safeList(message, 'data', []); const length = data.length; if (length === 0) { return; } for (let i = 0; i < length; i++) { const entry = data[i]; const marketId = this.safeString(entry, 's'); const symbol = this.safeSymbol(marketId); const messageHash = 'orderBook::' + symbol + '::' + channel; if (!(symbol in this.orderbooks)) { const limit = this.safeInteger(this.options['ws'], 'orderBookLimit', 1000); this.orderbooks[symbol] = this.orderBook({}, limit); } const orderbook = this.orderbooks[symbol]; const timestamp = this.safeInteger(entry, 't'); const snapshot = this.parseOrderBook(entry, symbol, timestamp, 'b', 'a'); orderbook.reset(snapshot); client.resolve(orderbook, messageHash); } } /** * @method * @name toobit#watchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#payload-account-update * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure} */ async watchBalance(params = {}) { await this.loadMarkets(); await this.authenticate(); let marketType = undefined; [marketType, params] = this.handleMarketTypeAndParams('watchBalance', undefined, params); const isSpot = (marketType === 'spot'); const type = isSpot ? 'spot' : 'contract'; const spotSubHash = 'spot:balance'; const swapSubHash = 'contract:private'; const spotMessageHash = 'spot:balance'; const swapMessageHash = 'contract:balance'; const messageHash = isSpot ? spotMessageHash : swapMessageHash; const subscriptionHash = isSpot ? spotSubHash : swapSubHash; const url = this.getUserStreamUrl(); const client = this.client(url); this.setBalanceCache(client, marketType, subscriptionHash, params); client.future(type + ':fetchBalanceSnapshot'); return await this.watch(url, messageHash, params, subscriptionHash); } setBalanceCache(client, marketType, subscriptionHash = undefined, params = {}) { if (subscriptionHash in client.subscriptions) { return; } const type = (marketType === 'spot') ? 'spot' : 'contract'; const messageHash = type + ':fetchBalanceSnapshot'; if (!(messageHash in client.futures)) { client.future(messageHash); this.spawn(this.loadBalanceSnapshot, client, messageHash, marketType); } } handleBalance(client, message) { // // spot // // [ // { // e: 'outboundAccountInfo', // E: '1758226989725', // T: true, // W: true, // D: true, // B: [ // { // a: "USDT", // f: "6.37242839", // l: "0", // }, // ] // } // ] // // contract // // [ // { // e: 'outboundContractAccountInfo', // E: '1758226989742', // T: true, // W: true, // D: true, // B: [ [Object] ] // } // ] // const channel = this.safeString(message, 'e'); const data = this.safeList(message, 'B', []); const timestamp = this.safeInteger(message, 'E'); const type = (channel === 'outboundContractAccountInfo') ? 'contract' : 'spot'; if (!(type in this.balance)) { this.balance[type] = {}; } this.balance[type]['info'] = data; this.balance[type]['timestamp'] = timestamp; this.balance[type]['datetime'] = this.iso8601(timestamp); for (let i = 0; i < data.length; i++) { const balance = data[i]; const currencyId = this.safeString(balance, 'a'); const code = this.safeCurrencyCode(currencyId); const account = this.account(); account['info'] = balance; account['used'] = this.safeString(balance, 'l'); account['free'] = this.safeString(balance, 'f'); this.balance[type][code] = account; } this.balance[type] = this.safeBalance(this.balance[type]); client.resolve(this.balance[type], type + ':balance'); } async loadBalanceSnapshot(client, messageHash, marketType) { const response = await this.fetchBalance({ 'type': marketType }); const type = (marketType === 'spot') ? 'spot' : 'contract'; this.balance[type] = this.extend(response, this.safeDict(this.balance, type, {})); // don't remove the future from the .futures cache const future = client.futures[messageHash]; future.resolve(); client.resolve(this.balance[type], type + ':fetchBalanceSnapshot'); client.resolve(this.balance[type], type + ':balance'); // we should also resolve right away after snapshot, so user doesn't double-fetch balance } /** * @method * @name toobit#watchOrders * @description watches information on multiple orders made by the user * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#payload-order-update * @param {string} symbol unified market symbol of the market orders were made in * @param {int} [since] the earliest time in ms to fetch orders for * @param {int} [limit] the maximum number of order structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} */ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); await this.authenticate(); const market = this.marketOrNull(symbol); symbol = this.safeString(market, 'symbol', symbol); let messageHash = 'orders'; if (symbol !== undefined) { messageHash = messageHash + ':' + symbol; } const url = this.getUserStreamUrl(); const orders = await this.watch(url, messageHash, params, messageHash); if (this.newUpdates) { limit = orders.getLimit(symbol, limit); } return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true); } handleOrder(client, message) { // // { // "e": "executionReport", // "E": "1758311011844", // "s": "DOGEUSDT", // "c": "1758311011948", // "S": "BUY", // "o": "LIMIT", // "f": "GTC", // "q": "22", // "p": "0.23", // "pt": "INPUT", // "X": "NEW", // "i": "2043255292855185152", // "l": "0", // Last executed quantity // "z": "0", // Cumulative filled quantity // "L": "0", // Last executed price // "n": "0", // "N": "", // "u": true, // "w": true, // "m": false, // "O": "1758311011833", // "U": "1758311011841", // "Z": "0", // "C": false, // "v": "0", // "rp": "0", // "td": "0" // } // if (this.orders === undefined) { const limit = this.safeInteger(this.options, 'ordersLimit', 1000); this.orders = new Cache.ArrayCacheBySymbolById(limit); } const orders = this.orders; const order = this.parseWsOrder(message); orders.append(order); let messageHash = 'orders'; client.resolve(orders, messageHash); messageHash = 'orders:' + this.safeString(order, 'symbol'); client.resolve(orders, messageHash); } parseWsOrder(order, market = undefined) { const timestamp = this.safeInteger(order, 'O'); const marketId = this.safeString(order, 's'); const symbol = this.safeSymbol(marketId, market); const priceType = this.safeStringLower(order, 'pt'); const rawOrderType = this.safeStringLower(order, 'o'); let orderType = undefined; if (priceType === 'market') { orderType = 'market'; } else { orderType = rawOrderType; } const feeCost = this.safeNumber(order, 'n'); let fee = undefined; if (feeCost !== undefined) { fee = { 'cost': feeCost, 'currency': undefined, }; } return this.safeOrder({ 'info': order, 'id': this.safeString(order, 'i'), 'clientOrderId': this.safeString(order, 'c'), 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'lastUpdateTimestamp': this.safeInteger2(order, 'U', 'E'), 'symbol': symbol, 'type': orderType, 'timeInForce': this.safeStringUpper(order, 'f'), 'postOnly': undefined, 'side': this.safeStringLower(order, 'S'), 'price': this.safeString(order, 'L'), 'stopPrice': undefined, 'triggerPrice': undefined, 'amount': this.safeString(order, 'q'), 'cost': undefined, 'average': this.safeString(order, 'p'), 'filled': this.safeString(order, 'z'), 'remaining': undefined, 'status': this.parseOrderStatus(this.safeString(order, 'X')), 'fee': fee, 'trades': undefined, }, market); } /** * @method * @name toobit#watchMyTrades * @description watches information on multiple trades made by the user * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#payload-ticket-push * @param {string} symbol unified market symbol of the market trades were made in * @param {int} [since] the earliest time in ms to fetch trades for * @param {int} [limit] the maximum number of trade structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {boolean} [params.unifiedMargin] use unified margin account * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure} */ async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); await this.authenticate(); const market = this.marketOrNull(symbol); symbol = this.safeString(market, 'symbol', symbol); let messageHash = 'myTrades'; if (symbol !== undefined) { messageHash = messageHash + ':' + symbol; } const url = this.getUserStreamUrl(); const trades = await this.watch(url, messageHash, params, messageHash); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } handleMyTrade(client, message) { // // { // "e": "ticketInfo", // "E": "1758314657847", // "s": "DOGEUSDT", // "q": "22.0", // "t": "1758314657842", // "p": "0.26667", // "T": "4864732022877055421", // "o": "2043285877770284800", // "c": "1758314657002", // "a": "1783404067076253952", // "m": false, // "S": "BUY" // } // let myTrades = this.myTrades; if (myTrades === undefined) { const limit = this.safeInteger(this.options, 'tradesLimit', 1000); myTrades = new Cache.ArrayCacheBySymbolById(limit); } const trade = this.parseMyTrade(message); myTrades.append(trade); let messageHash = 'myTrades:' + trade['symbol']; client.resolve(myTrades, messageHash); messageHash = 'myTrades'; client.resolve(myTrades, messageHash); } parseMyTrade(trade, market = undefined) { const marketId = this.safeString(trade, 's'); const ts = this.safeString(trade, 't'); return this.safeTrade({ 'info': trade, 'id': this.safeString(trade, 'T'), 'timestamp': ts, 'datetime': this.iso8601(ts), 'symbol': this.safeSymbol(marketId, market), 'order': this.safeString(trade, 'o'), 'type': undefined, 'side': this.safeStringLower(trade, 'S'), 'takerOrMaker': this.safeBool(trade, 'm') ? 'maker' : 'taker', 'price': this.safeString(trade, 'p'), 'amount': this.safeString(trade, 'q'), 'cost': undefined, 'fee': undefined, }, market); } /** * @method * @name toobit#watchPositions * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#event-position-update * @description watch all open positions * @param {string[]} [symbols] list of unified market symbols * @param {int} [since] the earliest time in ms to fetch positions for * @param {int} [limit] the maximum number of positions to retrieve * @param {object} params extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure} */ async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); await this.authenticate(); let messageHash = ''; if (!this.isEmpty(symbols)) { symbols = this.marketSymbols(symbols); messageHash = '::' + symbols.join(','); } const url = this.getUserStreamUrl(); const client = this.client(url); await this.authenticate(url); this.setPositionsCache(client, symbols); const cache = this.positions; if (cache === undefined) { const snapshot = await client.future('fetchPositionsSnapshot'); return this.filterBySymbolsSinceLimit(snapshot, symbols, since, limit, true); } const newPositions = await this.watch(url, messageHash, undefined, messageHash); if (this.newUpdates) { return newPositions; } return this.filterBySymbolsSinceLimit(cache, symbols, since, limit, true); } setPositionsCache(client, type, symbols = undefined, isPortfolioMargin = false) { if (this.positions === undefined) { this.positions = {}; } if (type in this.positions) { return; } const fetchPositionsSnapshot = this.handleOption('watchPositions', 'fetchPositionsSnapshot', false); if (fetchPositionsSnapshot) { const messageHash = type + ':fetchPositionsSnapshot'; if (!(messageHash in client.futures)) { client.future(messageHash); this.spawn(this.loadPositionsSnapshot, client, messageHash, type, isPortfolioMargin); } } else { this.positions[type] = new Cache.ArrayCacheBySymbolBySide(); } } async loadPositionsSnapshot(client, messageHash, type) { const params = { 'type': type, }; const positions = await this.fetchPositions(undefined, params); this.positions[type] = new Cache.ArrayCacheBySymbolBySide(); const cache = this.positions[type]; for (let i = 0; i < positions.length; i++) { const position = positions[i]; cache.append(position); } // don't remove the future from the .futures cache const future = client.futures[messageHash]; future.resolve(cache); client.resolve(cache, type + ':positions'); } handlePositions(client, message) { // // [ // { // e: 'outboundContractPositionInfo', // E: '1758316454554', // A: '1783404067076253954', // s: 'DOGE-SWAP-USDT', // S: 'LONG', // p: '0', // P: '0', // a: '0', // f: '0.1228', // m: '0', // r: '0', // up: '0', // pr: '0', // pv: '0', // v: '3.0', // mt: 'CROSS', // mm: '0', // mp: '0.265410000000000000' // } // ] // const subscriptions = Object.keys(client.subscriptions); const accountType = subscriptions[0]; if (this.positions === undefined) { this.positions = {}; } if (!(accountType in this.positions)) { this.positions[accountType] = new Cache.ArrayCacheBySymbolBySide(); } const cache = this.positions[accountType]; const newPositions = []; for (let i = 0; i < message.length; i++) { const rawPosition = message[i]; const position = this.parseWsPosition(rawPosition); const timestamp = this.safeInteger(rawPosition, 'E'); position['timestamp'] = timestamp; position['datetime'] = this.iso8601(timestamp); newPositions.push(position); cache.append(position); } const messageHashes = this.findMessageHashes(client, accountType + ':positions::'); for (let i = 0; i < messageHashes.length; i++) { const messageHash = messageHashes[i]; const parts = messageHash.split('::'); const symbolsString = parts[1]; const symbols = symbolsString.split(','); const positions = this.filterByArray(newPositions, 'symbol', symbols, false); if (!this.isEmpty(positions)) { client.resolve(positions, messageHash); } } client.resolve(newPositions, accountType + ':positions'); } parseWsPosition(position, market = undefined) { const marketId = this.safeString(position, 's'); return this.safePosition({ 'info': position, 'id': undefined, 'symbol': this.safeSymbol(marketId, undefined), 'notional': this.omitZero(this.safeString(position, 'pv')), 'marginMode': this.safeStringLower(position, 'mt'), 'liquidationPrice': this.safeString(position, 'f'), 'entryPrice': this.safeString(position, 'p'), 'unrealizedPnl': this.safeString(position, 'up'), 'realizedPnl': this.safeNumber(position, 'r'), 'percentage': undefined, 'contracts': undefined, 'contractSize': undefined, 'markPrice': this.safeString(position, 'mp'), 'side': this.safeStringLower(position, 'S'), 'hedged': undefined, 'timestamp': undefined, 'datetime': undefined, 'maintenanceMargin': this.safeString(position, 'mm'), 'maintenanceMarginPercentage': undefined, 'collateral': undefined, 'initialMargin': this.omitZero(this.safeString(position, 'm')), 'initialMarginPercentage': undefined, 'leverage': this.safeString(position, 'v'), 'marginRatio': undefined, }); } async authenticate(params = {}) { const client = this.client(this.getUserStreamUrl()); const messageHash = 'authenticated'; const future = client.reusableFuture(messageHash); const authenticated = this.safeValue(client.subscriptions, messageHash); if (authenticated === undefined) { this.checkRequiredCredentials(); const time = this.milliseconds(); const lastAuthenticatedTime = this.safeInteger(this.options['ws'], 'lastAuthenticatedTime', 0); const listenKeyRefreshRate = this.safeInteger(this.options['ws'], 'listenKeyRefreshRate', 1200000); const delay = this.sum(listenKeyRefreshRate, 10000); if (time - lastAuthenticatedTime > delay) { try { client.subscriptions[messageHash] = true; const response = await this.privatePostApiV1UserDataStream(params); this.options['ws']['listenKey'] = this.safeString(response, 'listenKey'); this.options['ws']['lastAuthenticatedTime'] = time; future.resolve(true); this.delay(listenKeyRefreshRate, this.keepAliveListenKey, params); } catch (e) { const err = new errors.AuthenticationError(this.id + ' ' + this.json(e)); client.reject(err, messageHash); if (messageHash in client.subscriptions) { delete client.subscriptions[messageHash]; } } } } return await future; } async keepAliveListenKey(params = {}) { const options = this.safeValue(this.options, 'ws', {}); const listenKey = this.safeString(options, 'listenKey'); if (listenKey === undefined) { // A network error happened: we can't renew a listen key that does not exist. return; } try { const response = await this.privatePostApiV1UserDataStream(params); this.options['ws']['listenKey'] = this.safeString(response, 'listenKey'); this.options['ws']['lastAuthenticatedTime'] = this.milliseconds(); } catch (error) { const url = this.getUserStreamUrl(); const client = this.client(url); const messageHashes = Object.keys(client.futures); for (let i = 0; i < messageHashes.length; i++) { const messageHash = messageHashes[i]; client.reject(error, messageHash); } this.options['ws']['listenKey'] = undefined; this.options['ws']['lastAuthenticatedTime'] = 0; return; } // whether or not to schedule another listenKey keepAlive request const listenKeyRefreshRate = this.safeInteger(this.options, 'listenKeyRefreshRate', 1200000); this.delay(listenKeyRefreshRate, this.keepAliveListenKey, params); } getUserStreamUrl() { return this.urls['api']['ws']['common'] + '/api/v1/ws/' + this.options['ws']['listenKey']; } handleErrorMessage(client, message) { // // { // "code": '-100010', // "desc": "Invalid Symbols!" // } // const code = this.safeString(message, 'code'); if (code !== undefined) { const desc = this.safeString(message, 'desc'); const msg = this.id + ' code: ' + code + ' message: ' + desc; const exception = new errors.ExchangeError(msg); // c# fix client.reject(exception); return true; } return false; } } exports["default"] = toobit;