@kraken-crypto/ccxt
Version:
A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go
1,054 lines (1,051 loc) • 73.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
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["default"] {
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,
'Currency pair not supported': 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 = 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 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 = 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 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 = 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 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 = 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["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) {
//
// {
// "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 Cache.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 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(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 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']['