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