UNPKG

ccxt

Version:

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

1,050 lines (1,048 loc) • 80.9 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import krakenRest from '../kraken.js'; import { ExchangeError, BadSymbol, PermissionDenied, AccountSuspended, BadRequest, InsufficientFunds, InvalidOrder, OrderNotFound, NotSupported, RateLimitExceeded, ExchangeNotAvailable, ChecksumError, AuthenticationError, ArgumentsRequired } from '../base/errors.js'; import { ArrayCache, ArrayCacheByTimestamp, ArrayCacheBySymbolById } from '../base/ws/Cache.js'; import { Precise } from '../base/Precise.js'; // --------------------------------------------------------------------------- export default class kraken extends krakenRest { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'watchBalance': true, 'watchMyTrades': true, 'watchOHLCV': true, 'watchOrderBook': true, 'watchOrderBookForSymbols': true, 'watchOrders': true, 'watchTicker': true, 'watchTickers': true, 'watchBidsAsks': true, 'watchTrades': true, 'watchTradesForSymbols': true, 'createOrderWs': true, 'editOrderWs': true, 'cancelOrderWs': true, 'cancelOrdersWs': true, 'cancelAllOrdersWs': true, // 'watchHeartbeat': true, // 'watchStatus': true, }, 'urls': { 'api': { 'ws': { 'public': 'wss://ws.kraken.com', 'private': 'wss://ws-auth.kraken.com', 'privateV2': 'wss://ws-auth.kraken.com/v2', 'publicV2': 'wss://ws.kraken.com/v2', 'beta': 'wss://beta-ws.kraken.com', 'beta-private': 'wss://beta-ws-auth.kraken.com', }, }, }, // 'versions': { // 'ws': '0.2.0', // }, 'options': { 'tradesLimit': 1000, 'OHLCVLimit': 1000, 'ordersLimit': 1000, 'symbolsByOrderId': {}, 'watchOrderBook': { 'checksum': false, }, }, 'streaming': { 'ping': this.ping, 'keepAlive': 6000, }, 'exceptions': { 'ws': { 'exact': { 'Event(s) not found': BadRequest, }, 'broad': { 'Already subscribed': BadRequest, 'Currency pair not in ISO 4217-A3 format': BadSymbol, 'Malformed request': BadRequest, 'Pair field must be an array': BadRequest, 'Pair field unsupported for this subscription type': BadRequest, 'Pair(s) not found': BadSymbol, 'Subscription book depth must be an integer': BadRequest, 'Subscription depth not supported': BadRequest, 'Subscription field must be an object': BadRequest, 'Subscription name invalid': BadRequest, 'Subscription object unsupported field': BadRequest, 'Subscription ohlc interval must be an integer': BadRequest, 'Subscription ohlc interval not supported': BadRequest, 'Subscription ohlc requires interval': BadRequest, 'EAccount:Invalid permissions': PermissionDenied, 'EAuth:Account temporary disabled': AccountSuspended, 'EAuth:Account unconfirmed': AuthenticationError, 'EAuth:Rate limit exceeded': RateLimitExceeded, 'EAuth:Too many requests': RateLimitExceeded, 'EDatabase: Internal error (to be deprecated)': ExchangeError, 'EGeneral:Internal error[:<code>]': ExchangeError, 'EGeneral:Invalid arguments': BadRequest, 'EOrder:Cannot open opposing position': InvalidOrder, 'EOrder:Cannot open position': InvalidOrder, 'EOrder:Insufficient funds (insufficient user funds)': InsufficientFunds, 'EOrder:Insufficient margin (exchange does not have sufficient funds to allow margin trading)': InsufficientFunds, 'EOrder:Invalid price': InvalidOrder, 'EOrder:Margin allowance exceeded': InvalidOrder, 'EOrder:Margin level too low': InvalidOrder, 'EOrder:Margin position size exceeded (client would exceed the maximum position size for this pair)': InvalidOrder, 'EOrder:Order minimum not met (volume too low)': InvalidOrder, 'EOrder:Orders limit exceeded': InvalidOrder, 'EOrder:Positions limit exceeded': InvalidOrder, 'EOrder:Rate limit exceeded': RateLimitExceeded, 'EOrder:Scheduled orders limit exceeded': InvalidOrder, 'EOrder:Unknown position': OrderNotFound, 'EOrder:Unknown order': OrderNotFound, 'EOrder:Invalid order': InvalidOrder, 'EService:Deadline elapsed': ExchangeNotAvailable, 'EService:Market in cancel_only mode': NotSupported, 'EService:Market in limit_only mode': NotSupported, 'EService:Market in post_only mode': NotSupported, 'EService:Unavailable': ExchangeNotAvailable, 'ETrade:Invalid request': BadRequest, 'ESession:Invalid session': AuthenticationError, }, }, }, }); } orderRequestWs(method, symbol, type, request, amount, price = undefined, params = {}) { const isLimitOrder = type.endsWith('limit'); // supporting limit, stop-loss-limit, take-profit-limit, etc if (isLimitOrder) { if (price === undefined) { throw new ArgumentsRequired(this.id + ' limit orders require a price argument'); } request['params']['limit_price'] = this.parseToNumeric(this.priceToPrecision(symbol, price)); } const isMarket = (type === 'market'); let postOnly = undefined; [postOnly, params] = this.handlePostOnly(isMarket, false, params); if (postOnly) { request['params']['post_only'] = true; } const clientOrderId = this.safeString(params, 'clientOrderId'); if (clientOrderId !== undefined) { request['params']['cl_ord_id'] = clientOrderId; } const cost = this.safeString(params, 'cost'); if (cost !== undefined) { request['params']['order_qty'] = this.parseToNumeric(this.costToPrecision(symbol, cost)); } const stopLoss = this.safeDict(params, 'stopLoss', {}); const takeProfit = this.safeDict(params, 'takeProfit', {}); const presetStopLoss = this.safeString(stopLoss, 'triggerPrice'); const presetTakeProfit = this.safeString(takeProfit, 'triggerPrice'); const presetStopLossLimit = this.safeString(stopLoss, 'price'); const presetTakeProfitLimit = this.safeString(takeProfit, 'price'); const isPresetStopLoss = presetStopLoss !== undefined; const isPresetTakeProfit = presetTakeProfit !== undefined; const stopLossPrice = this.safeString(params, 'stopLossPrice'); const takeProfitPrice = this.safeString(params, 'takeProfitPrice'); const isStopLossPriceOrder = stopLossPrice !== undefined; const isTakeProfitPriceOrder = takeProfitPrice !== undefined; const trailingAmount = this.safeString(params, 'trailingAmount'); const trailingPercent = this.safeString(params, 'trailingPercent'); const trailingLimitAmount = this.safeString(params, 'trailingLimitAmount'); const trailingLimitPercent = this.safeString(params, 'trailingLimitPercent'); const isTrailingAmountOrder = trailingAmount !== undefined; const isTrailingPercentOrder = trailingPercent !== undefined; const isTrailingLimitAmountOrder = trailingLimitAmount !== undefined; const isTrailingLimitPercentOrder = trailingLimitPercent !== undefined; const offset = this.safeString(params, 'offset', ''); // can set this to - for minus const trailingAmountString = (trailingAmount !== undefined) ? offset + this.numberToString(trailingAmount) : undefined; const trailingPercentString = (trailingPercent !== undefined) ? offset + this.numberToString(trailingPercent) : undefined; const trailingLimitAmountString = (trailingLimitAmount !== undefined) ? offset + this.numberToString(trailingLimitAmount) : undefined; const trailingLimitPercentString = (trailingLimitPercent !== undefined) ? offset + this.numberToString(trailingLimitPercent) : undefined; const priceType = (isTrailingPercentOrder || isTrailingLimitPercentOrder) ? 'pct' : 'quote'; if (method === 'createOrderWs') { const reduceOnly = this.safeBool(params, 'reduceOnly'); if (reduceOnly) { request['params']['reduce_only'] = true; } const timeInForce = this.safeStringLower(params, 'timeInForce'); if (timeInForce !== undefined) { request['params']['time_in_force'] = timeInForce; } params = this.omit(params, ['reduceOnly', 'timeInForce']); if (isStopLossPriceOrder || isTakeProfitPriceOrder || isTrailingAmountOrder || isTrailingPercentOrder || isTrailingLimitAmountOrder || isTrailingLimitPercentOrder) { request['params']['triggers'] = {}; } if (isPresetStopLoss || isPresetTakeProfit) { request['params']['conditional'] = {}; if (isPresetStopLoss) { request['params']['conditional']['order_type'] = 'stop-loss'; request['params']['conditional']['trigger_price'] = this.parseToNumeric(this.priceToPrecision(symbol, presetStopLoss)); } else if (isPresetTakeProfit) { request['params']['conditional']['order_type'] = 'take-profit'; request['params']['conditional']['trigger_price'] = this.parseToNumeric(this.priceToPrecision(symbol, presetTakeProfit)); } if (presetStopLossLimit !== undefined) { request['params']['conditional']['order_type'] = 'stop-loss-limit'; request['params']['conditional']['limit_price'] = this.parseToNumeric(this.priceToPrecision(symbol, presetStopLossLimit)); } else if (presetTakeProfitLimit !== undefined) { request['params']['conditional']['order_type'] = 'take-profit-limit'; request['params']['conditional']['limit_price'] = this.parseToNumeric(this.priceToPrecision(symbol, presetTakeProfitLimit)); } params = this.omit(params, ['stopLoss', 'takeProfit']); } else if (isStopLossPriceOrder || isTakeProfitPriceOrder) { if (isStopLossPriceOrder) { request['params']['triggers']['price'] = this.parseToNumeric(this.priceToPrecision(symbol, stopLossPrice)); if (isLimitOrder) { request['params']['order_type'] = 'stop-loss-limit'; } else { request['params']['order_type'] = 'stop-loss'; } } else { request['params']['triggers']['price'] = this.parseToNumeric(this.priceToPrecision(symbol, takeProfitPrice)); if (isLimitOrder) { request['params']['order_type'] = 'take-profit-limit'; } else { request['params']['order_type'] = 'take-profit'; } } } else if (isTrailingAmountOrder || isTrailingPercentOrder || isTrailingLimitAmountOrder || isTrailingLimitPercentOrder) { request['params']['triggers']['price_type'] = priceType; if (!isLimitOrder && (isTrailingAmountOrder || isTrailingPercentOrder)) { request['params']['order_type'] = 'trailing-stop'; if (isTrailingAmountOrder) { request['params']['triggers']['price'] = this.parseToNumeric(trailingAmountString); } else { request['params']['triggers']['price'] = this.parseToNumeric(trailingPercentString); } } else { // trailing limit orders are not conventionally supported because the static limit_price_type param is not available for trailing-stop-limit orders request['params']['limit_price_type'] = priceType; request['params']['order_type'] = 'trailing-stop-limit'; if (isTrailingLimitAmountOrder) { request['params']['triggers']['price'] = this.parseToNumeric(trailingLimitAmountString); } else { request['params']['triggers']['price'] = this.parseToNumeric(trailingLimitPercentString); } } } } else if (method === 'editOrderWs') { if (isPresetStopLoss || isPresetTakeProfit) { throw new NotSupported(this.id + ' editing the stopLoss and takeProfit on existing orders is currently not supported'); } if (isStopLossPriceOrder || isTakeProfitPriceOrder) { if (isStopLossPriceOrder) { request['params']['trigger_price'] = this.parseToNumeric(this.priceToPrecision(symbol, stopLossPrice)); } else { request['params']['trigger_price'] = this.parseToNumeric(this.priceToPrecision(symbol, takeProfitPrice)); } } else if (isTrailingAmountOrder || isTrailingPercentOrder || isTrailingLimitAmountOrder || isTrailingLimitPercentOrder) { request['params']['trigger_price_type'] = priceType; if (!isLimitOrder && (isTrailingAmountOrder || isTrailingPercentOrder)) { if (isTrailingAmountOrder) { request['params']['trigger_price'] = this.parseToNumeric(trailingAmountString); } else { request['params']['trigger_price'] = this.parseToNumeric(trailingPercentString); } } else { request['params']['limit_price_type'] = priceType; if (isTrailingLimitAmountOrder) { request['params']['trigger_price'] = this.parseToNumeric(trailingLimitAmountString); } else { request['params']['trigger_price'] = this.parseToNumeric(trailingLimitPercentString); } } } } params = this.omit(params, ['clientOrderId', 'cost', 'offset', 'stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingLimitAmount', 'trailingLimitPercent']); return [request, params]; } /** * @method * @name kraken#createOrderWs * @description create a trade order * @see https://docs.kraken.com/api/docs/websocket-v2/add_order * @param {string} symbol unified symbol of the market to create an order in * @param {string} type 'market' or 'limit' * @param {string} side 'buy' or 'sell' * @param {float} amount how much of currency you want to trade in units of base currency * @param {float} [price] the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ async createOrderWs(symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets(); const token = await this.authenticate(); const market = this.market(symbol); const url = this.urls['api']['ws']['privateV2']; const requestId = this.requestId(); const messageHash = requestId; let request = { 'method': 'add_order', 'params': { 'order_type': type, 'side': side, 'order_qty': this.parseToNumeric(this.amountToPrecision(symbol, amount)), 'symbol': market['symbol'], 'token': token, }, 'req_id': requestId, }; [request, params] = this.orderRequestWs('createOrderWs', symbol, type, request, amount, price, params); return await this.watch(url, messageHash, this.extend(request, params), messageHash); } handleCreateEditOrder(client, message) { // // createOrder // { // "method": "add_order", // "req_id": 1, // "result": { // "order_id": "OXM2QD-EALR2-YBAVEU" // }, // "success": true, // "time_in": "2025-05-13T10:12:13.876173Z", // "time_out": "2025-05-13T10:12:13.890137Z" // } // // editOrder // { // "method": "amend_order", // "req_id": 1, // "result": { // "amend_id": "TYDLSQ-OYNYU-3MNRER", // "order_id": "OGL7HR-SWFO4-NRQTHO" // }, // "success": true, // "time_in": "2025-05-14T13:54:10.840342Z", // "time_out": "2025-05-14T13:54:10.855046Z" // } // const result = this.safeDict(message, 'result', {}); const order = this.parseOrder(result); const messageHash = this.safeValue2(message, 'reqid', 'req_id'); client.resolve(order, messageHash); } /** * @method * @name kraken#editOrderWs * @description edit a trade order * @see https://docs.kraken.com/api/docs/websocket-v2/amend_order * @param {string} id order id * @param {string} symbol unified symbol of the market to create an order in * @param {string} type 'market' or 'limit' * @param {string} side 'buy' or 'sell' * @param {float} amount how much of the currency you want to trade in units of the base currency * @param {float} [price] the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ async editOrderWs(id, symbol, type, side, amount = undefined, price = undefined, params = {}) { await this.loadMarkets(); const token = await this.authenticate(); const url = this.urls['api']['ws']['privateV2']; const requestId = this.requestId(); const messageHash = requestId; let request = { 'method': 'amend_order', 'params': { 'order_id': id, 'order_qty': this.parseToNumeric(this.amountToPrecision(symbol, amount)), 'token': token, }, 'req_id': requestId, }; [request, params] = this.orderRequestWs('editOrderWs', symbol, type, request, amount, price, params); return await this.watch(url, messageHash, this.extend(request, params), messageHash); } /** * @method * @name kraken#cancelOrdersWs * @description cancel multiple orders * @see https://docs.kraken.com/api/docs/websocket-v2/cancel_order * @param {string[]} ids order ids * @param {string} [symbol] unified market symbol, default is undefined * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} */ async cancelOrdersWs(ids, symbol = undefined, params = {}) { if (symbol !== undefined) { throw new NotSupported(this.id + ' cancelOrdersWs () does not support cancelling orders for a specific symbol.'); } await this.loadMarkets(); const token = await this.authenticate(); const url = this.urls['api']['ws']['privateV2']; const requestId = this.requestId(); const messageHash = requestId; const request = { 'method': 'cancel_order', 'params': { 'order_id': ids, 'token': token, }, 'req_id': requestId, }; return await this.watch(url, messageHash, this.extend(request, params), messageHash); } /** * @method * @name kraken#cancelOrderWs * @description cancels an open order * @see https://docs.kraken.com/api/docs/websocket-v2/cancel_order * @param {string} id order id * @param {string} [symbol] unified symbol of the market the order was made in * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ async cancelOrderWs(id, symbol = undefined, params = {}) { if (symbol !== undefined) { throw new NotSupported(this.id + ' cancelOrderWs () does not support cancelling orders for a specific symbol.'); } await this.loadMarkets(); const token = await this.authenticate(); const url = this.urls['api']['ws']['privateV2']; const requestId = this.requestId(); const messageHash = requestId; const request = { 'method': 'cancel_order', 'params': { 'order_id': [id], 'token': token, }, 'req_id': requestId, }; return await this.watch(url, messageHash, this.extend(request, params), messageHash); } handleCancelOrder(client, message) { // // { // "method": "cancel_order", // "req_id": 123456789, // "result": { // "order_id": "OKAGJC-YHIWK-WIOZWG" // }, // "success": true, // "time_in": "2023-09-21T14:36:57.428972Z", // "time_out": "2023-09-21T14:36:57.437952Z" // } // const reqId = this.safeValue(message, 'req_id'); client.resolve(message, reqId); } /** * @method * @name kraken#cancelAllOrdersWs * @description cancel all open orders * @see https://docs.kraken.com/api/docs/websocket-v2/cancel_all * @param {string} [symbol] unified market symbol, only orders in the market of this symbol are cancelled when symbol is not undefined * @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 cancelAllOrdersWs(symbol = undefined, params = {}) { if (symbol !== undefined) { throw new NotSupported(this.id + ' cancelAllOrdersWs () does not support cancelling orders in a specific market.'); } await this.loadMarkets(); const token = await this.authenticate(); const url = this.urls['api']['ws']['privateV2']; const requestId = this.requestId(); const messageHash = requestId; const request = { 'method': 'cancel_all', 'params': { 'token': token, }, 'req_id': requestId, }; return await this.watch(url, messageHash, this.extend(request, params), messageHash); } handleCancelAllOrders(client, message) { // // { // "method": "cancel_all", // "req_id": 123456789, // "result": { // "count": 1 // }, // "success": true, // "time_in": "2023-09-21T14:36:57.428972Z", // "time_out": "2023-09-21T14:36:57.437952Z" // } // const reqId = this.safeValue(message, 'req_id'); client.resolve(message, reqId); } handleTicker(client, message) { // // { // "channel": "ticker", // "type": "snapshot", // "data": [ // { // "symbol": "BTC/USD", // "bid": 108359.8, // "bid_qty": 0.01362603, // "ask": 108359.9, // "ask_qty": 17.17988863, // "last": 108359.8, // "volume": 2158.32346723, // "vwap": 108894.5, // "low": 106824, // "high": 111300, // "change": -2679.9, // "change_pct": -2.41 // } // ] // } // const data = this.safeList(message, 'data', []); const ticker = data[0]; const symbol = this.safeString(ticker, 'symbol'); const messageHash = this.getMessageHash('ticker', undefined, symbol); const vwap = this.safeString(ticker, 'vwap'); let quoteVolume = undefined; const baseVolume = this.safeString(ticker, 'volume'); if (baseVolume !== undefined && vwap !== undefined) { quoteVolume = Precise.stringMul(baseVolume, vwap); } const last = this.safeString(ticker, 'last'); const result = this.safeTicker({ 'symbol': symbol, 'timestamp': undefined, 'datetime': undefined, 'high': this.safeString(ticker, 'high'), 'low': this.safeString(ticker, 'low'), 'bid': this.safeString(ticker, 'bid'), 'bidVolume': this.safeString(ticker, 'bid_qty'), 'ask': this.safeString(ticker, 'ask'), 'askVolume': this.safeString(ticker, 'ask_qty'), 'vwap': vwap, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': this.safeString(ticker, 'change'), 'percentage': this.safeString(ticker, 'change_pct'), 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }); this.tickers[symbol] = result; client.resolve(result, messageHash); } handleTrades(client, message) { // // { // "channel": "trade", // "type": "update", // "data": [ // { // "symbol": "MATIC/USD", // "side": "sell", // "price": 0.5117, // "qty": 40.0, // "ord_type": "market", // "trade_id": 4665906, // "timestamp": "2023-09-25T07:49:37.708706Z" // } // ] // } // const data = this.safeList(message, 'data', []); const trade = data[0]; const symbol = this.safeString(trade, 'symbol'); const messageHash = this.getMessageHash('trade', undefined, 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; } const market = this.market(symbol); const parsed = this.parseTrades(data, market); for (let i = 0; i < parsed.length; i++) { stored.append(parsed[i]); } client.resolve(stored, messageHash); } handleOHLCV(client, message, subscription) { // // [ // 216, // channelID // [ // "1574454214.962096", // Time, seconds since epoch // "1574454240.000000", // End timestamp of the interval // "0.020970", // Open price at midnight UTC // "0.020970", // Intraday high price // "0.020970", // Intraday low price // "0.020970", // Closing price at midnight UTC // "0.020970", // Volume weighted average price // "0.08636138", // Accumulated volume today // 1, // Number of trades today // ], // "ohlc-1", // Channel Name of subscription // "ETH/XBT", // Asset pair // ] // const info = this.safeValue(subscription, 'subscription', {}); const interval = this.safeInteger(info, 'interval'); const name = this.safeString(info, 'name'); const wsName = this.safeString(message, 3); const market = this.safeValue(this.options['marketsByWsName'], wsName); const symbol = market['symbol']; const timeframe = this.findTimeframe(interval); const duration = this.parseTimeframe(timeframe); if (timeframe !== undefined) { const candle = this.safeValue(message, 1); const messageHash = name + ':' + timeframe + ':' + wsName; let timestamp = this.safeFloat(candle, 1); timestamp -= duration; const ts = this.parseToInt(timestamp * 1000); const result = [ ts, this.safeFloat(candle, 2), this.safeFloat(candle, 3), this.safeFloat(candle, 4), this.safeFloat(candle, 5), this.safeFloat(candle, 7), ]; 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; } stored.append(result); client.resolve(stored, messageHash); } } requestId() { // their support said that reqid must be an int32, not documented const reqid = this.sum(this.safeInteger(this.options, 'reqid', 0), 1); this.options['reqid'] = reqid; return reqid; } async watchPublic(name, symbol, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const wsName = this.safeValue(market['info'], 'wsname'); const messageHash = name + ':' + wsName; const url = this.urls['api']['ws']['public']; const requestId = this.requestId(); const subscribe = { 'event': 'subscribe', 'reqid': requestId, 'pair': [ wsName, ], 'subscription': { 'name': name, }, }; const request = this.deepExtend(subscribe, params); return await this.watch(url, messageHash, request, messageHash); } /** * @method * @name kraken#watchTicker * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://docs.kraken.com/api/docs/websocket-v2/ticker * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchTicker(symbol, params = {}) { await this.loadMarkets(); symbol = this.symbol(symbol); const tickers = await this.watchTickers([symbol], params); return tickers[symbol]; } /** * @method * @name kraken#watchTickers * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://docs.kraken.com/api/docs/websocket-v2/ticker * @param {string[]} symbols * @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 ticker = await this.watchMultiHelper('ticker', 'ticker', symbols, undefined, params); if (this.newUpdates) { const result = {}; result[ticker['symbol']] = ticker; return result; } return this.filterByArray(this.tickers, 'symbol', symbols); } /** * @method * @name kraken#watchBidsAsks * @description watches best bid & ask for symbols * @see https://docs.kraken.com/api/docs/websocket-v2/ticker * @param {string[]} symbols unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async watchBidsAsks(symbols = undefined, params = {}) { await this.loadMarkets(); symbols = this.marketSymbols(symbols, undefined, false); params['event_trigger'] = 'bbo'; const ticker = await this.watchMultiHelper('bidask', 'ticker', symbols, undefined, params); if (this.newUpdates) { const result = {}; result[ticker['symbol']] = ticker; return result; } return this.filterByArray(this.bidsasks, 'symbol', symbols); } /** * @method * @name kraken#watchTrades * @description get the list of most recent trades for a particular symbol * @see https://docs.kraken.com/api/docs/websocket-v2/trade * @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 * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades} */ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { return await this.watchTradesForSymbols([symbol], since, limit, params); } /** * @method * @name kraken#watchTradesForSymbols * @description get the list of most recent trades for a list of symbols * @see https://docs.kraken.com/api/docs/websocket-v2/trade * @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 * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades} */ async watchTradesForSymbols(symbols, since = undefined, limit = undefined, params = {}) { const trades = await this.watchMultiHelper('trade', 'trade', symbols, undefined, params); if (this.newUpdates) { const first = this.safeList(trades, 0); const tradeSymbol = this.safeString(first, 'symbol'); limit = trades.getLimit(tradeSymbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } /** * @method * @name kraken#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://docs.kraken.com/api/docs/websocket-v2/book * @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 kraken#watchOrderBookForSymbols * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://docs.kraken.com/api/docs/websocket-v2/book * @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 = {}) { const request = {}; if (limit !== undefined) { if (this.inArray(limit, [10, 25, 100, 500, 1000])) { request['params'] = { 'depth': limit, // default 10, valid options 10, 25, 100, 500, 1000 }; } else { throw new NotSupported(this.id + ' watchOrderBook accepts limit values of 10, 25, 100, 500 and 1000 only'); } } const orderbook = await this.watchMultiHelper('orderbook', 'book', symbols, { 'limit': limit }, this.extend(request, params)); return orderbook.limit(); } /** * @method * @name kraken#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://docs.kraken.com/api/docs/websocket-v1/ohlc * @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 = {}) { await this.loadMarkets(); const name = 'ohlc'; const market = this.market(symbol); symbol = market['symbol']; const wsName = this.safeValue(market['info'], 'wsname'); const messageHash = name + ':' + timeframe + ':' + wsName; const url = this.urls['api']['ws']['public']; const requestId = this.requestId(); const subscribe = { 'event': 'subscribe', 'reqid': requestId, 'pair': [ wsName, ], 'subscription': { 'name': name, 'interval': this.safeValue(this.timeframes, timeframe, timeframe), }, }; const request = this.deepExtend(subscribe, params); const ohlcv = await this.watch(url, messageHash, request, messageHash); if (this.newUpdates) { limit = ohlcv.getLimit(symbol, limit); } return this.filterBySinceLimit(ohlcv, since, limit, 0, true); } async loadMarkets(reload = false, params = {}) { const markets = await super.loadMarkets(reload, params); let marketsByWsName = this.safeValue(this.options, 'marketsByWsName'); if ((marketsByWsName === undefined) || reload) { marketsByWsName = {}; for (let i = 0; i < this.symbols.length; i++) { const symbol = this.symbols[i]; const market = this.markets[symbol]; const info = this.safeValue(market, 'info', {}); const wsName = this.safeString(info, 'wsname'); marketsByWsName[wsName] = market; } this.options['marketsByWsName'] = marketsByWsName; } return markets; } ping(client) { const url = client.url; const request = {}; if (url.indexOf('v2') >= 0) { request['method'] = 'ping'; } else { request['event'] = 'ping'; } return request; } handlePong(client, message) { client.lastPong = this.milliseconds(); return message; } async watchHeartbeat(params = {}) { await this.loadMarkets(); const event = 'heartbeat'; const url = this.urls['api']['ws']['public']; return await this.watch(url, event); } handleHeartbeat(client, message) { // // every second (approx) if no other updates are sent // // { "event": "heartbeat" } // const event = this.safeString(message, 'event'); client.resolve(message, event); } handleOrderBook(client, message) { // // first message (snapshot) // // { // "channel": "book", // "type": "snapshot", // "data": [ // { // "symbol": "MATIC/USD", // "bids": [ // { // "price": 0.5666, // "qty": 4831.75496356 // }, // { // "price": 0.5665, // "qty": 6658.22734739 // } // ], // "asks": [ // { // "price": 0.5668, // "qty": 4410.79769741 // }, // { // "price": 0.5669, // "qty": 4655.40412487 // } // ], // "checksum": 2439117997 // } // ] // } // // subsequent updates // // { // "channel": "book", // "type": "update", // "data": [ // { // "symbol": "MATIC/USD", // "bids": [ // { // "price": 0.5657, // "qty": 1098.3947558 // } // ], // "asks": [], // "checksum": 2114181697, // "timestamp": "2023-10-06T17:35:55.440295Z" // } // ] // } // const type = this.safeString(message, 'type'); const data = this.safeList(message, 'data', []); const first = this.safeDict(data, 0, {}); const symbol = this.safeString(first, 'symbol'); const a = this.safeValue(first, 'asks', []); const b = this.safeValue(first, 'bids', []); const c = this.safeInteger(first, 'checksum'); const messageHash = this.getMessageHash('orderbook', undefined, symbol); let orderbook = undefined; if (type === 'update') { orderbook = this.orderbooks[symbol]; const storedAsks = orderbook['asks']; const storedBids = orderbook['bids']; if (a !== undefined) { this.customHandleDeltas(storedAsks, a); } if (b !== undefined) { this.customHandleDeltas(storedBids, b); } const datetime = this.safeString(first, 'timestamp'); orderbook['symbol'] = symbol; orderbook['timestamp'] = this.parse8601(datetime); orderbook['datetime'] = datetime; } else { // snapshot const depth = a.length; this.orderbooks[symbol] = this.orderBook({}, depth); orderbook = this.orderbooks[symbol]; const keys = ['asks', 'bids']; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const bookside = orderbook[key]; const deltas = this.safeValue(first, key, []); if (deltas.length > 0) { this.customHandleDeltas(bookside, deltas); } } orderbook['symbol'] = symbol; } orderbook.limit(); // checksum temporarily disabled because the exchange checksum was not reliable const checksum = this.handleOption('watchOrderBook', 'checksum', false); if (checksum) { const payloadArray = []; if (c !== undefined) { const checkAsks = orderbook['asks']; const checkBids = orderbook['bids']; // const checkAsks = asks.map ((elem) => [ elem['price'], elem['qty'] ]); // const checkBids = bids.map ((elem) => [ elem['price'], elem['qty'] ]); for (let i = 0; i < 10; i++) { const currentAsk = this.safeValue(checkAsks, i, {}); const formattedAsk = this.formatNumber(currentAsk[0]) + this.formatNumber(currentAsk[1]); payloadArray.push(formattedAsk); } for (let i = 0; i < 10; i++) { const currentBid = this.safeValue(checkBids, i, {}); const formattedBid = this.formatNumber(currentBid[0]) + this.formatNumber(currentBid[1]); payloadArray.push(formattedBid); } } const payload = payloadArray.join(''); const localChecksum = this.crc32(payload, false); if (localChecksum !== c) { const error = new ChecksumError(this.id + ' ' + this.orderbookChecksumMessage(symbol)); delete client.subscriptions[messageHash]; delete this.orderbooks[symbol]; client.reject(error, messageHash); return; } } client.resolve(orderbook, messageHash); } customHandleDeltas(bookside, deltas) { // const sortOrder = (key === 'bids') ? true : false; for (let j = 0; j < deltas.length; j++) { const delta = deltas[j]; const price = this.safeNumber(delta, 'price'); const amount = this.safeNumber(delta, 'qty'); bookside.store(price, amount); // if (amount === 0) { // const index = bookside.findIndex ((x: Int) => x[0] === price); // bookside.splice (index, 1); // } else { // bookside.store (price, amount); // } // bookside = this.sortBy (bookside, 0, sortOrder); // bookside.slice (0, 9); } } formatNumber(data) { const parts = data.split('.'); const integer = this.safeString(parts, 0); const decimals = this.safeString(parts, 1, ''); let joinedResult = integer + decimals; let i = 0; while (joinedResult[i] === '0') { i += 1; } if (i > 0) { joinedResult = joinedResult.slice(i); } return joinedResult; } handleSystemStatus(client, message) { // // todo: answer the question whether handleSystemStatus should be renamed // and unified as handleStatus for any usage pattern that // involves system status and maintenance updates // // { // "connectionID": 15527282728335292000, // "event": "sy