UNPKG

astro-perp-ccxt-dev

Version:

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

1,784 lines 82.6 kB
'use strict';

var kraken$1 = require('../kraken.js');
var errors = require('../base/errors.js');
var Cache = require('../base/ws/Cache.js');
var Precise = require('../base/Precise.js');

// ----------------------------------------------------------------------------
//  ---------------------------------------------------------------------------
class kraken extends kraken$1 {
    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': errors.BadRequest,
                    },
                    'broad': {
                        'Already subscribed': errors.BadRequest,
                        'Currency pair not in ISO 4217-A3 format': errors.BadSymbol,
                        'Malformed request': errors.BadRequest,
                        'Pair field must be an array': errors.BadRequest,
                        'Pair field unsupported for this subscription type': errors.BadRequest,
                        'Pair(s) not found': errors.BadSymbol,
                        'Subscription book depth must be an integer': errors.BadRequest,
                        'Subscription depth not supported': errors.BadRequest,
                        'Subscription field must be an object': errors.BadRequest,
                        'Subscription name invalid': errors.BadRequest,
                        'Subscription object unsupported field': errors.BadRequest,
                        'Subscription ohlc interval must be an integer': errors.BadRequest,
                        'Subscription ohlc interval not supported': errors.BadRequest,
                        'Subscription ohlc requires interval': errors.BadRequest,
                        'EAccount:Invalid permissions': errors.PermissionDenied,
                        'EAuth:Account temporary disabled': errors.AccountSuspended,
                        'EAuth:Account unconfirmed': errors.AuthenticationError,
                        'EAuth:Rate limit exceeded': errors.RateLimitExceeded,
                        'EAuth:Too many requests': errors.RateLimitExceeded,
                        'EDatabase: Internal error (to be deprecated)': errors.ExchangeError,
                        'EGeneral:Internal error[:<code>]': errors.ExchangeError,
                        'EGeneral:Invalid arguments': errors.BadRequest,
                        'EOrder:Cannot open opposing position': errors.InvalidOrder,
                        'EOrder:Cannot open position': errors.InvalidOrder,
                        'EOrder:Insufficient funds (insufficient user funds)': errors.InsufficientFunds,
                        'EOrder:Insufficient margin (exchange does not have sufficient funds to allow margin trading)': errors.InsufficientFunds,
                        'EOrder:Invalid price': errors.InvalidOrder,
                        'EOrder:Margin allowance exceeded': errors.InvalidOrder,
                        'EOrder:Margin level too low': errors.InvalidOrder,
                        'EOrder:Margin position size exceeded (client would exceed the maximum position size for this pair)': errors.InvalidOrder,
                        'EOrder:Order minimum not met (volume too low)': errors.InvalidOrder,
                        'EOrder:Orders limit exceeded': errors.InvalidOrder,
                        'EOrder:Positions limit exceeded': errors.InvalidOrder,
                        'EOrder:Rate limit exceeded': errors.RateLimitExceeded,
                        'EOrder:Scheduled orders limit exceeded': errors.InvalidOrder,
                        'EOrder:Unknown position': errors.OrderNotFound,
                        'EOrder:Unknown order': errors.OrderNotFound,
                        'EOrder:Invalid order': errors.InvalidOrder,
                        'EService:Deadline elapsed': errors.ExchangeNotAvailable,
                        'EService:Market in cancel_only mode': errors.NotSupported,
                        'EService:Market in limit_only mode': errors.NotSupported,
                        'EService:Market in post_only mode': errors.NotSupported,
                        'EService:Unavailable': errors.ExchangeNotAvailable,
                        'ETrade:Invalid request': errors.BadRequest,
                        'ESession:Invalid session': errors.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 errors.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 errors.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 errors.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 errors.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 errors.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["default"].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 Cache.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 Cache.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 errors.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 errors.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": "systemStatus",
        //         "status": "online", // online|maintenance|(custom status tbd)
        //         "version": "0.2.0"
        //     }
        //
        // v2
        //     {
        //         channel: 'status',
        //         type: 'update',
        //         data: [
        //             {
        //                 version: '2.0.10',
        //                 system: 'online',
        //                 api_version: 'v2',
        //                 connection_id: 6447481662169813000
        //             }
        //         ]
        //     }
        //
        return message;
    }
    async authenticate(params = {}) {
        const url = this.urls['api']['ws']['private'];
        const client = this.client(url);
        const authenticated = 'authenticated';
        let subscription = this.safeValue(client.subscriptions, authenticated);
        const now = this.seconds();
        const start = this.safeInteger(subscription, 'start');
        const expires = this.safeInteger(subscription, 'expires');
        if ((subscription === undefined) || ((subscription !== undefined) && (start + expires) <= now)) {
            // https://docs.kraken.com/api/docs/rest-api/get-websockets-token
            const response = await this.privatePostGetWebSocketsToken(params);
            //
            //     {
            //         "error":[],
            //         "result":{
            //             "token":"xeAQ\/RCChBYNVh53sTv1yZ5H4wIbwDF20PiHtTF+4UI",
            //             "expires":900
            //         }
            //     }
            //
            subscription = this.safeDict(response, 'result');
            subscription['start'] = now;
            client.subscriptions[authenticated] = subscription;
        }
        return this.safeString(subscription, 'token');
    }
    async watchPrivate(name, symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets();
        const token = await this.authenticate();
        const subscriptionHash = name;
        let messageHash = name;
        if (symbol !== undefined) {
            symbol = this.symbol(symbol);
            messageHash += ':' + symbol;
        }
        const url = this.urls['api']['ws']['private'];
        const requestId = this.requestId();
        const subscribe = {
            'event': 'subscribe',
            'reqid': requestId,
            'subscription': {
                'name': name,
                'token': token,
            },
        };
        const request = this.deepExtend(subscribe, params);
        const result = await this.watch(url, messageHash, request, subscriptionHash);
        if (this.newUpdates) {
            limit = result.getLimit(symbol, limit);
        }
        return this.filterBySymbolSinceLimit(result, symbol, since, limit);
    }
    /**
     * @method
     * @name kraken#watchMyTrades
     * @description watches information on multiple trades made by the user
     * @see https://docs.kraken.com/api/docs/websocket-v1/owntrades
     * @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 watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
        return await this.watchPrivate('ownTrades', symbol, since, limit, params);
    }
    handleMyTrades(client, message, subscription = undefined) {
        //
        //     [
        //         [
        //             {
        //                 "TT5UC3-GOIRW-6AZZ6R": {
        //                     "cost": "1493.90107",
        //                     "fee": "3.88415",
        //                     "margin": "0.00000",
        //                     "ordertxid": "OTLAS3-RRHUF-NDWH5A",
        //                     "ordertype": "market",
        //                     "pair": "XBT/USDT",
        //                     "postxid": "TKH2SE-M7IF5-CFI7LT",
        //                     "price": "6851.50005",
        //                     "time": "1586822919.335498",
        //                     "type": "sell",
        //                     "vol": "0.21804000"
        //                 }
        //             },
        //             {
        //                 "TIY6G4-LKLAI-Y3GD4A": {
        //                     "cost": "22.17134",
        //                     "fee": "0.05765",
        //                     "margin": "0.00000",
        //                     "ordertxid": "ODQXS7-MOLK6-ICXKAA",
        //                     "ordertype": "market",
        //                     "pair": "ETH/USD",
        //                     "postxid": "TKH2SE-M7IF5-CFI7LT",
        //                     "price": "169.97999",
        //                     "time": "1586340530.895739",
        //                     "type": "buy",
        //                     "vol": "0.13043500"
        //                 }
        //             },
        //         ],
        //         "ownTrades",
        //         { sequence: 1 }
        //     ]
        //
        const allTrades = this.safeValue(message, 0, []);
        const allTradesLength = allTrades.length;
        if (allTradesLength > 0) {
            if (this.myTrades === undefined) {
                const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
                this.myTrades = new Cache.ArrayCache(limit);
            }
            const stored = this.myTrades;
            const symbols = {};
            for (let i = 0; i < allTrades.length; i++) {
                const trades = this.safeValue(allTrades, i, {});
                const ids = Object.keys(trades);
                for (let j = 0; j < ids.length; j++) {
                    const id = ids[j];
                    const trade = trades[id];
                    const parsed = this.parseWsTrade(this.extend({ 'id': id }, trade));
                    stored.append(parsed);
                    const symbol = parsed['symbol'];
                    symbols[symbol] = true;
                }
            }
            const name = 'ownTrades';
            client.resolve(this.myTrades, name);
            const keys = Object.keys(symbols);
            for (let i = 0; i < keys.length; i++) {
                const messageHash = name + ':' + keys[i];
                client.resolve(this.myTrades, messageHash);
            }
        }
    }
    parseWsTrade(trade, market = undefined) {
        //
        //     {
        //         "id": "TIMIRG-WUNNE-RRJ6GT", // injected from outside
        //         "ordertxid": "OQRPN2-LRHFY-HIFA7D",
        //         "postxid": "TKH2SE-M7IF5-CFI7LT",
        //         "pair": "USDCUSDT",
        //         "time": 1586340086.457,
        //         "type": "sell",
        //         "ordertype": "market",
        //         "price": "0.99860000",
        //         "cost": "22.16892001",
        //         "fee": "0.04433784",
        //         "vol": "22.20000000",
        //         "margin": "0.00000000",
        //         "misc": ''
        //     }
        //
        //     {
        //         "id": "TIY6G4-LKLAI-Y3GD4A",
        //         "cost": "22.17134",
        //         "fee": "0.05765",
        //         "margin": "0.00000",
        //         "ordertxid": "ODQXS7-MOLK6-ICXKAA",
        //         "ordertype": "market",
        //         "pair": "ETH/USD",
        //         "postxid": "TKH2SE-M7IF5-CFI7LT",
        //         "price": "169.97999",
        //         "time": "1586340530.895739",
        //         "type": "buy",
        //         "vol": "0.13043500"
        //     }
        //
        const wsName = this.safeString(trade, 'pair');
        market = this.safeValue(this.options['marketsByWsName'], wsName, market);
        let symbol = undefined;
        const orderId = this.safeString(trade, 'ordertxid');
        const id = this.safeString2(trade, 'id', 'postxid');
        const timestamp = this.safeTimestamp(trade, 'time');
        const side = this.safeString(trade, 'type');
        const type = this.safeString(trade, 'ordertype');
        const price = this.safeFloat(trade, 'price');
        const amount = this.safeFloat(trade, 'vol');
        let cost = undefined;
        let fee = undefined;
        if ('fee' in trade) {
            let currency = undefined;
            if (market !== undefined) {
                currency = market['quote'];
            }
            fee = {
                'cost': this.safeFloat(trade, 'fee'),
                'currency': currency,
            };
        }
        if (market !== undefined) {
            symbol = market['symbol'];
        }
        if (price !== undefined) {
            if (amount !== undefined) {
                cost = price * amount;
            }
        }
        return {
            'id': id,
            'order': orderId,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'side': side,
            'takerOrMaker': undefined,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        };
    }
    /**
     * @method
     * @name kraken#watchOrders
     * @see https://docs.kraken.com/api/docs/websocket-v1/openorders
     * @description watches information on multiple orders made by the user
     * @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  orde structures to retrieve
     * @param {object} [params] maximum number of orderic 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 = {}) {
        return await this.watchPrivate('openOrders', symbol, since, limit, params);
    }
    handleOrders(client, message, subscription = undefined) {
        //
        //     [
        //         [
        //             {
        //                 "OGTT3Y-C6I3P-XRI6HX": {
        //                     "cost": "0.00000",
        //                     "descr": {
        //                         "close": "",
        //                         "leverage": "0:1",
        //                         "order": "sell 10.00345345 XBT/EUR @ limit 34.50000 with 0:1 leverage",
        //                         "ordertype": "limit",
        //                         "pair": "XBT/EUR",
        //                         "price": "34.50000",
        //                         "price2": "0.00000",
        //                         "type": "sell"
        //                     },
        //                     "expiretm": "0.000000",
        //                     "fee": "0.00000",
        //                     "limitprice": "34.50000",
        //                     "misc": "",
        //                     "oflags": "fcib",
        //                     "opentm": "0.000000",
        //                     "price": "34.50000",
        //                     "refid": "OKIVMP-5GVZN-Z2D2UA",
        //                     "starttm": "0.000000",
        //                     "status": "open",
        //                     "stopprice": "0.000000",
        //                     "userref": 0,
        //                     "vol": "10.00345345",
        //                     "vol_exec": "0.00000000"
        //                 }
        //             },
        //             {
        //                 "OGTT3Y-C6I3P-XRI6HX": {
        //                     "cost": "0.00000",
        //                     "descr": {
        //                         "close": "",
        //                         "leverage": "0:1",
        //                         "order": "sell 0.00000010 XBT/EUR @ limit 5334.60000 with 0:1 leverage",
        //                         "ordertype": "limit",
        //                         "pair": "XBT/EUR",
        //                         "price": "5334.60000",
        //                         "price2": "0.00000",
        //                         "type": "sell"
        //                     },
        //                     "expiretm": "0.000000",
        //                     "fee": "0.00000",
        //                     "limitprice": "5334.60000",
        //                     "misc": "",
        //                     "oflags": "fcib",
        //                     "opentm": "0.000000",
        //                     "price": "5334.60000",
        //                     "refid": "OKIVMP-5GVZN-Z2D2UA",
        //                     "starttm": "0.000000",
        //                     "status": "open",
        //                     "stopprice": "0.000000",
        //                     "userref": 0,
        //                     "vol": "0.00000010",
        //                     "vol_exec": "0.00000000"
        //                 }
        //             },
        //         ],
        //         "openOrders",
        //         { "sequence": 234 }
        //     ]
        //
        // status-change
        //
        //     [
        //         [
        //             { "OGTT3Y-C6I3P-XRI6HX": { "status": "closed" }},
        //             { "OGTT3Y-C6I3P-XRI6HX": { "status": "closed" }},
        //         ],
        //         "openOrders",
        //         { "sequence": 59342 }
        //     ]
        //
        const allOrders = this.safeValue(message, 0, []);
        const allOrdersLength = allOrders.length;
        if (allOrdersLength > 0) {
            const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
            if (this.orders === undefined) {
                this.orders = new Cache.ArrayCacheBySymbolById(limit);
            }
            const stored = this.orders;
            const symbols = {};
            for (let i = 0; i < allOrders.length; i++) {
                const orders = this.safeValue(allOrders, i, {});
                const ids = Object.keys(orders);
                for (let j = 0; j < ids.length; j++) {
                    const id = ids[j];
                    const order = orders[id];
                    const parsed = this.parseWsOrder(order);
                    parsed['id'] = id;
                    let symbol = undefined;
                    const symbolsByOrderId = this.safeValue(this.options, 'symbolsByOrderId', {});
                    if (parsed['symbol'] !== undefined) {
                        symbol = parsed['symbol'];
                        symbolsByOrderId[id] = symbol;
                        this.options['symbolsByOrderId'] = symbolsByOrderId;
                    }
                    else {
                        symbol = this.safeString(symbolsByOrderId, id);
                    }
                    const previousOrders = this.safeValue(stored.hashmap, symbol);
                    const previousOrder = this.safeValue(previousOrders, id);
                    let newOrder = parsed;
                    if (previousOrder !== undefined) {
                        const newRawOrder = this.extend(previousOrder['info'], newOrder['info']);
                        newOrder = this.parseWsOrder(newRawOrder);
                        newOrder['id'] = id;
                    }
                    const length = stored.length;
                    if (length === limit && (previousOrder === undefined)) {
                        const first = stored[0];
                        if (first['id'] in symbolsByOrderId) {
                            delete symbolsByOrderId[first['id']];
                        }
                    }
                    stored.append(newOrder);
                    symbols[symbol] = true;
                }
            }
            const name = 'openOrders';
            client.resolve(this.orders, name);
            const keys = Object.keys(symbols);
            for (let i = 0; i < keys.length; i++) {
                const messageHash = name + ':' + keys[i];
                client.resolve(this.orders, messageHash);
            }
        }
    }
    parseWsOrder(order, market = undefined) {
        //
        // createOrder
        //    {
        //        "avg_price": "0.00000",
        //        "cost": "0.00000",
        //        "descr": {
        //            "close": null,
        //            "leverage": null,
        //            "order": "sell 0.01000000 ETH/USDT @ limit 1900.00000",
        //            "ordertype": "limit",
        //            "pair": "ETH/USDT",
        //            "price": "1900.00000",
        //            "price2": "0.00000",
        //            "type": "sell"
        //        },
        //        "expiretm": null,
        //        "fee": "0.00000",
        //        "limitprice": "0.00000",
        //        "misc": '',
        //        "oflags": "fciq",
        //        "opentm": "1667522705.757622",
        //        "refid": null,
        //        "starttm": null,
        //        "status": "open",
        //        "stopprice": "0.00000",
        //        "timeinforce": "GTC",
        //        "userref": 0,
        //        "vol": "0.01000000",
        //        "vol_exec": "0.00000000"
        //    }
        //
        const description = this.safeValue(order, 'descr', {});
        const orderDescription = this.safeString(description, 'order');
        let side = undefined;
        let type = undefined;
        let wsName = undefined;
        let price = undefined;
        let amount = undefined;
        if (orderDescription !== undefined) {
            const parts = orderDescription.split(' ');
            side = this.safeString(parts, 0);
            amount = this.safeString(parts, 1);
            wsName = this.safeString(parts, 2);
            type = this.safeString(parts, 4);
            price = this.safeString(parts, 5);
        }
        side = this.safeString(description, 'type', side);
        type = this.safeString(description, 'ordertype', type);
        wsName = this.safeString(description, 'pair', wsName);
        market = this.safeValue(this.options['marketsByWsName'], wsName, market);
        let symbol = undefined;
        const timestamp = this.safeTimestamp(order, 'opentm');
        amount = this.safeString(order, 'vol', amount);
        const filled = this.safeString(order, 'vol_exec');
        let fee = undefined;
        const cost = this.safeString(order, 'cost');
        price = this.safeString(description, 'price', price);
        if ((price === undefined) || (Precise["default"].stringEq(price, '0.0'))) {
            price = this.safeString(description, 'price2');
        }
        if ((price === undefined) || (Precise["default"].stringEq(price, '0.0'))) {
            price = this.safeString(order, 'price', price);
        }
        const average = this.safeString2(order, 'avg_price', 'price');
        if (market !== undefined) {
            symbol = market['symbol'];
            if ('fee' in order) {
                const flags = order['oflags'];
                const feeCost = this.safeString(order, 'fee');
                fee = {
                    'cost': feeCost,
                    'rate': undefined,
                };
                if (flags.indexOf('fciq') >= 0) {
                    fee['currency'] = market['quote'];
                }
                else if (flags.indexOf('fcib') >= 0) {
                    fee['currency'] = market['base'];
                }
            }
        }
        const status = this.parseOrderStatus(this.safeString(order, 'status'));
        let id = this.safeString(order, 'id');
        if (id === undefined) {
            const txid = this.safeValue(order, 'txid');
            id = this.safeString(txid, 0);
        }
        const clientOrderId = this.safeString(order, 'userref');
        const rawTrades = this.safeValue(order, 'trades');
        let trades = undefined;
        if (rawTrades !== undefined) {
            trades = this.parseTrades(rawTrades, market, undefined, undefined, { 'order': id });
        }
        const stopPrice = this.safeNumber(order, 'stopprice');
        return this.safeOrder({
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': this.iso8601(timestamp),
            'lastTradeTimestamp': undefined,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': undefined,
            'postOnly': undefined,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'triggerPrice': stopPrice,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'average': average,
            'remaining': undefined,
            'fee': fee,
            'trades': trades,
        });
    }
    async watchMultiHelper(unifiedName, channelName, symbols = undefined, subscriptionArgs = undefined, params = {}) {
        await this.loadMarkets();
        // symbols are required
        symbols = this.marketSymbols(symbols, undefined, false, true, false);
        const messageHashes = [];
        for (let i = 0; i < symbols.length; i++) {
            const eventTrigger = this.safeString(params, 'event_trigger');
            if (eventTrigger !== undefined) {
                messageHashes.push(this.getMessageHash(channelName, undefined, this.symbol(symbols[i])));
            }
            else {
                messageHashes.push(this.getMessageHash(unifiedName, undefined, this.symbol(symbols[i])));
            }
        }
        const request = {
            'method': 'subscribe',
            'params': {
                'channel': channelName,
                'symbol': symbols,
            },
            'req_id': this.requestId(),
        };
        request['params'] = this.deepExtend(request['params'], params);
        const url = this.urls['api']['ws']['publicV2'];
        return await this.watchMultiple(url, messageHashes, request, messageHashes, subscriptionArgs);
    }
    /**
     * @method
     * @name kraken#watchBalance
     * @description watch balance and get the amount of funds available for trading or funds locked in orders
     * @see https://docs.kraken.com/api/docs/websocket-v2/balances
     * @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();
        const token = await this.authenticate();
        const messageHash = 'balances';
        const url = this.urls['api']['ws']['privateV2'];
        const requestId = this.requestId();
        const subscribe = {
            'method': 'subscribe',
            'req_id': requestId,
            'params': {
                'channel': 'balances',
                'token': token,
            },
        };
        const request = this.deepExtend(subscribe, params);
        return await this.watch(url, messageHash, request, messageHash);
    }
    handleBalance(client, message) {
        //
        //     {
        //         "channel": "balances",
        //         "data": [
        //             {
        //                 "asset": "BTC",
        //                 "asset_class": "currency",
        //                 "balance": 1.2,
        //                 "wallets": [
        //                     {
        //                         "type": "spot",
        //                         "id": "main",
        //                         "balance": 1.2
        //                     }
        //                 ]
        //             }
        //         ],
        //         "type": "snapshot",
        //         "sequence": 1
        //     }
        //
        const data = this.safeList(message, 'data', []);
        const result = { 'info': message };
        for (let i = 0; i < data.length; i++) {
            const currencyId = this.safeString(data[i], 'asset');
            const code = this.safeCurrencyCode(currencyId);
            const account = this.account();
            const eq = this.safeString(data[i], 'balance');
            account['total'] = eq;
            result[code] = account;
        }
        const type = 'spot';
        const balance = this.safeBalance(result);
        const oldBalance = this.safeValue(this.balance, type, {});
        const newBalance = this.deepExtend(oldBalance, balance);
        this.balance[type] = this.safeBalance(newBalance);
        const channel = this.safeString(message, 'channel');
        client.resolve(this.balance[type], channel);
    }
    getMessageHash(unifiedElementName, subChannelName = undefined, symbol = undefined) {
        // unifiedElementName can be : orderbook, trade, ticker, bidask ...
        // subChannelName only applies to channel that needs specific variation (i.e. depth_50, depth_100..) to be selected
        const withSymbol = symbol !== undefined;
        let messageHash = unifiedElementName;
        if (!withSymbol) {
            messageHash += 's';
        }
        else {
            messageHash += '@' + symbol;
        }
        if (subChannelName !== undefined) {
            messageHash += '#' + subChannelName;
        }
        return messageHash;
    }
    handleSubscriptionStatus(client, message) {
        //
        // public
        //
        //     {
        //         "channelID": 210,
        //         "channelName": "book-10",
        //         "event": "subscriptionStatus",
        //         "reqid": 1574146735269,
        //         "pair": "ETH/XBT",
        //         "status": "subscribed",
        //         "subscription": { depth: 10, name: "book" }
        //     }
        //
        // private
        //
        //     {
        //         "channelName": "openOrders",
        //         "event": "subscriptionStatus",
        //         "reqid": 1,
        //         "status": "subscribed",
        //         "subscription": { maxratecount: 125, name: "openOrders" }
        //     }
        //
        const channelId = this.safeString(message, 'channelID');
        if (channelId !== undefined) {
            client.subscriptions[channelId] = message;
        }
        // const requestId = this.safeString (message, "reqid");
        // if (requestId in client.futures) {
        //     delete client.futures[requestId];
        // }
    }
    handleErrorMessage(client, message) {
        //
        //     {
        //         "errorMessage": "Currency pair not in ISO 4217-A3 format foobar",
        //         "event": "subscriptionStatus",
        //         "pair": "foobar",
        //         "reqid": 1574146735269,
        //         "status": "error",
        //         "subscription": { name: "ticker" }
        //     }
        //
        // v2
        //     {
        //         "error": "Unsupported field: 'price' for the given msg type: add order",
        //         "method": "add_order",
        //         "success": false,
        //         "time_in": "2025-05-13T08:59:44.803511Z",
        //         "time_out": "2025-05-13T08:59:44.803542Z'
        //     }
        //
        const errorMessage = this.safeString2(message, 'errorMessage', 'error');
        if (errorMessage !== undefined) {
            const requestId = this.safeValue2(message, 'reqid', 'req_id');
            if (requestId !== undefined) {
                const broad = this.exceptions['ws']['broad'];
                const broadKey = this.findBroadlyMatchedKey(broad, errorMessage);
                let exception = undefined;
                if (broadKey === undefined) {
                    exception = new errors.ExchangeError(errorMessage); // c# requirement to convert the errorMessage to string
                }
                else {
                    exception = new broad[broadKey](errorMessage);
                }
                client.reject(exception, requestId);
                return false;
            }
        }
        return true;
    }
    handleMessage(client, message) {
        if (Array.isArray(message)) {
            const channelId = this.safeString(message, 0);
            const subscription = this.safeValue(client.subscriptions, channelId, {});
            const info = this.safeValue(subscription, 'subscription', {});
            const messageLength = message.length;
            const channelName = this.safeString(message, messageLength - 2);
            const name = this.safeString(info, 'name');
            const methods = {
                // public
                'ohlc': this.handleOHLCV,
                // private
                'openOrders': this.handleOrders,
                'ownTrades': this.handleMyTrades,
            };
            const method = this.safeValue2(methods, name, channelName);
            if (method !== undefined) {
                method.call(this, client, message, subscription);
            }
        }
        else {
            const channel = this.safeString(message, 'channel');
            if (channel !== undefined) {
                const methods = {
                    'balances': this.handleBalance,
                    'book': this.handleOrderBook,
                    'ticker': this.handleTicker,
                    'trade': this.handleTrades,
                };
                const method = this.safeValue(methods, channel);
                if (method !== undefined) {
                    method.call(this, client, message);
                }
            }
            if (this.handleErrorMessage(client, message)) {
                const event = this.safeString2(message, 'event', 'method');
                const methods = {
                    'heartbeat': this.handleHeartbeat,
                    'systemStatus': this.handleSystemStatus,
                    'subscriptionStatus': this.handleSubscriptionStatus,
                    'add_order': this.handleCreateEditOrder,
                    'amend_order': this.handleCreateEditOrder,
                    'cancel_order': this.handleCancelOrder,
                    'cancel_all': this.handleCancelAllOrders,
                    'pong': this.handlePong,
                };
                const method = this.safeValue(methods, event);
                if (method !== undefined) {
                    method.call(this, client, message);
                }
            }
        }
    }
}

module.exports = kraken;