@proton/ccxt
Version:
A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 130+ exchanges
1,101 lines (1,098 loc) • 60.5 kB
JavaScript
'use strict';
var kraken$1 = require('../kraken.js');
var errors = require('../base/errors.js');
var Cache = require('../base/ws/Cache.js');
var Precise = require('../base/Precise.js');
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
class kraken extends kraken$1 {
describe() {
return this.deepExtend(super.describe(), {
'has': {
'ws': true,
'watchBalance': false,
'watchMyTrades': true,
'watchOHLCV': true,
'watchOrderBook': true,
'watchOrders': true,
'watchTicker': true,
'watchTickers': false,
'watchTrades': 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',
'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': {},
'checksum': true,
},
'exceptions': {
'ws': {
'exact': {
'Event(s) not found': errors.BadRequest,
},
'broad': {
'Already subscribed': errors.BadRequest,
'Currency pair not in ISO 4217-A3 format': errors.BadSymbol,
'Malformed request': errors.BadRequest,
'Pair field must be an array': errors.BadRequest,
'Pair field unsupported for this subscription type': errors.BadRequest,
'Pair(s) not found': errors.BadSymbol,
'Subscription book depth must be an integer': errors.BadRequest,
'Subscription depth not supported': errors.BadRequest,
'Subscription field must be an object': errors.BadRequest,
'Subscription name invalid': errors.BadRequest,
'Subscription object unsupported field': errors.BadRequest,
'Subscription ohlc interval must be an integer': errors.BadRequest,
'Subscription ohlc interval not supported': errors.BadRequest,
'Subscription ohlc requires interval': errors.BadRequest,
'EAccount:Invalid permissions': errors.PermissionDenied,
'EAuth:Account temporary disabled': errors.AccountSuspended,
'EAuth:Account unconfirmed': errors.AuthenticationError,
'EAuth:Rate limit exceeded': errors.RateLimitExceeded,
'EAuth:Too many requests': errors.RateLimitExceeded,
'EDatabase: Internal error (to be deprecated)': errors.ExchangeError,
'EGeneral:Internal error[:<code>]': errors.ExchangeError,
'EGeneral:Invalid arguments': errors.BadRequest,
'EOrder:Cannot open opposing position': errors.InvalidOrder,
'EOrder:Cannot open position': errors.InvalidOrder,
'EOrder:Insufficient funds (insufficient user funds)': errors.InsufficientFunds,
'EOrder:Insufficient margin (exchange does not have sufficient funds to allow margin trading)': errors.InsufficientFunds,
'EOrder:Invalid price': errors.InvalidOrder,
'EOrder:Margin allowance exceeded': errors.InvalidOrder,
'EOrder:Margin level too low': errors.InvalidOrder,
'EOrder:Margin position size exceeded (client would exceed the maximum position size for this pair)': errors.InvalidOrder,
'EOrder:Order minimum not met (volume too low)': errors.InvalidOrder,
'EOrder:Orders limit exceeded': errors.InvalidOrder,
'EOrder:Positions limit exceeded': errors.InvalidOrder,
'EOrder:Rate limit exceeded': errors.RateLimitExceeded,
'EOrder:Scheduled orders limit exceeded': errors.InvalidOrder,
'EOrder:Unknown position': errors.OrderNotFound,
'EOrder:Unknown order': errors.OrderNotFound,
'EOrder:Invalid order': errors.InvalidOrder,
'EService:Deadline elapsed': errors.ExchangeNotAvailable,
'EService:Market in cancel_only mode': errors.NotSupported,
'EService:Market in limit_only mode': errors.NotSupported,
'EService:Market in post_only mode': errors.NotSupported,
'EService:Unavailable': errors.ExchangeNotAvailable,
'ETrade:Invalid request': errors.BadRequest,
},
},
},
});
}
async createOrderWs(symbol, type, side, amount, price = undefined, params = {}) {
/**
* @method
* @name kraken#createOrderWs
* @see https://docs.kraken.com/websockets/#message-addOrder
* @description create a trade 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|undefined} price the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {object} an [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMarkets();
const token = await this.authenticate();
const market = this.market(symbol);
const url = this.urls['api']['ws']['private'];
const requestId = this.requestId();
const messageHash = requestId;
let request = {
'event': 'addOrder',
'token': token,
'reqid': requestId,
'ordertype': type,
'type': side,
'pair': market['wsId'],
'volume': this.amountToPrecision(symbol, amount),
};
[request, params] = this.orderRequest('createOrderWs()', symbol, type, request, price, params);
return await this.watch(url, messageHash, this.extend(request, params), messageHash);
}
handleCreateEditOrder(client, message) {
//
// createOrder
// {
// descr: 'sell 0.00010000 XBTUSDT @ market',
// event: 'addOrderStatus',
// reqid: 1,
// status: 'ok',
// txid: 'OAVXZH-XIE54-JCYYDG'
// }
// editOrder
// {
// "descr": "order edited price = 9000.00000000",
// "event": "editOrderStatus",
// "originaltxid": "O65KZW-J4AW3-VFS74A",
// "reqid": 3,
// "status": "ok",
// "txid": "OTI672-HJFAO-XOIPPK"
// }
//
const order = this.parseOrder(message);
const messageHash = this.safeValue(message, 'reqid');
client.resolve(order, messageHash);
}
async editOrderWs(id, symbol, type, side, amount, price = undefined, params = {}) {
/**
* @method
* @name kraken#editOrderWs
* @description edit a trade order
* @see https://docs.kraken.com/websockets/#message-editOrder
* @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|undefined} price the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {object} an [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMarkets();
const token = await this.authenticate();
const market = this.market(symbol);
const url = this.urls['api']['ws']['private'];
const requestId = this.requestId();
const messageHash = requestId;
let request = {
'event': 'editOrder',
'token': token,
'reqid': requestId,
'orderid': id,
'pair': market['wsId'],
'volume': this.amountToPrecision(symbol, amount),
};
[request, params] = this.orderRequest('editOrderWs()', symbol, type, request, price, params);
return await this.watch(url, messageHash, this.extend(request, params), messageHash);
}
async cancelOrdersWs(ids, symbol = undefined, params = {}) {
/**
* @method
* @name kraken#cancelOrdersWs
* @see https://docs.kraken.com/websockets/#message-cancelOrder
* @description cancel multiple orders
* @param {[string]} ids order ids
* @param {string|undefined} symbol unified market symbol, default is undefined
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {object} an list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMarkets();
const token = await this.authenticate();
const url = this.urls['api']['ws']['private'];
const requestId = this.requestId();
const messageHash = requestId;
const request = {
'event': 'cancelOrder',
'token': token,
'reqid': requestId,
'txid': ids,
};
return await this.watch(url, messageHash, this.extend(request, params), messageHash);
}
async cancelOrderWs(id, symbol = undefined, params = {}) {
/**
* @method
* @name kraken#cancelOrderWs
* @see https://docs.kraken.com/websockets/#message-cancelOrder
* @description cancels an open order
* @param {string} id order id
* @param {string|undefined} symbol unified symbol of the market the order was made in
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {object} An [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMarkets();
const token = await this.authenticate();
const url = this.urls['api']['ws']['private'];
const requestId = this.requestId();
const messageHash = requestId;
const clientOrderId = this.safeValue2(params, 'userref', 'clientOrderId', id);
params = this.omit(params, ['userref', 'clientOrderId']);
const request = {
'event': 'cancelOrder',
'token': token,
'reqid': requestId,
'txid': [clientOrderId],
};
return await this.watch(url, messageHash, this.extend(request, params), messageHash);
}
handleCancelOrder(client, message) {
//
// success
// {
// "event": "cancelOrderStatus",
// "status": "ok"
// "reqid": 1,
// }
//
const reqId = this.safeValue(message, 'reqid');
client.resolve(message, reqId);
}
async cancelAllOrdersWs(symbol = undefined, params = {}) {
/**
* @method
* @name kraken#cancelAllOrdersWs
* @see https://docs.kraken.com/websockets/#message-cancelAll
* @description cancel all open orders
* @param {string|undefined} 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 kraken api endpoint
* @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
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']['private'];
const requestId = this.requestId();
const messageHash = requestId;
const request = {
'event': 'cancelAll',
'token': token,
'reqid': requestId,
};
return await this.watch(url, messageHash, this.extend(request, params), messageHash);
}
handleCancelAllOrders(client, message) {
//
// {
// "count": 2,
// "event": "cancelAllStatus",
// "status": "ok",
// "reqId": 1
// }
//
const reqId = this.safeValue(message, 'reqid');
client.resolve(message, reqId);
}
handleTicker(client, message, subscription) {
//
// [
// 0, // channelID
// {
// "a": [ "5525.40000", 1, "1.000" ], // ask, wholeAskVolume, askVolume
// "b": [ "5525.10000", 1, "1.000" ], // bid, wholeBidVolume, bidVolume
// "c": [ "5525.10000", "0.00398963" ], // closing price, volume
// "h": [ "5783.00000", "5783.00000" ], // high price today, high price 24h ago
// "l": [ "5505.00000", "5505.00000" ], // low price today, low price 24h ago
// "o": [ "5760.70000", "5763.40000" ], // open price today, open price 24h ago
// "p": [ "5631.44067", "5653.78939" ], // vwap today, vwap 24h ago
// "t": [ 11493, 16267 ], // number of trades today, 24 hours ago
// "v": [ "2634.11501494", "3591.17907851" ], // volume today, volume 24 hours ago
// },
// "ticker",
// "XBT/USD"
// ]
//
const wsName = message[3];
const name = 'ticker';
const messageHash = name + ':' + wsName;
const market = this.safeValue(this.options['marketsByWsName'], wsName);
const symbol = market['symbol'];
const ticker = message[1];
const vwap = this.safeString(ticker['p'], 0);
let quoteVolume = undefined;
const baseVolume = this.safeString(ticker['v'], 0);
if (baseVolume !== undefined && vwap !== undefined) {
quoteVolume = Precise["default"].stringMul(baseVolume, vwap);
}
const last = this.safeString(ticker['c'], 0);
const timestamp = this.milliseconds();
const result = this.safeTicker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'high': this.safeString(ticker['h'], 0),
'low': this.safeString(ticker['l'], 0),
'bid': this.safeString(ticker['b'], 0),
'bidVolume': this.safeString(ticker['b'], 2),
'ask': this.safeString(ticker['a'], 0),
'askVolume': this.safeString(ticker['a'], 2),
'vwap': vwap,
'open': this.safeString(ticker['o'], 0),
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': undefined,
'average': undefined,
'baseVolume': baseVolume,
'quoteVolume': quoteVolume,
'info': ticker,
});
// todo add support for multiple tickers (may be tricky)
// kraken confirms multi-pair subscriptions separately one by one
// trigger correct watchTickers calls upon receiving any of symbols
this.tickers[symbol] = result;
client.resolve(result, messageHash);
}
handleTrades(client, message, subscription) {
//
// [
// 0, // channelID
// [ // price volume time side type misc
// [ "5541.20000", "0.15850568", "1534614057.321597", "s", "l", "" ],
// [ "6060.00000", "0.02455000", "1534614057.324998", "b", "l", "" ],
// ],
// "trade",
// "XBT/USD"
// ]
//
const wsName = this.safeString(message, 3);
const name = this.safeString(message, 2);
const messageHash = name + ':' + wsName;
const market = this.safeValue(this.options['marketsByWsName'], wsName);
const symbol = market['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 trades = this.safeValue(message, 1, []);
const parsed = this.parseTrades(trades, market);
for (let i = 0; i < parsed.length; i++) {
stored.append(parsed[i]);
}
client.resolve(stored, messageHash);
}
handleOHLCV(client, message, subscription) {
//
// [
// 216, // channelID
// [
// '1574454214.962096', // Time, seconds since epoch
// '1574454240.000000', // End timestamp of the interval
// '0.020970', // Open price at midnight UTC
// '0.020970', // Intraday high price
// '0.020970', // Intraday low price
// '0.020970', // Closing price at midnight UTC
// '0.020970', // Volume weighted average price
// '0.08636138', // Accumulated volume today
// 1, // Number of trades today
// ],
// 'ohlc-1', // Channel Name of subscription
// 'ETH/XBT', // Asset pair
// ]
//
const info = this.safeValue(subscription, 'subscription', {});
const interval = this.safeInteger(info, 'interval');
const name = this.safeString(info, 'name');
const wsName = this.safeString(message, 3);
const market = this.safeValue(this.options['marketsByWsName'], wsName);
const symbol = market['symbol'];
const timeframe = this.findTimeframe(interval);
const duration = this.parseTimeframe(timeframe);
if (timeframe !== undefined) {
const candle = this.safeValue(message, 1);
const messageHash = name + ':' + timeframe + ':' + wsName;
let timestamp = this.safeFloat(candle, 1);
timestamp -= duration;
const ts = this.parseToInt(timestamp * 1000);
const result = [
ts,
this.safeFloat(candle, 2),
this.safeFloat(candle, 3),
this.safeFloat(candle, 4),
this.safeFloat(candle, 5),
this.safeFloat(candle, 7),
];
this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {});
let stored = this.safeValue(this.ohlcvs[symbol], timeframe);
if (stored === undefined) {
const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
stored = new Cache.ArrayCacheByTimestamp(limit);
this.ohlcvs[symbol][timeframe] = stored;
}
stored.append(result);
client.resolve(stored, messageHash);
}
}
requestId() {
// their support said that reqid must be an int32, not documented
const reqid = this.sum(this.safeInteger(this.options, 'reqid', 0), 1);
this.options['reqid'] = reqid;
return reqid;
}
async watchPublic(name, symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const wsName = this.safeValue(market['info'], 'wsname');
const messageHash = name + ':' + wsName;
const url = this.urls['api']['ws']['public'];
const requestId = this.requestId();
const subscribe = {
'event': 'subscribe',
'reqid': requestId,
'pair': [
wsName,
],
'subscription': {
'name': name,
},
};
const request = this.deepExtend(subscribe, params);
return await this.watch(url, messageHash, request, messageHash);
}
async watchTicker(symbol, params = {}) {
/**
* @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
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
return await this.watchPublic('ticker', symbol, params);
}
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name kraken#watchTrades
* @description get the list of most recent trades for a particular symbol
* @param {string} symbol unified symbol of the market to fetch trades for
* @param {int|undefined} since timestamp in ms of the earliest trade to fetch
* @param {int|undefined} limit the maximum amount of trades to fetch
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades}
*/
await this.loadMarkets();
symbol = this.symbol(symbol);
const name = 'trade';
const trades = await this.watchPublic(name, symbol, params);
if (this.newUpdates) {
limit = trades.getLimit(symbol, limit);
}
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
}
async watchOrderBook(symbol, limit = undefined, params = {}) {
/**
* @method
* @name kraken#watchOrderBook
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int|undefined} limit the maximum amount of order book entries to return
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
const name = 'book';
const request = {};
if (limit !== undefined) {
if ((limit === 10) || (limit === 25) || (limit === 100) || (limit === 500) || (limit === 1000)) {
request['subscription'] = {
'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.watchPublic(name, symbol, this.extend(request, params));
return orderbook.limit();
}
async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name kraken#watchOHLCV
* @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @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|undefined} since timestamp in ms of the earliest candle to fetch
* @param {int|undefined} limit the maximum amount of candles to fetch
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
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.safeString(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];
if (market['darkpool']) {
const info = this.safeValue(market, 'info', {});
const altname = this.safeString(info, 'altname');
const wsName = altname.slice(0, 3) + '/' + altname.slice(3);
marketsByWsName[wsName] = market;
}
else {
const info = this.safeValue(market, 'info', {});
const wsName = this.safeString(info, 'wsname');
marketsByWsName[wsName] = market;
}
}
this.options['marketsByWsName'] = marketsByWsName;
}
return markets;
}
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, subscription) {
//
// first message (snapshot)
//
// [
// 1234, // channelID
// {
// "as": [
// [ "5541.30000", "2.50700000", "1534614248.123678" ],
// [ "5541.80000", "0.33000000", "1534614098.345543" ],
// [ "5542.70000", "0.64700000", "1534614244.654432" ]
// ],
// "bs": [
// [ "5541.20000", "1.52900000", "1534614248.765567" ],
// [ "5539.90000", "0.30000000", "1534614241.769870" ],
// [ "5539.50000", "5.00000000", "1534613831.243486" ]
// ]
// },
// "book-10",
// "XBT/USD"
// ]
//
// subsequent updates
//
// [
// 1234,
// { // optional
// "a": [
// [ "5541.30000", "2.50700000", "1534614248.456738" ],
// [ "5542.50000", "0.40100000", "1534614248.456738" ]
// ]
// },
// { // optional
// "b": [
// [ "5541.30000", "0.00000000", "1534614335.345903" ]
// ]
// },
// "book-10",
// "XBT/USD"
// ]
//
const messageLength = message.length;
const wsName = message[messageLength - 1];
const bookDepthString = message[messageLength - 2];
const parts = bookDepthString.split('-');
const depth = this.safeInteger(parts, 1, 10);
const market = this.safeValue(this.options['marketsByWsName'], wsName);
const symbol = market['symbol'];
let timestamp = undefined;
const messageHash = 'book:' + wsName;
// if this is a snapshot
if ('as' in message[1]) {
// todo get depth from marketsByWsName
this.orderbooks[symbol] = this.orderBook({}, depth);
const orderbook = this.orderbooks[symbol];
const sides = {
'as': 'asks',
'bs': 'bids',
};
const keys = Object.keys(sides);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const side = sides[key];
const bookside = orderbook[side];
const deltas = this.safeValue(message[1], key, []);
timestamp = this.handleDeltas(bookside, deltas, timestamp);
}
orderbook['symbol'] = symbol;
orderbook['timestamp'] = timestamp;
orderbook['datetime'] = this.iso8601(timestamp);
client.resolve(orderbook, messageHash);
}
else {
const orderbook = this.orderbooks[symbol];
// else, if this is an orderbook update
let a = undefined;
let b = undefined;
let c = undefined;
if (messageLength === 5) {
a = this.safeValue(message[1], 'a', []);
b = this.safeValue(message[2], 'b', []);
c = this.safeInteger(message[1], 'c');
c = this.safeInteger(message[2], 'c', c);
}
else {
c = this.safeInteger(message[1], 'c');
if ('a' in message[1]) {
a = this.safeValue(message[1], 'a', []);
}
else {
b = this.safeValue(message[1], 'b', []);
}
}
const storedAsks = orderbook['asks'];
const storedBids = orderbook['bids'];
let example = undefined;
if (a !== undefined) {
timestamp = this.handleDeltas(storedAsks, a, timestamp);
example = this.safeValue(a, 0);
}
if (b !== undefined) {
timestamp = this.handleDeltas(storedBids, b, timestamp);
example = this.safeValue(b, 0);
}
// don't remove this line or I will poop on your face
orderbook.limit();
const checksum = this.safeValue(this.options, 'checksum', true);
if (checksum) {
const priceString = this.safeString(example, 0);
const amountString = this.safeString(example, 1);
const priceParts = priceString.split('.');
const amountParts = amountString.split('.');
const priceLength = priceParts[1].length - 0;
const amountLength = amountParts[1].length - 0;
const payloadArray = [];
if (c !== undefined) {
for (let i = 0; i < 10; i++) {
const formatted = this.formatNumber(storedAsks[i][0], priceLength) + this.formatNumber(storedAsks[i][1], amountLength);
payloadArray.push(formatted);
}
for (let i = 0; i < 10; i++) {
const formatted = this.formatNumber(storedBids[i][0], priceLength) + this.formatNumber(storedBids[i][1], amountLength);
payloadArray.push(formatted);
}
}
const payload = payloadArray.join('');
const localChecksum = this.crc32(payload, false);
if (localChecksum !== c) {
const error = new errors.InvalidNonce(this.id + ' invalid checksum');
client.reject(error, messageHash);
}
}
orderbook['symbol'] = symbol;
orderbook['timestamp'] = timestamp;
orderbook['datetime'] = this.iso8601(timestamp);
client.resolve(orderbook, messageHash);
}
}
formatNumber(n, length) {
const string = this.numberToString(n);
const parts = string.split('.');
const integer = this.safeString(parts, 0);
const decimals = this.safeString(parts, 1, '');
const paddedDecimals = decimals.padEnd(length, '0');
const joined = integer + paddedDecimals;
let i = 0;
while (joined[i] === '0') {
i += 1;
}
if (i > 0) {
return joined.slice(i);
}
else {
return joined;
}
}
handleDeltas(bookside, deltas, timestamp = undefined) {
for (let j = 0; j < deltas.length; j++) {
const delta = deltas[j];
const price = this.parseNumber(delta[0]);
const amount = this.parseNumber(delta[1]);
const oldTimestamp = timestamp ? timestamp : 0;
timestamp = Math.max(oldTimestamp, this.parseToInt(parseFloat(delta[2]) * 1000));
bookside.store(price, amount);
}
return timestamp;
}
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'
// }
//
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);
if (subscription === undefined) {
const response = await this.privatePostGetWebSocketsToken(params);
//
// {
// "error":[],
// "result":{
// "token":"xeAQ\/RCChBYNVh53sTv1yZ5H4wIbwDF20PiHtTF+4UI",
// "expires":900
// }
// }
//
subscription = this.safeValue(response, 'result');
client.subscriptions[authenticated] = subscription;
}
return this.safeString(subscription, 'token');
}
async watchPrivate(name, symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const token = await this.authenticate();
const subscriptionHash = name;
let messageHash = name;
if (symbol !== undefined) {
symbol = this.symbol(symbol);
messageHash += ':' + symbol;
}
const url = this.urls['api']['ws']['private'];
const requestId = this.requestId();
const subscribe = {
'event': 'subscribe',
'reqid': requestId,
'subscription': {
'name': name,
'token': token,
},
};
const request = this.deepExtend(subscribe, params);
const result = await this.watch(url, messageHash, request, subscriptionHash);
if (this.newUpdates) {
limit = result.getLimit(symbol, limit);
}
return this.filterBySymbolSinceLimit(result, symbol, since, limit);
}
async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name kraken#watchMyTrades
* @description watches information on multiple trades made by the user
* @param {string} symbol unified market symbol of the market orders were made in
* @param {int|undefined} since the earliest time in ms to fetch orders for
* @param {int|undefined} limit the maximum number of orde structures to retrieve
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
*/
return await this.watchPrivate('ownTrades', symbol, since, limit, params);
}
handleMyTrades(client, message, subscription = undefined) {
//
// [
// [
// {
// 'TT5UC3-GOIRW-6AZZ6R': {
// cost: '1493.90107',
// fee: '3.88415',
// margin: '0.00000',
// ordertxid: 'OTLAS3-RRHUF-NDWH5A',
// ordertype: 'market',
// pair: 'XBT/USDT',
// postxid: 'TKH2SE-M7IF5-CFI7LT',
// price: '6851.50005',
// time: '1586822919.335498',
// type: 'sell',
// vol: '0.21804000'
// }
// },
// {
// 'TIY6G4-LKLAI-Y3GD4A': {
// cost: '22.17134',
// fee: '0.05765',
// margin: '0.00000',
// ordertxid: 'ODQXS7-MOLK6-ICXKAA',
// ordertype: 'market',
// pair: 'ETH/USD',
// postxid: 'TKH2SE-M7IF5-CFI7LT',
// price: '169.97999',
// time: '1586340530.895739',
// type: 'buy',
// vol: '0.13043500'
// }
// },
// ],
// 'ownTrades',
// { sequence: 1 }
// ]
//
const allTrades = this.safeValue(message, 0, []);
const allTradesLength = allTrades.length;
if (allTradesLength > 0) {
if (this.myTrades === undefined) {
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
this.myTrades = new Cache.ArrayCache(limit);
}
const stored = this.myTrades;
const symbols = {};
for (let i = 0; i < allTrades.length; i++) {
const trades = this.safeValue(allTrades, i, {});
const ids = Object.keys(trades);
for (let j = 0; j < ids.length; j++) {
const id = ids[j];
const trade = trades[id];
const parsed = this.parseWsTrade(this.extend({ 'id': id }, trade));
stored.append(parsed);
const symbol = parsed['symbol'];
symbols[symbol] = true;
}
}
const name = 'ownTrades';
client.resolve(this.myTrades, name);
const keys = Object.keys(symbols);
for (let i = 0; i < keys.length; i++) {
const messageHash = name + ':' + keys[i];
client.resolve(this.myTrades, messageHash);
}
}
}
parseWsTrade(trade, market = undefined) {
//
// {
// id: 'TIMIRG-WUNNE-RRJ6GT', // injected from outside
// ordertxid: 'OQRPN2-LRHFY-HIFA7D',
// postxid: 'TKH2SE-M7IF5-CFI7LT',
// pair: 'USDCUSDT',
// time: 1586340086.457,
// type: 'sell',
// ordertype: 'market',
// price: '0.99860000',
// cost: '22.16892001',
// fee: '0.04433784',
// vol: '22.20000000',
// margin: '0.00000000',
// misc: ''
// }
//
// {
// id: 'TIY6G4-LKLAI-Y3GD4A',
// cost: '22.17134',
// fee: '0.05765',
// margin: '0.00000',
// ordertxid: 'ODQXS7-MOLK6-ICXKAA',
// ordertype: 'market',
// pair: 'ETH/USD',
// postxid: 'TKH2SE-M7IF5-CFI7LT',
// price: '169.97999',
// time: '1586340530.895739',
// type: 'buy',
// vol: '0.13043500'
// }
//
const wsName = this.safeString(trade, 'pair');
market = this.safeValue(this.options['marketsByWsName'], wsName, market);
let symbol = undefined;
const orderId = this.safeString(trade, 'ordertxid');
const id = this.safeString2(trade, 'id', 'postxid');
const timestamp = this.safeTimestamp(trade, 'time');
const side = this.safeString(trade, 'type');
const type = this.safeString(trade, 'ordertype');
const price = this.safeFloat(trade, 'price');
const amount = this.safeFloat(trade, 'vol');
let cost = undefined;
let fee = undefined;
if ('fee' in trade) {
let currency = undefined;
if (market !== undefined) {
currency = market['quote'];
}
fee = {
'cost': this.safeFloat(trade, 'fee'),
'currency': currency,
};
}
if (market !== undefined) {
symbol = market['symbol'];
}
if (price !== undefined) {
if (amount !== undefined) {
cost = price * amount;
}
}
return {
'id': id,
'order': orderId,
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'symbol': symbol,
'type': type,
'side': side,
'takerOrMaker': undefined,
'price': price,
'amount': amount,
'cost': cost,
'fee': fee,
};
}
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name kraken#watchOrders
* @see https://docs.kraken.com/websockets/#message-openOrders
* @description watches information on multiple orders made by the user
* @param {string|undefined} symbol unified market symbol of the market orders were made in
* @param {int|undefined} since the earliest time in ms to fetch orders for
* @param {int|undefined} limit the maximum number of orde structures to retrieve
* @param {object} params extra parameters specific to the kraken api endpoint
* @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
return await this.watchPrivate('openOrders', symbol, since, limit, params);
}
handleOrders(client, message, subscription = undefined) {
//
// [
// [
// {
// "OGTT3Y-C6I3P-XRI6HX": {
// "cost": "0.00000",
// "descr": {
// "close": "",
// "leverage": "0:1",
// "order": "sell 10.00345345 XBT/EUR @ limit 34.50000 with 0:1 leverage",
// "ordertype": "limit",
// "pair": "XBT/EUR",
// "price": "34.50000",
// "price2": "0.00000",
// "type": "sell"
// },
// "expiretm": "0.000000",
// "fee": "0.00000",
// "limitprice": "34.50000",
// "misc": "",
// "oflags": "fcib",
// "opentm": "0.000000",
// "price": "34.50000",
// "refid": "OKIVMP-5GVZN-Z2D2UA",
// "starttm": "0.000000",
// "status": "open",
// "stopprice": "0.000000",
// "userref": 0,
// "vol": "10.00345345",
// "vol_exec": "0.00000000"
// }
// },
// {
// "OGTT3Y-C6I3P-XRI6HX": {
// "cost": "0.00000",
// "descr": {
// "close": "",
// "leverage": "0:1",
// "order": "sell 0.00000010 XBT/EUR @ limit 5334.60000 with 0:1 leverage",
// "ordertype": "limit",
// "pair": "XBT/EUR",
// "price": "5334.60000",
// "price2": "0.00000",
// "type": "sell"
// },
// "expiretm": "0.000000",
// "fee": "0.00000",
// "limitprice": "5334.60000",
// "misc": "",
// "oflags": "fcib",
// "opentm": "0.000000",
// "price": "5334.60000",
// "refid": "OKIVMP-5GVZN-Z2D2UA",
// "starttm": "0.000000",
// "status": "open",
// "stopprice": "0.000000",
// "userref": 0,
// "vol": "0.00000010",
// "vol_exec": "0.00000000"
// }
// },
// ],
// "openOrders",
// { "sequence": 234 }
// ]
//
// status-change
//
// [
// [
// { "OGTT3Y-C6I3P-XRI6HX": { "status": "closed" }},
// { "OGTT3Y-C6I3P-XRI6HX": { "status": "closed" }},
// ],
// "openOrders",
// { "sequence": 59342 }
// ]
//
const allOrders = this.safeValue(message, 0, []);
const allOrdersLength = allOrders.length;
if (allOrdersLength > 0) {
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
if (this.orders === undefined) {
this.orders = new Cache.ArrayCacheBySymbolById(limit);
}
const stored = this.orders;
const symbols = {};
for (let i = 0; i < allOrders.length; i++) {
const orders = this.safeValue(allOrders, i, {});
const ids = Object.keys(orders);
for (let j = 0; j < ids.length; j++) {
const id = ids[j];
const order = orders[id];
const parsed = this.parseWsOrder(order);
parsed['id'] = id;
let symbol = undefined;
const symbolsByOrderId = this.safeValue(this.options, 'symbolsByOrderId', {});
if (parsed['symbol'] !== undefined) {
symbol = parsed['symbol'];
symbolsByOrderId[id] = symbol;
this.options['symbolsByOrderId'] = symbolsByOrderId;
}