@kraken-crypto/ccxt
Version:
A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go
1,609 lines • 73.4 kB
JavaScript
// ---------------------------------------------------------------------------
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,
'Currency pair not supported': 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 = this.numberToString(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.safeString2(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 = this.numberToString(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 = this.numberToString(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 = this.numberToString(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.safeString(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 = this.numberToString(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.safeString(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) {
//
// {
// "channel": "ohlc",
// "type": "update",
// "timestamp": "2023-10-04T16:26:30.524394914Z",
// "data": [
// {
// "symbol": "MATIC/USD",
// "open": 0.5624,
// "high": 0.5628,
// "low": 0.5622,
// "close": 0.5627,
// "trades": 12,
// "volume": 30927.68066226,
// "vwap": 0.5626,
// "interval_begin": "2023-10-04T16:25:00.000000000Z",
// "interval": 5,
// "timestamp": "2023-10-04T16:30:00.000000Z"
// }
// ]
// }
//
const data = this.safeList(message, 'data', []);
const first = data[0];
const marketId = this.safeString(first, 'symbol');
const symbol = this.safeSymbol(marketId);
if (!(symbol in this.ohlcvs)) {
this.ohlcvs[symbol] = {};
}
const interval = this.safeInteger(first, 'interval');
const timeframe = this.findTimeframe(interval);
const messageHash = this.getMessageHash('ohlcv', undefined, symbol);
let stored = this.safeValue(this.ohlcvs[symbol], timeframe);
this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {});
if (stored === undefined) {
const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
stored = new ArrayCacheByTimestamp(limit);
this.ohlcvs[symbol][timeframe] = stored;
}
const ohlcvsLength = data.length;
for (let i = 0; i < ohlcvsLength; i++) {
const candle = data[ohlcvsLength - i - 1];
const datetime = this.safeString(candle, 'timestamp');
const timestamp = this.parse8601(datetime);
const parsed = [
timestamp,
this.safeString(candle, 'open'),
this.safeString(candle, 'high'),
this.safeString(candle, 'low'),
this.safeString(candle, 'close'),
this.safeString(candle, 'volume'),
];
stored.append(parsed);
}
client.resolve(stored, messageHash);
}
requestId() {
// their support said that reqid must be an int32, not documented
this.lockId();
const reqid = this.sum(this.safeInteger(this.options, 'reqid', 0), 1);
this.options['reqid'] = reqid;
this.unlockId();
return reqid;
}
/**
* @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 requiredParams = {};
if (limit !== undefined) {
if (this.inArray(limit, [10, 25, 100, 500, 1000])) {
requiredParams['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(requiredParams, 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-v2/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 url = this.urls['api']['ws']['publicV2'];
const requestId = this.requestId();
const messageHash = this.getMessageHash('ohlcv', undefined, symbol);
const subscribe = {
'method': 'subscribe',
'params': {
'channel': name,
'symbol': [symbol],
'interval': this.safeValue(this.timeframes, timeframe, timeframe),
},
'req_id': requestId,
};
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, 'timestamp', 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']['publicV2'];
return await this.watch(url, event);
}
handleHeartbeat(client, message) {
//
// every second (approx) if no other updates are sent
//
// { "channel": "heartbeat" }
//
const event = this.safeString(message, 'channel');
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": "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 = 'executions';
let messageHash = name;
if (symbol !== undefined) {
symbol = this.symbol(symbol);
messageHash += ':' + symbol;
}
const url = this.urls['api']['ws']['privateV2'];
const requestId = this.requestId();
const subscribe = {
'method': 'subscribe',
'params': {
'channel': 'executions',
'token': token,
},
'req_id': requestId,
};
if (params !== undefined) {
subscribe['params'] = this.deepExtend(subscribe['params'], params);
}
const result = await this.watch(url, messageHash, subscribe, 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-v2/executions
* @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 = {}) {
params['snap_trades'] = true;
return await this.watchPrivate('myTrades', symbol, since, limit, params);
}
handleMyTrades(client, message, subscription = undefined) {
//
// {
// "channel": "executions",
// "type": "update",
// "data": [
// {
// "order_id": "O6NTZC-K6FRH-ATWBCK",
// "exec_id": "T5DIUI-5N4KO-Z5BPXK",
// "exec_type": "trade",
// "trade_id": 8253473,
// "symbol": "USDC/USD",
// "side": "sell",
// "last_qty": 15.44,
// "last_price": 1.0002,
// "liquidity_ind": "t",
// "cost": 15.443088,
// "order_userref": 0,
// "order_status": "filled",
// "order_type": "market",
// "fee_usd_equiv": 0.03088618,
// "fees": [
// {
// "asset": "USD",
// "qty": 0.3458
// }
// ]
// }
// ],
// "sequence": 10
// }
//
const allTrades = this.safeList(message, 'data', []);
const allTradesLength = allTrades.length;
if (allTradesLength > 0) {
if (this.myTrades === undefined) {
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
this.myTrades = new ArrayCache(limit);
}
const stored = this.myTrades;
const symbols = {};
for (let i = 0; i < allTrades.length; i++) {
const trade = this.safeDict(allTrades, i, {});
const parsed = this.parseWsTrade(trade);
stored.append(parsed);
const symbol = parsed['symbol'];
symbols[symbol] = true;
}
const name = 'myTrades';
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) {
//
// {
// "order_id": "O6NTZC-K6FRH-ATWBCK",
// "exec_id": "T5DIUI-5N4KO-Z5BPXK",
// "exec_type": "trade",
// "trade_id": 8253473,
// "symbol": "USDC/USD",
// "side": "sell",
// "last_qty": 15.44,
// "last_price": 1.0002,
// "liquidity_ind": "t",
// "cost": 15.443088,
// "order_userref": 0,
// "order_status": "filled",
// "order_type": "market",
// "fee_usd_equiv": 0.03088618,
// "fees": [
// {
// "asset": "USD",
// "qty": 0.3458
// }
// ]
// }
//
let symbol = this.safeString(trade, 'symbol');
if (market !== undefined) {
symbol = market['symbol'];
}
let fee = undefined;
if ('fees' in trade) {
const fees = this.safeList(trade, 'fees', []);
const firstFee = this.safeDict(fees, 0, {});
fee = {
'cost': this.safeNumber(firstFee, 'qty'),
'currency': this.safeString(firstFee, 'asset'),
};
}
const datetime = this.safeString(trade, 'timestamp');
const liquidityIndicator = this.safeString(trade, 'liquidity_ind');
const takerOrMaker = (liquidityIndicator === 't') ? 'taker' : 'maker';
return {
'info': trade,
'id': this.safeString(trade, 'exec_id'),
'order': this.safeString(trade, 'order_id'),
'timestamp': this.parse8601(datetime),
'datetime': datetime,
'symbol': symbol,
'type': this.safeString(trade, 'order_type'),
'side': this.safeString(trade, 'side'),
'takerOrMaker': takerOrMaker,
'price': this.safeNumber(trade, 'last_price'),
'amount': this.safeNumber(trade, 'last_qty'),
'cost': this.safeNumber(trade, 'cost'),
'fee': fee,
};
}
/**
* @method
* @name kraken#watchOrders
* @description watches information on multiple orders made by the user
* @see https://docs.kraken.com/api/docs/websocket-v2/executions
* @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('orders', symbol, since, limit, this.extend(params, { 'snap_orders': true }));
}
handleOrders(client, message, subscription = undefined) {
//
// {
// "channel": "executions",
// "type": "update",
// "data": [
// {
// "order_id": "OK4GJX-KSTLS-7DZZO5",
// "order_userref": 3,
// "symbol": "BTC/USD",
// "order_qty": 0.005,
// "cum_cost": 0.0,
// "time_in_force": "GTC",
// "exec_type": "pending_new",
// "side": "sell",
// "order_type": "limit",
// "limit_price_type": "static",
// "limit_price": 26500.0,
// "stop_price": 0.0,
// "order_status": "pending_new",
// "fee_usd_equiv": 0.0,
// "fee_ccy_pref": "fciq",
// "timestamp": "2023-09-22T10:33:05.709950Z"
// }
// ],
// "sequence": 8
// }
//
const allOrders = this.safeList(message, 'data', []);
const allOrdersLength = allOrders.length;
if (allOrdersLength > 0) {
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
if (this.orders === undefined) {
this.orders = new ArrayCacheBySymbolById(limit);
}
const stored = this.orders;
const symbols = {};
for (let i = 0; i < allOrders.length; i++) {
const order = this.safeDict(allOrders, i, {});
const id = this.safeString(order, 'order_id');
const parsed = this.parseWsOrder(order);
const symbol = this.safeString(order, 'symbol');
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);
}
const length = stored.length;
if (length === limit && (previousOrder === undefined)) {
const first = stored[0];
const symbolsByOrderId = this.safeValue(this.options, 'symbolsByOrderId', {});
if (first['id'] in symbolsByOrderId) {
delete symbolsByOrderId[first['id']];
}
}
stored.append(newOrder);
if (symbol !== undefined) {
symbols[symbol] = true;
}
}
const name = 'orders';
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) {
//
// watchOrders
//
// open order
// {
// "order_id": "OK4GJX-KSTLS-7DZZO5",
// "order_userref": 3,
// "symbol": "BTC/USD",
// "order_qty": 0.005,
// "cum_cost": 0.0,
// "time_in_force": "GTC",
// "exec_type": "pending_new",
// "side": "sell",
// "order_type": "limit",
// "limit_price_type": "static",
// "limit_price": 26500.0,
// "stop_price": 0.0,
// "order_status": "pending_new",
// "fee_usd_equiv": 0.0,
// "fee_ccy_pref": "fciq",
// "timestamp": "2023-09-22T10:33:05.709950Z"
// }
//
// canceled order
//
// {
// "timestamp": "2025-10-11T15:11:47.695226Z",
// "order_status": "canceled",
// "exec_type": "canceled",
// "order_userref": 0,
// "order_id": "OGAB7Y-BKX5F-PTK5RW",
// "cum_qty": 0,
// "cum_cost": 0,
// "fee_usd_equiv": 0,
// "avg_price": 0,
// "cancel_reason": "User requested",
// "reason": "User requested"
// }
//
const fee = {
'cost': this.safeString(order, 'fee_usd_equiv'),
'currency': 'USD',
};
const stopPrice = this.safeString(order, 'stop_price');
const datetime = this.safeString(order, 'timestamp');
return this.safeOrder({
'id': this.safeString(order, 'order_id'),
'clientOrderId': this.safeString(order, 'order_userref'),
'info': order,
'timestamp': this.parse8601(datetime),
'datetime': datetime,
'lastTradeTimestamp': undefined,
'status': this.parseOrderStatus(this.safeString(order, 'order_status')),
'symbol': this.safeString(order, 'symbol'),
'type': this.safeString(order, 'order_type'),
'timeInForce': this.safeString(order, 'time_in_force'),
'postOnly': undefined,
'side': this.safeString(order, 'side'),
'price': this.safeString(order, 'limit_price'),
'stopPrice': stopPrice,
'triggerPrice': stopPrice,
'cost': this.safeString(order, 'cum_cost'),
'amount': this.safeString2(order, 'order_qty', 'cum_qty'),
'filled': undefined,
'average': this.safeString(order, 'avg_price'),
'remaining': undefined,
'fee': fee,
'trades': undefined,
});
}
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.safeString2(message, 'reqid', 'req_id');
const broad = this.exceptions['ws']['broad'];
const broadKey = this.findBroadlyMatchedKey(broad, errorMessage);
let exception = undefined;
if (broadKey === undefined) {
exception = new ExchangeError(errorMessage); // c# requirement to convert the errorMessage to string
}
else {
exception = new broad[broadKey](errorMessage);
}
if (requestId !== undefined) {
client.reject(exception, requestId);
}
return false;
}
return true;
}
handleMessage(client, message) {
let channel = this.safeString(message, 'channel');
if (channel !== undefined) {
if (channel === 'executions') {
const data = this.safeList(message, 'data', []);
const first = this.safeDict(data, 0, {});
const execType = this.safeString(first, 'exec_type');
channel = (execType === 'trade') ? 'myTrades' : 'orders';
}
const methods = {
'balances': this.handleBalance,
'book': this.handleOrderBook,
'ohlc': this.handleOHLCV,
'ticker': this.handleTicker,
'trade': this.handleTrades,
// private
'myTrades': this.handleMyTrades,
'orders': this.handleOrders,
};
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);
}
}
}
}