UNPKG

ccxt

Version:

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

1,091 lines • 146 kB
// --------------------------------------------------------------------------- import kucoinRest from '../kucoin.js'; import { ArgumentsRequired, ExchangeError } from '../base/errors.js'; import { Precise } from '../base/Precise.js'; import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js'; // --------------------------------------------------------------------------- export default class kucoin extends kucoinRest { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'createOrderWs': false, 'editOrderWs': false, 'fetchOpenOrdersWs': false, 'fetchOrderWs': false, 'cancelOrderWs': false, 'cancelOrdersWs': false, 'cancelAllOrdersWs': false, 'watchBidsAsks': true, 'watchOrderBook': true, 'watchOrders': true, 'watchPosition': true, 'watchPositions': false, 'watchMyTrades': true, 'watchTickers': true, 'watchTicker': true, 'watchTrades': true, 'watchTradesForSymbols': true, 'watchOrderBookForSymbols': true, 'watchBalance': true, 'watchOHLCV': true, 'unWatchTicker': true, 'unWatchOHLCV': true, 'unWatchOrderBook': true, 'unWatchTrades': true, 'unWatchhTradesForSymbols': true, }, 'urls': { // only for pro (uta) accounts 'api': { 'ws': { 'spot': 'wss://x-push-spot.kucoin.com', 'futures': 'wss://x-push-futures.kucoin.com', 'private': 'wss://wsapi-push.kucoin.com', }, }, }, 'options': { 'utaToken': undefined, 'utaTokenLastUpdate': 0, 'utaTokenRefreshInterval': 1000 * 60 * 60 * 24, 'tradesLimit': 1000, 'watchTicker': { 'spotMethod': '/market/snapshot', // '/market/ticker' }, 'watchOrderBook': { 'snapshotDelay': 5, 'snapshotMaxRetries': 3, 'utaDepth': 'increment', 'spotMethod': '/market/level2', 'contractMethod': '/contractMarket/level2', // '/contractMarket/level2Depth5' or '/contractMarket/level2Depth20' }, 'watchMyTrades': { 'spotMethod': '/spotMarket/tradeOrders', // or '/spot/tradeFills' }, 'watchBalance': { 'fetchBalanceSnapshot': true, 'awaitBalanceSnapshot': true, // whether to wait for the balance snapshot before providing updates }, 'watchPosition': { 'fetchPositionSnapshot': true, 'awaitPositionSnapshot': true, // whether to wait for the position snapshot before providing updates }, 'watchPositions': { 'fetchPositionsSnapshot': true, 'awaitPositionsSnapshot': true, // whether to wait for the positions snapshot before providing updates }, }, 'streaming': { // kucoin does not support built-in ws protocol-level ping-pong // instead it requires a custom json-based text ping-pong // https://docs.kucoin.com/#ping 'ping': this.ping, }, }); } async negotiate(privateChannel, isFuturesMethod = false, params = {}) { let connectId = privateChannel ? 'private' : 'public'; if (isFuturesMethod) { connectId += 'Futures'; } const urls = this.safeDict(this.options, 'urls', {}); let future = this.safeValue(urls, connectId); if (future !== undefined) { return await future; } // we store an awaitable to the url // so that multiple calls don't asynchronously // fetch different urls and overwrite each other urls[connectId] = this.spawn(this.negotiateHelper, privateChannel, connectId, params); this.options['urls'] = urls; future = urls[connectId]; return await future; } async negotiateHelper(privateChannel, connectId, params = {}) { let response = undefined; try { if (connectId === 'private') { response = await this.privatePostBulletPrivate(params); // // { // "code": "200000", // "data": { // "instanceServers": [ // { // "pingInterval": 50000, // "endpoint": "wss://push-private.kucoin.com/endpoint", // "protocol": "websocket", // "encrypt": true, // "pingTimeout": 10000 // } // ], // "token": "2neAiuYvAU61ZDXANAGAsiL4-iAExhsBXZxftpOeh_55i3Ysy2q2LEsEWU64mdzUOPusi34M_wGoSf7iNyEWJ1UQy47YbpY4zVdzilNP-Bj3iXzrjjGlWtiYB9J6i9GjsxUuhPw3BlrzazF6ghq4Lzf7scStOz3KkxjwpsOBCH4=.WNQmhZQeUKIkh97KYgU0Lg==" // } // } // } else if (connectId === 'public') { response = await this.publicPostBulletPublic(params); } else if (connectId === 'privateFutures') { response = await this.futuresPrivatePostBulletPrivate(params); } else { response = await this.futuresPublicPostBulletPublic(params); } const data = this.safeDict(response, 'data', {}); const instanceServers = this.safeList(data, 'instanceServers', []); const firstInstanceServer = this.safeDict(instanceServers, 0); const pingInterval = this.safeInteger(firstInstanceServer, 'pingInterval'); const endpoint = this.safeString(firstInstanceServer, 'endpoint'); const token = this.safeString(data, 'token'); const result = endpoint + '?' + this.urlencode({ 'token': token, 'privateChannel': privateChannel, 'connectId': connectId, }); const client = this.client(result); client.keepAlive = pingInterval; return result; } catch (e) { const future = this.safeValue(this.options['urls'], connectId); future.reject(e); delete this.options['urls'][connectId]; } return undefined; } requestId() { this.lockId(); const requestId = this.sum(this.safeInteger(this.options, 'requestId', 0), 1); this.options['requestId'] = requestId; this.unlockId(); return requestId; } async subscribe(url, messageHash, subscriptionHash, params = {}, subscription = undefined) { const requestId = this.requestId().toString(); const request = { 'id': requestId, 'type': 'subscribe', 'topic': subscriptionHash, 'response': true, }; const message = this.extend(request, params); const client = this.client(url); if (!(subscriptionHash in client.subscriptions)) { client.subscriptions[requestId] = subscriptionHash; } return await this.watch(url, messageHash, message, subscriptionHash, subscription); } async subscribePublicUta(messageHash, channel, symbol, params = {}, subscription = undefined) { const requestId = this.requestId().toString(); const market = this.market(symbol); const urlType = market['contract'] ? 'futures' : 'spot'; const tradeType = urlType.toUpperCase(); let action = 'subscribe'; if (subscription !== undefined) { const unsubscribe = this.safeBool(subscription, 'unsubscribe', false); action = unsubscribe ? 'unsubscribe' : action; } const request = { 'id': requestId, 'action': action, 'channel': channel, 'tradeType': tradeType, 'symbol': market['id'], }; const message = this.extend(request, params); const url = this.safeString(this.urls['api']['ws'], urlType); const client = this.client(url); if (!(messageHash in client.subscriptions)) { client.subscriptions[requestId] = messageHash; } return await this.watch(url, messageHash, message, messageHash, subscription); } async subscribePrivateUta(messageHashes, subscribeHash, channel, symbol = undefined, params = {}, subscription = undefined) { this.checkRequiredCredentials(); const requestId = this.requestId().toString(); let action = 'subscribe'; if (subscription !== undefined) { const unsubscribe = this.safeBool(subscription, 'unsubscribe', false); action = unsubscribe ? 'unsubscribe' : action; } const request = { 'id': requestId, 'action': action, 'channel': channel, }; if (symbol !== undefined) { const market = this.market(symbol); request['symbol'] = market['id']; } const message = this.extend(request, params); const url = await this.getUtaUrl(); const client = this.client(url); if (!(subscribeHash in client.subscriptions)) { client.subscriptions[requestId] = subscribeHash; } return await this.watchMultiple(url, messageHashes, message, [subscribeHash], subscription); } async getUtaUrl() { const utaToken = await this.authenticateUta(); return this.urls['api']['ws']['private'] + '?token=' + utaToken; } async authenticateUta() { this.checkRequiredCredentials(); const utaToken = this.safeValue(this.options, 'utaToken'); const lastUpdate = this.safeInteger(this.options, 'utaTokenLastUpdate', 0); let refreshInterval = 1000 * 60 * 60 * 24; // 24 hours refreshInterval = this.safeInteger(this.options, 'utaTokenRefreshInterval', refreshInterval); const now = this.milliseconds(); const expired = (now - lastUpdate) >= refreshInterval; const messageHash = 'utaToken'; const url = this.urls['api']['ws']['private']; const client = this.client(url); if ((utaToken === undefined) || expired) { if (messageHash in client.futures) { // wait the existing future if it's already being fetched by another call await client.future(messageHash); } else { // fetch new token and store the future to the .futures to prevent concurrent fetches client.future(messageHash); try { const response = await this.privatePostBulletPrivate({ 'version': 'v2' }); const data = this.safeDict(response, 'data', {}); const utaTokenString = this.safeString(data, 'token'); this.options['utaTokenLastUpdate'] = now; this.options['utaToken'] = utaTokenString; client.resolve(utaTokenString, messageHash); } catch (e) { this.options['utaToken'] = undefined; client.reject(e, messageHash); } } } return this.safeString(this.options, 'utaToken'); } async unSubscribe(url, messageHash, topic, subscriptionHash, params = {}, subscription = undefined) { return await this.unSubscribeMultiple(url, [messageHash], topic, [subscriptionHash], params, subscription); } async subscribeMultiple(url, messageHashes, topic, subscriptionHashes, params = {}, subscription = undefined) { const requestId = this.requestId().toString(); const request = { 'id': requestId, 'type': 'subscribe', 'topic': topic, 'response': true, }; const message = this.extend(request, params); const client = this.client(url); for (let i = 0; i < subscriptionHashes.length; i++) { const subscriptionHash = subscriptionHashes[i]; if (!(subscriptionHash in client.subscriptions)) { client.subscriptions[requestId] = subscriptionHash; } } return await this.watchMultiple(url, messageHashes, message, subscriptionHashes, subscription); } async unSubscribeMultiple(url, messageHashes, topic, subscriptionHashes, params = {}, subscription = undefined) { const requestId = this.requestId().toString(); const request = { 'id': requestId, 'type': 'unsubscribe', 'topic': topic, 'response': true, }; const message = this.extend(request, params); if (subscription !== undefined) { subscription[requestId] = requestId; } const client = this.client(url); for (let i = 0; i < subscriptionHashes.length; i++) { const subscriptionHash = subscriptionHashes[i]; if (!(subscriptionHash in client.subscriptions)) { client.subscriptions[requestId] = subscriptionHash; } } return await this.watchMultiple(url, messageHashes, message, subscriptionHashes, subscription); } /** * @method * @name kucoin#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://www.kucoin.com/docs-new/3470063w0 * @see https://www.kucoin.com/docs-new/3470081w0 * @see https://www.kucoin.com/docs-new/3470222w0 * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {boolean} [params.uta] set to true for the unified trading account (uta), default is false * @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']; let messageHash = 'ticker:' + symbol; let uta = false; [uta, params] = this.handleOptionAndParams(params, 'watchTicker', 'uta', uta); if (uta) { messageHash = 'uta:' + messageHash; const channel = 'ticker'; return await this.subscribePublicUta(messageHash, channel, symbol, params); } const isFuturesMethod = market['contract']; const url = await this.negotiate(false, isFuturesMethod); let method = '/market/snapshot'; if (isFuturesMethod) { method = '/contractMarket/ticker'; } else { [method, params] = this.handleOptionAndParams(params, 'watchTicker', 'spotMethod', method); } const topic = method + ':' + market['id']; return await this.subscribe(url, messageHash, topic, params); } /** * @method * @name kucoin#unWatchTicker * @description unWatches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://www.kucoin.com/docs-new/3470063w0 * @see https://www.kucoin.com/docs-new/3470081w0 * @see https://www.kucoin.com/docs-new/3470222w0 * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {boolean} [params.uta] set to true for the unified trading account (uta), default is false * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/?id=ticker-structure} */ async unWatchTicker(symbol, params = {}) { await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const isFuturesMethod = market['contract']; let uta = false; [uta, params] = this.handleOptionAndParams(params, 'unWatchTicker', 'uta', uta); const subscription = { 'symbols': [symbol], 'topic': 'ticker', 'unsubscribe': true, }; let subMessageHash = 'ticker:' + symbol; if (uta) { subMessageHash = 'uta:' + subMessageHash; subscription['subMessageHashes'] = [subMessageHash]; const utaMessageHash = 'unsubscribe:' + subMessageHash; subscription['messageHashes'] = [utaMessageHash]; return await this.subscribePublicUta(utaMessageHash, 'ticker', symbol, params, subscription); } else { const url = await this.negotiate(false, isFuturesMethod); let method = '/market/snapshot'; if (isFuturesMethod) { method = '/contractMarket/ticker'; } else { [method, params] = this.handleOptionAndParams(params, 'watchTicker', 'spotMethod', method); } const topic = method + ':' + market['id']; const messageHash = 'unsubscribe:' + subMessageHash; // we have to add the topic to the messageHashes and subMessageHashes // because handleSubscriptionStatus needs them to remove the subscription from the client // without them subscription would never be removed and re-subscribe would fail because of duplicate subscriptionHash subscription['messageHashes'] = [messageHash, topic]; subscription['subMessageHashes'] = [subMessageHash, topic]; return await this.unSubscribe(url, messageHash, topic, subMessageHash, params, subscription); } } /** * @method * @name kucoin#watchTickers * @see https://www.kucoin.com/docs-new/3470063w0 * @see https://www.kucoin.com/docs-new/3470064w0 * @see https://www.kucoin.com/docs-new/3470081w0 * @see https://www.kucoin.com/docs-new/3470222w0 * @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 * @param {string} [params.method] *spot markets only* either '/market/snapshot' or '/market/ticker' default is '/market/ticker' * @param {boolean} [params.uta] set to true for the unified trading account (uta), default is false * @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, true, true); const firstMarket = this.getMarketFromSymbols(symbols); let marketType = undefined; [marketType, params] = this.handleMarketTypeAndParams('watchTickers', firstMarket, params); let uta = false; [uta, params] = this.handleOptionAndParams(params, 'watchTickers', 'uta', uta); const isFuturesMethod = (marketType !== 'spot') && (marketType !== 'margin'); if ((isFuturesMethod || uta) && symbols === undefined) { throw new ArgumentsRequired(this.id + ' watchTickers() requires a list of symbols for ' + marketType + ' markets and unified trading account (uta)'); } const messageHash = 'tickers'; let method = '/market/ticker'; if (isFuturesMethod) { method = '/contractMarket/ticker'; } else { [method, params] = this.handleOptionAndParams2(params, 'watchTickers', 'method', 'spotMethod', method); } const messageHashes = []; const topics = []; if (symbols !== undefined) { for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; messageHashes.push('ticker:' + symbol); const market = this.market(symbol); topics.push(method + ':' + market['id']); } } const url = await this.negotiate(false, isFuturesMethod); let tickers = undefined; if (symbols === undefined) { const allTopic = method + ':all'; tickers = await this.subscribe(url, messageHash, allTopic, params); if (this.newUpdates) { return tickers; } } else { const marketIds = this.marketIds(symbols); const symbolsTopic = method + ':' + marketIds.join(','); tickers = await this.subscribeMultiple(url, messageHashes, symbolsTopic, topics, params); if (this.newUpdates) { const newDict = {}; newDict[tickers['symbol']] = tickers; return newDict; } } return this.filterByArray(this.tickers, 'symbol', symbols); } async subscribePublicMultipleUta(messageHashes, channel, symbols, params = {}, subscription = undefined) { const requestId = this.requestId().toString(); const market = this.getMarketFromSymbols(symbols); const urlType = market['contract'] ? 'futures' : 'spot'; const tradeType = urlType.toUpperCase(); let action = 'subscribe'; if (subscription !== undefined) { const unsubscribe = this.safeBool(subscription, 'unsubscribe', false); action = unsubscribe ? 'unsubscribe' : action; } const request = { 'id': requestId, 'action': action, 'channel': channel, 'tradeType': tradeType, 'symbols': this.marketIds(symbols), }; const message = this.extend(request, params); const url = this.safeString(this.urls['api']['ws'], urlType); const client = this.client(url); const messageHashWithSymbols = channel + ':' + symbols.join(','); if (!(messageHashWithSymbols in client.subscriptions)) { client.subscriptions[requestId] = messageHashWithSymbols; } return await this.watchMultiple(url, messageHashes, message, messageHashes, subscription); } async watchUtaTickers(symbols = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false, true); const messageHash = 'uta:ticker'; const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const symbol = this.safeString(symbols, i); const market = this.market(symbol); const subMessageHash = messageHash + ':' + market['symbol']; messageHashes.push(subMessageHash); } const tickers = await this.subscribePublicMultipleUta(messageHashes, 'ticker', symbols, params); if (this.newUpdates) { return tickers; } return this.filterByArray(this.tickers, 'symbol', symbols); } handleTicker(client, message) { // // market/snapshot // // updates come in every 2 sec unless there // were no changes since the previous update // // { // "data": { // "sequence": "1545896669291", // "data": { // "trading": true, // "symbol": "KCS-BTC", // "buy": 0.00011, // "sell": 0.00012, // "sort": 100, // "volValue": 3.13851792584, // total // "baseCurrency": "KCS", // "market": "BTC", // "quoteCurrency": "BTC", // "symbolCode": "KCS-BTC", // "datetime": 1548388122031, // "high": 0.00013, // "vol": 27514.34842, // "low": 0.0001, // "changePrice": -1.0e-5, // "changeRate": -0.0769, // "lastTradedPrice": 0.00012, // "board": 0, // "mark": 0 // } // }, // "subject": "trade.snapshot", // "topic": "/market/snapshot:KCS-BTC", // "type": "message" // } // // market/ticker // // { // "type": "message", // "topic": "/market/ticker:BTC-USDT", // "subject": "trade.ticker", // "data": { // "bestAsk": "62163", // "bestAskSize": "0.99011388", // "bestBid": "62162.9", // "bestBidSize": "0.04794181", // "price": "62162.9", // "sequence": "1621383371852", // "size": "0.00832274", // "time": 1634641987564 // } // } // // futures // { // "subject": "ticker", // "topic": "/contractMarket/ticker:XBTUSDM", // "data": { // "symbol": "XBTUSDM", //Market of the symbol // "sequence": 45, //Sequence number which is used to judge the continuity of the pushed messages // "side": "sell", //Transaction side of the last traded taker order // "price": "3600.0", //Filled price // "size": 16, //Filled quantity // "tradeId": "5c9dcf4170744d6f5a3d32fb", //Order ID // "bestBidSize": 795, //Best bid size // "bestBidPrice": "3200.0", //Best bid // "bestAskPrice": "3600.0", //Best ask size // "bestAskSize": 284, //Best ask // "ts": 1553846081210004941 //Filled time - nanosecond // } // } // const topic = this.safeString(message, 'topic'); if (topic.indexOf('contractMarket') < 0) { let market = undefined; if (topic !== undefined) { const parts = topic.split(':'); const first = this.safeString(parts, 1); let marketId = undefined; if (first === 'all') { marketId = this.safeString(message, 'subject'); } else { marketId = first; } market = this.safeMarket(marketId, market, '-'); } const data = this.safeDict(message, 'data', {}); const rawTicker = this.safeDict(data, 'data', data); const ticker = this.parseSpotOrUtaTicker(rawTicker, market); const symbol = ticker['symbol']; this.tickers[symbol] = ticker; const messageHash = 'ticker:' + symbol; client.resolve(ticker, messageHash); // watchTickers const allTickers = {}; allTickers[symbol] = ticker; client.resolve(allTickers, 'tickers'); } else { this.handleContractTicker(client, message); } } handleContractTicker(client, message) { // // ticker (v1) // // { // "subject": "ticker", // "topic": "/contractMarket/ticker:XBTUSDM", // "data": { // "symbol": "XBTUSDM", //Market of the symbol // "sequence": 45, //Sequence number which is used to judge the continuity of the pushed messages // "side": "sell", //Transaction side of the last traded taker order // "price": "3600.0", //Filled price // "size": 16, //Filled quantity // "tradeId": "5c9dcf4170744d6f5a3d32fb", //Order ID // "bestBidSize": 795, //Best bid size // "bestBidPrice": "3200.0", //Best bid // "bestAskPrice": "3600.0", //Best ask size // "bestAskSize": 284, //Best ask // "ts": 1553846081210004941 //Filled time - nanosecond // } // } // const data = this.safeDict(message, 'data', {}); const marketId = this.safeString(data, 'symbol'); const market = this.safeMarket(marketId, undefined, '-'); const ticker = this.parseTicker(data, market); this.tickers[market['symbol']] = ticker; const messageHash = 'ticker:' + market['symbol']; client.resolve(ticker, messageHash); } handleUtaTicker(client, message) { // // { // "T": "ticker.SPOT", // "P": "1774100940787520626", // "d": { // "A": "0.5972689", // "B": "23.3114947", // "E": 20310552932, // "M": "1774100940780000000", // "S": "SELL", // "a": "2155.55", // "b": "2155.54", // "l": "2155.54", // "q": "0.0001529", // "s": "ETH-USDT" // } // } // const data = this.safeDict(message, 'd', {}); const marketId = this.safeString(data, 's'); const market = this.safeMarket(marketId); const ticker = this.parseWsUtaTicker(data, market); this.tickers[market['symbol']] = ticker; const messageHash = 'uta:ticker:' + market['symbol']; client.resolve(ticker, messageHash); } parseWsUtaTicker(ticker, market = undefined) { const symbol = this.safeString(market, 'symbol'); market = this.safeMarket(symbol, market); const timestamp = this.safeIntegerProduct(ticker, 'M', 0.000001); return this.safeTicker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'high': undefined, 'low': undefined, 'bid': this.safeString(ticker, 'a'), 'bidVolume': this.safeString(ticker, 'A'), 'ask': this.safeString(ticker, 'b'), 'askVolume': this.safeString(ticker, 'B'), 'vwap': undefined, 'open': undefined, 'close': undefined, 'last': this.safeString(ticker, 'l'), 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': undefined, 'quoteVolume': undefined, 'markPrice': undefined, 'info': ticker, }, market); } /** * @method * @name kucoin#watchBidsAsks * @see https://www.kucoin.com/docs-new/3470067w0 * @see https://www.kucoin.com/docs-new/3470080w0 * @description watches best bid & ask for symbols * @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, true, false); const firstMarket = this.getMarketFromSymbols(symbols); const isFuturesMethod = firstMarket['contract']; let channelName = '/spotMarket/level1:'; if (isFuturesMethod) { channelName = '/contractMarket/tickerV2:'; } const ticker = await this.watchMultiHelper('watchBidsAsks', channelName, isFuturesMethod, symbols, params); if (this.newUpdates) { const tickers = {}; tickers[ticker['symbol']] = ticker; return tickers; } return this.filterByArray(this.bidsasks, 'symbol', symbols); } async watchMultiHelper(methodName, channelName, isFuturesChannel, symbols = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false, true, false); const length = symbols.length; if (length > 100) { throw new ArgumentsRequired(this.id + ' ' + methodName + '() accepts a maximum of 100 symbols'); } const messageHashes = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.market(symbol); messageHashes.push('bidask@' + market['symbol']); } const url = await this.negotiate(false, isFuturesChannel); const marketIds = this.marketIds(symbols); const joined = marketIds.join(','); const requestId = this.requestId().toString(); const request = { 'id': requestId, 'type': 'subscribe', 'topic': channelName + joined, 'response': true, }; const message = this.extend(request, params); return await this.watchMultiple(url, messageHashes, message, messageHashes); } handleBidAsk(client, message) { // // arrives one symbol dict // // { // topic: '/spotMarket/level1:ETH-USDT', // type: 'message', // data: { // asks: [ '3347.42', '2.0778387' ], // bids: [ '3347.41', '6.0411697' ], // timestamp: 1712231142085 // }, // subject: 'level1' // } // // futures // { // "subject": "tickerV2", // "topic": "/contractMarket/tickerV2:XBTUSDM", // "data": { // "symbol": "XBTUSDM", //Market of the symbol // "bestBidSize": 795, // Best bid size // "bestBidPrice": 3200.0, // Best bid // "bestAskPrice": 3600.0, // Best ask // "bestAskSize": 284, // Best ask size // "ts": 1553846081210004941 // Filled time - nanosecond // } // } // const parsedTicker = this.parseWsBidAsk(message); const symbol = parsedTicker['symbol']; this.bidsasks[symbol] = parsedTicker; const messageHash = 'bidask@' + symbol; client.resolve(parsedTicker, messageHash); } parseWsBidAsk(ticker, market = undefined) { const topic = this.safeString(ticker, 'topic'); if (topic.indexOf('contractMarket') < 0) { const parts = topic.split(':'); const marketId = parts[1]; market = this.safeMarket(marketId, market); const symbol = this.safeString(market, 'symbol'); const data = this.safeDict(ticker, 'data', {}); const ask = this.safeList(data, 'asks', []); const bid = this.safeList(data, 'bids', []); const timestamp = this.safeInteger(data, 'timestamp'); return this.safeTicker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'ask': this.safeNumber(ask, 0), 'askVolume': this.safeNumber(ask, 1), 'bid': this.safeNumber(bid, 0), 'bidVolume': this.safeNumber(bid, 1), 'info': ticker, }, market); } else { // futures const data = this.safeDict(ticker, 'data', {}); const marketId = this.safeString(data, 'symbol'); market = this.safeMarket(marketId, market); const symbol = this.safeString(market, 'symbol'); const timestamp = this.safeIntegerProduct(data, 'ts', 0.000001); return this.safeTicker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'ask': this.safeNumber(data, 'bestAskPrice'), 'askVolume': this.safeNumber(data, 'bestAskSize'), 'bid': this.safeNumber(data, 'bestBidPrice'), 'bidVolume': this.safeNumber(data, 'bestBidSize'), 'info': ticker, }, market); } } /** * @method * @name kucoin#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://www.kucoin.com/docs-new/3470071w0 * @see https://www.kucoin.com/docs-new/3470086w0 * @see https://www.kucoin.com/docs-new/3470223w0 * @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 * @param {boolean} [params.uta] set to true for the unified trading account (uta), default is false * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const period = this.safeString(this.timeframes, timeframe, timeframe); let messageHash = 'candles:' + symbol + ':' + timeframe; let uta = false; [uta, params] = this.handleOptionAndParams(params, 'watchOHLCV', 'uta', uta); let ohlcv = undefined; if (uta) { const channel = 'kline'; messageHash = 'uta:' + messageHash; const extendedParams = { 'interval': period, }; params = this.extend(extendedParams, params); ohlcv = await this.subscribePublicUta(messageHash, channel, symbol, this.extend(extendedParams, params)); } else { const isFuturesMethod = market['contract']; const url = await this.negotiate(false, isFuturesMethod); let channelName = '/market/candles:'; if (isFuturesMethod) { channelName = '/contractMarket/limitCandle:'; } const topic = channelName + market['id'] + '_' + period; ohlcv = await this.subscribe(url, messageHash, topic, params); } if (this.newUpdates) { limit = ohlcv.getLimit(symbol, limit); } return this.filterBySinceLimit(ohlcv, since, limit, 0, true); } /** * @method * @name kucoin#unWatchOHLCV * @description unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://www.kucoin.com/docs-new/3470071w0 * @see https://www.kucoin.com/docs-new/3470086w0 * @see https://www.kucoin.com/docs-new/3470223w0 * @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 * @param {boolean} [params.uta] set to true for the unified trading account (uta), default is false * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async unWatchOHLCV(symbol, timeframe = '1m', params = {}) { await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; let uta = false; [uta, params] = this.handleOptionAndParams(params, 'unWatchOHLCV', 'uta', uta); const period = this.safeString(this.timeframes, timeframe, timeframe); const symbolAndTimeframe = [symbol, timeframe]; const subscription = { 'symbols': [symbol], 'symbolsAndTimeframes': [symbolAndTimeframe], 'topic': 'ohlcv', 'unsubscribe': true, }; let subMessageHash = 'candles:' + symbol + ':' + timeframe; if (uta) { subMessageHash = 'uta:' + subMessageHash; subscription['subMessageHashes'] = [subMessageHash]; const utaMessageHash = 'unsubscribe:' + subMessageHash; subscription['messageHashes'] = [utaMessageHash]; const extendedParams = { 'interval': period, }; return await this.subscribePublicUta(utaMessageHash, 'kline', symbol, this.extend(extendedParams, params), subscription); } else { const isFuturesMethod = market['contract']; const url = await this.negotiate(false, isFuturesMethod); let channelName = '/market/candles:'; if (isFuturesMethod) { channelName = '/contractMarket/limitCandle:'; } const messageHash = 'unsubscribe:' + subMessageHash; const topic = channelName + market['id'] + '_' + period; // we have to add the topic to the messageHashes and subMessageHashes // because handleSubscriptionStatus needs them to remove the subscription from the client // without them subscription would never be removed and re-subscribe would fail because of duplicate subscriptionHash subscription['messageHashes'] = [messageHash, topic]; subscription['subMessageHashes'] = [subMessageHash, topic]; return await this.unSubscribe(url, messageHash, topic, messageHash, params, subscription); } } handleOHLCV(client, message) { // // { // "data": { // "symbol": "BTC-USDT", // "candles": [ // "1624881240", // "34138.8", // "34121.6", // "34138.8", // "34097.9", // "3.06097133", // "104430.955068564" // ], // "time": 1624881284466023700 // }, // "subject": "trade.candles.update", // "topic": "/market/candles:BTC-USDT_1min", // "type": "message" // } // // futures // { // "topic":"/contractMarket/limitCandle:LTCUSDTM_1min", // "type":"message", // "data":{ // "symbol":"LTCUSDTM", // "candles":[ // "1715470980", // "81.38", // "81.38", // "81.38", // "81.38", // "61.0", - Note value 5 is incorrect and will be fixed in subsequent versions of kucoin // "61" // ], // "time":1715470994801 // }, // "subject":"candle.stick" // } // const data = this.safeDict(message, 'data', {}); const marketId = this.safeString(data, 'symbol'); const candles = this.safeList(data, 'candles', []); const topic = this.safeString(message, 'topic'); const parts = topic.split('_'); const interval = this.safeString(parts, 1); // use a reverse lookup in a static map instead const timeframe = this.findTimeframe(interval); const market = this.safeMarket(marketId); const symbol = market['symbol']; const messageHash = 'candles:' + symbol + ':' + timeframe; this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {}); let stored = this.safeValue(this.ohlcvs[symbol], timeframe); if (stored === undefined) { const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000); stored = new ArrayCacheByTimestamp(limit); this.ohlcvs[symbol][timeframe] = stored; } const isContractMarket = (topic.indexOf('contractMarket') >= 0); const baseVolumeIndex = isContractMarket ? 6 : 5; // Note value 5 is incorrect and will be fixed in subsequent versions of kucoin const parsed = [ this.safeTimestamp(candles, 0), this.safeNumber(candles, 1), this.safeNumber(candles, 3), this.safeNumber(candles, 4), this.safeNumber(candles, 2), this.safeNumber(candles, baseVolumeIndex), ]; stored.append(parsed); client.resolve(stored, messageHash); } handleUtaOHLCV(client, message) { // // { // "T": "kline.SPOT", // "P": "1774621652314890314", // "d": { // "a": "195333.419819132", // "s": "ETH-USDT", // "C": 1774621680, // "c": "1973.4", // "S": false, // "v": "98.941095", // "h": "1974.97", // "i": "1min", // "l": "1973.4", // "O": 1774621620, // "o": "1974.34" // } // } // const data = this.safeDict(message, 'd', {}); const marketId = this.safeString(data, 's'); const market = this.safeMarket(marketId); const symbol = market['symbol']; const interval = this.safeString(data, 'i'); const timeframe = this.findTimeframe(interval); const messageHash = 'uta:candles:' + symbol + ':' + timeframe; this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {}); let stored = this.safeValue(this.ohlcvs[symbol], timeframe); if (stored === undefined) { const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000); stored = new ArrayCacheByTimestamp(limit); this.ohlcvs[symbol][timeframe] = stored; } const parsed = [ this.safeIntegerProduct(data, 'O', 1000), this.safeNumber(data, 'o'), this.safeNumber(data, 'h'), this.safeNumber(data, 'l'), this.safeNumber(data, 'c'), this.safeNumber(data, 'v'), ]; stored.append(parsed); client.resolve(stored, messageHash); } /** * @method * @name kucoin#watchTrades * @description get the list of most recent trades for a particular symbol * @see https://www.kucoin.com/docs-new/3470072w0 * @see https://www.kucoin.com/docs-new/3470084w0 * @see https://www.kucoin.com/docs-new/3470224w0 * @param {string} symbol 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 {boolean} [params.uta] set to true for the unified trading account (uta), default is false * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=public-trades} */ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { let uta = false; [uta, params] = this.handleOptionAndParams(params, 'watchTrades', 'uta', uta); if (uta) { await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const messageHash = 'uta:trades:' + symbol; const channel = 'trade'; const trades = await this.subscribePublicUta(messageHash, channel, symbol, params); 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); } return await this.watchTradesForSymbols([symbol], since, limit, params); } /** * @method * @name kucoin#watchTradesForSymbols * @description get the list of most recent trades for a particular symbol * @see https://www.kucoin.com/docs-new/3470072w0 * @see https://www.kucoin.com/docs-new/3470084w0 * @param {string[]} symbols * @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 * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/?id=public-trades} */ async watchTradesForSymbols(symbols, since = undefined, limit = un