UNPKG

sfccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

1,152 lines (1,127 loc) 110 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { BadSymbol, BadRequest, ExchangeNotAvailable, ArgumentsRequired, NotSupported, PermissionDenied, AuthenticationError, ExchangeError, OrderNotFound, DDoSProtection, InvalidNonce, InsufficientFunds, CancelPending, InvalidOrder, InvalidAddress, RateLimitExceeded, OnMaintenance, AccountSuspended } = require ('./base/errors'); const { TRUNCATE, TICK_SIZE } = require ('./base/functions/number'); const Precise = require ('./base/Precise'); // --------------------------------------------------------------------------- module.exports = class kraken extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'kraken', 'name': 'Kraken', 'countries': [ 'US' ], 'version': '0', 'rateLimit': 1000, 'certified': false, 'pro': true, 'has': { 'CORS': undefined, 'spot': true, 'margin': true, 'swap': false, 'future': false, 'option': false, 'addMargin': false, 'cancelAllOrders': true, 'cancelOrder': true, 'cancelOrders': true, 'createDepositAddress': true, 'createOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'editOrder': true, 'fetchBalance': true, 'fetchBorrowInterest': false, 'fetchBorrowRate': false, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchBorrowRates': false, 'fetchClosedOrders': true, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDeposits': true, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': false, 'fetchFundingRates': false, 'fetchIndexOHLCV': false, 'fetchLedger': true, 'fetchLedgerEntry': true, 'fetchLeverageTiers': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenInterestHistory': false, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrderTrades': 'emulated', 'fetchPositions': true, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': true, 'fetchTrades': true, 'fetchTradingFee': true, 'fetchTradingFees': false, 'fetchWithdrawals': true, 'setLeverage': false, 'setMarginMode': false, // Kraken only supports cross margin 'withdraw': true, }, 'marketsByAltname': {}, 'timeframes': { '1m': 1, '5m': 5, '15m': 15, '30m': 30, '1h': 60, '4h': 240, '1d': 1440, '1w': 10080, '2w': 21600, }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg', 'api': { 'public': 'https://api.kraken.com', 'private': 'https://api.kraken.com', 'zendesk': 'https://kraken.zendesk.com/api/v2/help_center/en-us/articles', // use the public zendesk api to receive article bodies and bypass new anti-spam protections }, 'www': 'https://www.kraken.com', 'doc': 'https://www.kraken.com/features/api', 'fees': 'https://www.kraken.com/en-us/features/fee-schedule', }, 'fees': { 'trading': { 'tierBased': true, 'percentage': true, 'taker': this.parseNumber ('0.0026'), 'maker': this.parseNumber ('0.0016'), 'tiers': { 'taker': [ [ this.parseNumber ('0'), this.parseNumber ('0.0026') ], [ this.parseNumber ('50000'), this.parseNumber ('0.0024') ], [ this.parseNumber ('100000'), this.parseNumber ('0.0022') ], [ this.parseNumber ('250000'), this.parseNumber ('0.0020') ], [ this.parseNumber ('500000'), this.parseNumber ('0.0018') ], [ this.parseNumber ('1000000'), this.parseNumber ('0.0016') ], [ this.parseNumber ('2500000'), this.parseNumber ('0.0014') ], [ this.parseNumber ('5000000'), this.parseNumber ('0.0012') ], [ this.parseNumber ('10000000'), this.parseNumber ('0.0001') ], ], 'maker': [ [ this.parseNumber ('0'), this.parseNumber ('0.0016') ], [ this.parseNumber ('50000'), this.parseNumber ('0.0014') ], [ this.parseNumber ('100000'), this.parseNumber ('0.0012') ], [ this.parseNumber ('250000'), this.parseNumber ('0.0010') ], [ this.parseNumber ('500000'), this.parseNumber ('0.0008') ], [ this.parseNumber ('1000000'), this.parseNumber ('0.0006') ], [ this.parseNumber ('2500000'), this.parseNumber ('0.0004') ], [ this.parseNumber ('5000000'), this.parseNumber ('0.0002') ], [ this.parseNumber ('10000000'), this.parseNumber ('0.0') ], ], }, }, }, 'handleContentTypeApplicationZip': true, 'api': { 'zendesk': { 'get': [ // we should really refrain from putting fixed fee numbers and stop hardcoding // we will be using their web APIs to scrape all numbers from these articles '360000292886', // -What-are-the-deposit-fees- '201893608', // -What-are-the-withdrawal-fees- ], }, 'public': { 'get': { // public endpoint rate-limits are described in article: https://support.kraken.com/hc/en-us/articles/206548367-What-are-the-API-rate-limits-#1 'Assets': 1, 'AssetPairs': 1, 'Depth': 1, 'OHLC': 1, 'Spread': 1, 'Ticker': 1, 'Time': 1, 'Trades': 1, }, }, 'private': { 'post': { 'AddOrder': 0, 'AddOrderBatch': 0, 'AddExport': 3, 'Balance': 3, 'CancelAll': 3, 'CancelOrder': 0, 'CancelOrderBatch': 0, 'ClosedOrders': 6, 'DepositAddresses': 3, 'DepositMethods': 3, 'DepositStatus': 3, 'EditOrder': 0, 'ExportStatus': 3, 'GetWebSocketsToken': 3, 'Ledgers': 6, 'OpenOrders': 3, 'OpenPositions': 3, 'QueryLedgers': 3, 'QueryOrders': 3, 'QueryTrades': 3, 'RetrieveExport': 3, 'RemoveExport': 3, 'TradeBalance': 3, 'TradesHistory': 6, 'TradeVolume': 3, 'Withdraw': 3, 'WithdrawCancel': 3, 'WithdrawInfo': 3, 'WithdrawStatus': 3, // staking 'Stake': 3, 'Unstake': 3, 'Staking/Assets': 3, 'Staking/Pending': 3, 'Staking/Transactions': 3, }, }, }, 'commonCurrencies': { 'LUNA': 'LUNC', 'LUNA2': 'LUNA', 'REPV2': 'REP', 'REP': 'REPV1', 'UST': 'USTC', 'XBT': 'BTC', 'XBT.M': 'BTC.M', // https://support.kraken.com/hc/en-us/articles/360039879471-What-is-Asset-S-and-Asset-M- 'XDG': 'DOGE', }, 'options': { 'delistedMarketsById': {}, // cannot withdraw/deposit these 'inactiveCurrencies': [ 'CAD', 'USD', 'JPY', 'GBP' ], 'networks': { 'ETH': 'ERC20', 'TRX': 'TRC20', }, 'depositMethods': { '1INCH': '1inch (1INCH)', 'AAVE': 'Aave', 'ADA': 'ADA', 'ALGO': 'Algorand', 'ANKR': 'ANKR (ANKR)', 'ANT': 'Aragon (ANT)', 'ATOM': 'Cosmos', 'AXS': 'Axie Infinity Shards (AXS)', 'BADGER': 'Bager DAO (BADGER)', 'BAL': 'Balancer (BAL)', 'BAND': 'Band Protocol (BAND)', 'BAT': 'BAT', 'BCH': 'Bitcoin Cash', 'BNC': 'Bifrost (BNC)', 'BNT': 'Bancor (BNT)', 'BTC': 'Bitcoin', 'CHZ': 'Chiliz (CHZ)', 'COMP': 'Compound (COMP)', 'CQT': '\tCovalent Query Token (CQT)', 'CRV': 'Curve DAO Token (CRV)', 'CTSI': 'Cartesi (CTSI)', 'DAI': 'Dai', 'DASH': 'Dash', 'DOGE': 'Dogecoin', 'DOT': 'Polkadot', 'DYDX': 'dYdX (DYDX)', 'ENJ': 'Enjin Coin (ENJ)', 'EOS': 'EOS', 'ETC': 'Ether Classic (Hex)', 'ETH': 'Ether (Hex)', 'EWT': 'Energy Web Token', 'FEE': 'Kraken Fee Credit', 'FIL': 'Filecoin', 'FLOW': 'Flow', 'GHST': 'Aavegotchi (GHST)', 'GNO': 'GNO', 'GRT': 'GRT', 'ICX': 'Icon', 'INJ': 'Injective Protocol (INJ)', 'KAR': 'Karura (KAR)', 'KAVA': 'Kava', 'KEEP': 'Keep Token (KEEP)', 'KNC': 'Kyber Network (KNC)', 'KSM': 'Kusama', 'LINK': 'Link', 'LPT': 'Livepeer Token (LPT)', 'LRC': 'Loopring (LRC)', 'LSK': 'Lisk', 'LTC': 'Litecoin', 'MANA': 'MANA', 'MATIC': 'Polygon (MATIC)', 'MINA': 'Mina', // inspected from webui 'MIR': 'Mirror Protocol (MIR)', 'MKR': 'Maker (MKR)', 'MLN': 'MLN', 'MOVR': 'Moonriver (MOVR)', 'NANO': 'NANO', 'OCEAN': 'OCEAN', 'OGN': 'Origin Protocol (OGN)', 'OMG': 'OMG', 'OXT': 'Orchid (OXT)', 'OXY': 'Oxygen (OXY)', 'PAXG': 'PAX (Gold)', 'PERP': 'Perpetual Protocol (PERP)', 'PHA': 'Phala (PHA)', 'QTUM': 'QTUM', 'RARI': 'Rarible (RARI)', 'RAY': 'Raydium (RAY)', 'REN': 'Ren Protocol (REN)', 'REP': 'REPv2', 'REPV1': 'REP', 'SAND': 'The Sandbox (SAND)', 'SC': 'Siacoin', 'SDN': 'Shiden (SDN)', 'SOL': 'Solana', // their deposit method api doesn't work for SOL - was guessed 'SNX': 'Synthetix Network (SNX)', 'SRM': 'Serum', // inspected from webui 'STORJ': 'Storj (STORJ)', 'SUSHI': 'Sushiswap (SUSHI)', 'TBTC': 'tBTC', 'TRX': 'Tron', 'UNI': 'UNI', 'USDC': 'USDC', 'USDT': 'Tether USD (ERC20)', 'USDT-TRC20': 'Tether USD (TRC20)', 'WAVES': 'Waves', 'WBTC': 'Wrapped Bitcoin (WBTC)', 'XLM': 'Stellar XLM', 'XMR': 'Monero', 'XRP': 'Ripple XRP', 'XTZ': 'XTZ', 'YFI': 'YFI', 'ZEC': 'Zcash (Transparent)', 'ZRX': '0x (ZRX)', }, }, 'precisionMode': TICK_SIZE, 'exceptions': { 'EQuery:Invalid asset pair': BadSymbol, // {"error":["EQuery:Invalid asset pair"]} 'EAPI:Invalid key': AuthenticationError, 'EFunding:Unknown withdraw key': InvalidAddress, // {"error":["EFunding:Unknown withdraw key"]} 'EFunding:Invalid amount': InsufficientFunds, 'EService:Unavailable': ExchangeNotAvailable, 'EDatabase:Internal error': ExchangeNotAvailable, 'EService:Busy': ExchangeNotAvailable, 'EQuery:Unknown asset': BadSymbol, // {"error":["EQuery:Unknown asset"]} 'EAPI:Rate limit exceeded': DDoSProtection, 'EOrder:Rate limit exceeded': DDoSProtection, 'EGeneral:Internal error': ExchangeNotAvailable, 'EGeneral:Temporary lockout': DDoSProtection, 'EGeneral:Permission denied': PermissionDenied, 'EOrder:Unknown order': InvalidOrder, 'EOrder:Order minimum not met': InvalidOrder, 'EGeneral:Invalid arguments': BadRequest, 'ESession:Invalid session': AuthenticationError, 'EAPI:Invalid nonce': InvalidNonce, 'EFunding:No funding method': BadRequest, // {"error":"EFunding:No funding method"} 'EFunding:Unknown asset': BadSymbol, // {"error":["EFunding:Unknown asset"]} 'EService:Market in post_only mode': OnMaintenance, // {"error":["EService:Market in post_only mode"]} 'EGeneral:Too many requests': DDoSProtection, // {"error":["EGeneral:Too many requests"]} 'ETrade:User Locked': AccountSuspended, // {"error":["ETrade:User Locked"]} }, }); } feeToPrecision (symbol, fee) { return this.decimalToPrecision (fee, TRUNCATE, this.markets[symbol]['precision']['amount'], this.precisionMode); } async fetchMarkets (params = {}) { /** * @method * @name kraken#fetchMarkets * @description retrieves data on all markets for kraken * @param {object} params extra parameters specific to the exchange api endpoint * @returns {[object]} an array of objects representing market data */ const response = await this.publicGetAssetPairs (params); // // { // "error": [], // "result": { // "ADAETH": { // "altname": "ADAETH", // "wsname": "ADA\/ETH", // "aclass_base": "currency", // "base": "ADA", // "aclass_quote": "currency", // "quote": "XETH", // "lot": "unit", // "pair_decimals": 7, // "lot_decimals": 8, // "lot_multiplier": 1, // "leverage_buy": [], // "leverage_sell": [], // "fees": [ // [0, 0.26], // [50000, 0.24], // [100000, 0.22], // [250000, 0.2], // [500000, 0.18], // [1000000, 0.16], // [2500000, 0.14], // [5000000, 0.12], // [10000000, 0.1] // ], // "fees_maker": [ // [0, 0.16], // [50000, 0.14], // [100000, 0.12], // [250000, 0.1], // [500000, 0.08], // [1000000, 0.06], // [2500000, 0.04], // [5000000, 0.02], // [10000000, 0] // ], // "fee_volume_currency": "ZUSD", // "margin_call": 80, // "margin_stop": 40, // "ordermin": "1" // }, // } // } // const markets = this.safeValue (response, 'result', {}); const keys = Object.keys (markets); let result = []; for (let i = 0; i < keys.length; i++) { const id = keys[i]; const market = markets[id]; const baseId = this.safeString (market, 'base'); const quoteId = this.safeString (market, 'quote'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const darkpool = id.indexOf ('.d') >= 0; const altname = this.safeString (market, 'altname'); const makerFees = this.safeValue (market, 'fees_maker', []); const firstMakerFee = this.safeValue (makerFees, 0, []); const firstMakerFeeRate = this.safeString (firstMakerFee, 1); let maker = undefined; if (firstMakerFeeRate !== undefined) { maker = this.parseNumber (Precise.stringDiv (firstMakerFeeRate, '100')); } const takerFees = this.safeValue (market, 'fees', []); const firstTakerFee = this.safeValue (takerFees, 0, []); const firstTakerFeeRate = this.safeString (firstTakerFee, 1); let taker = undefined; if (firstTakerFeeRate !== undefined) { taker = this.parseNumber (Precise.stringDiv (firstTakerFeeRate, '100')); } const leverageBuy = this.safeValue (market, 'leverage_buy', []); const leverageBuyLength = leverageBuy.length; const precisionPrice = this.parseNumber (this.parsePrecision (this.safeString (market, 'pair_decimals'))); result.push ({ 'id': id, 'symbol': darkpool ? altname : (base + '/' + quote), 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'darkpool': darkpool, 'altname': market['altname'], 'type': 'spot', 'spot': true, 'margin': (leverageBuyLength > 0), 'swap': false, 'future': false, 'option': false, 'active': true, 'contract': false, 'linear': undefined, 'inverse': undefined, 'taker': taker, 'maker': maker, 'contractSize': undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': this.parseNumber (this.parsePrecision (this.safeString (market, 'lot_decimals'))), 'price': precisionPrice, }, 'limits': { 'leverage': { 'min': this.parseNumber ('1'), 'max': this.safeNumber (leverageBuy, leverageBuyLength - 1, 1), }, 'amount': { 'min': this.safeNumber (market, 'ordermin'), 'max': undefined, }, 'price': { 'min': precisionPrice, 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'info': market, }); } result = this.appendInactiveMarkets (result); this.marketsByAltname = this.indexBy (result, 'altname'); return result; } safeCurrency (currencyId, currency = undefined) { if (currencyId !== undefined) { if (currencyId.length > 3) { if ((currencyId.indexOf ('X') === 0) || (currencyId.indexOf ('Z') === 0)) { if (currencyId.indexOf ('.') > 0) { return super.safeCurrency (currencyId, currency); } else { currencyId = currencyId.slice (1); } } } } return super.safeCurrency (currencyId, currency); } appendInactiveMarkets (result) { // result should be an array to append to const precision = { 'amount': this.parseNumber ('1e-8'), 'price': this.parseNumber ('1e-8'), }; const costLimits = { 'min': undefined, 'max': undefined }; const priceLimits = { 'min': precision['price'], 'max': undefined }; const amountLimits = { 'min': precision['amount'], 'max': undefined }; const limits = { 'amount': amountLimits, 'price': priceLimits, 'cost': costLimits }; const defaults = { 'darkpool': false, 'info': undefined, 'maker': undefined, 'taker': undefined, 'active': false, 'precision': precision, 'limits': limits, }; const markets = [ // { 'id': 'XXLMZEUR', 'symbol': 'XLM/EUR', 'base': 'XLM', 'quote': 'EUR', 'altname': 'XLMEUR' }, ]; for (let i = 0; i < markets.length; i++) { result.push (this.extend (defaults, markets[i])); } return result; } async fetchCurrencies (params = {}) { /** * @method * @name kraken#fetchCurrencies * @description fetches all available currencies on an exchange * @param {object} params extra parameters specific to the kraken api endpoint * @returns {object} an associative dictionary of currencies */ const response = await this.publicGetAssets (params); // // { // "error": [], // "result": { // "ADA": { "aclass": "currency", "altname": "ADA", "decimals": 8, "display_decimals": 6 }, // "BCH": { "aclass": "currency", "altname": "BCH", "decimals": 10, "display_decimals": 5 }, // ... // }, // } // const currencies = this.safeValue (response, 'result', {}); const ids = Object.keys (currencies); const result = {}; for (let i = 0; i < ids.length; i++) { const id = ids[i]; const currency = currencies[id]; // todo: will need to rethink the fees // see: https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees- // to add support for multiple withdrawal/deposit methods and // differentiated fees for each particular method const code = this.safeCurrencyCode (this.safeString (currency, 'altname')); const precision = this.parseNumber (this.parsePrecision (this.safeString (currency, 'decimals'))); // assumes all currencies are active except those listed above const active = !this.inArray (code, this.options['inactiveCurrencies']); result[code] = { 'id': id, 'code': code, 'info': currency, 'name': code, 'active': active, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'precision': precision, 'limits': { 'amount': { 'min': precision, 'max': undefined, }, 'withdraw': { 'min': undefined, 'max': undefined, }, }, }; } return result; } async fetchTradingFee (symbol, params = {}) { /** * @method * @name kraken#fetchTradingFee * @description fetch the trading fees for a market * @param {string} symbol unified market symbol * @param {object} params extra parameters specific to the kraken api endpoint * @returns {object} a [fee structure]{@link https://docs.ccxt.com/en/latest/manual.html#fee-structure} */ await this.loadMarkets (); const market = this.market (symbol); const request = { 'pair': market['id'], 'fee-info': true, }; const response = await this.privatePostTradeVolume (this.extend (request, params)); // // { // error: [], // result: { // currency: 'ZUSD', // volume: '0.0000', // fees: { // XXBTZUSD: { // fee: '0.2600', // minfee: '0.1000', // maxfee: '0.2600', // nextfee: '0.2400', // tiervolume: '0.0000', // nextvolume: '50000.0000' // } // }, // fees_maker: { // XXBTZUSD: { // fee: '0.1600', // minfee: '0.0000', // maxfee: '0.1600', // nextfee: '0.1400', // tiervolume: '0.0000', // nextvolume: '50000.0000' // } // } // } // } // const result = this.safeValue (response, 'result', {}); return this.parseTradingFee (result, market); } parseTradingFee (response, market) { const makerFees = this.safeValue (response, 'fees_maker', {}); const takerFees = this.safeValue (response, 'fees', {}); const symbolMakerFee = this.safeValue (makerFees, market['id'], {}); const symbolTakerFee = this.safeValue (takerFees, market['id'], {}); return { 'info': response, 'symbol': market['symbol'], 'maker': this.safeNumber (symbolMakerFee, 'fee'), 'taker': this.safeNumber (symbolTakerFee, 'fee'), 'percentage': true, 'tierBased': true, }; } parseBidAsk (bidask, priceKey = 0, amountKey = 1) { const price = this.safeNumber (bidask, priceKey); const amount = this.safeNumber (bidask, amountKey); const timestamp = this.safeInteger (bidask, 2); return [ price, amount, timestamp ]; } async fetchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name kraken#fetchOrderBook * @description fetches 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/en/latest/manual.html#order-book-structure} indexed by market symbols */ await this.loadMarkets (); const market = this.market (symbol); if (market['darkpool']) { throw new ExchangeError (this.id + ' fetchOrderBook() does not provide an order book for darkpool symbol ' + symbol); } const request = { 'pair': market['id'], }; if (limit !== undefined) { request['count'] = limit; // 100 } const response = await this.publicGetDepth (this.extend (request, params)); // // { // "error":[], // "result":{ // "XETHXXBT":{ // "asks":[ // ["0.023480","4.000",1586321307], // ["0.023490","50.095",1586321306], // ["0.023500","28.535",1586321302], // ], // "bids":[ // ["0.023470","59.580",1586321307], // ["0.023460","20.000",1586321301], // ["0.023440","67.832",1586321306], // ] // } // } // } // const result = this.safeValue (response, 'result', {}); let orderbook = this.safeValue (result, market['id']); // sometimes kraken returns wsname instead of market id // https://github.com/ccxt/ccxt/issues/8662 const marketInfo = this.safeValue (market, 'info', {}); const wsName = this.safeValue (marketInfo, 'wsname'); if (wsName !== undefined) { orderbook = this.safeValue (result, wsName, orderbook); } return this.parseOrderBook (orderbook, symbol); } parseTicker (ticker, market = undefined) { // // { // "a":["2432.77000","1","1.000"], // "b":["2431.37000","2","2.000"], // "c":["2430.58000","0.04408910"], // "v":["4147.94474901","8896.96086304"], // "p":["2456.22239","2568.63032"], // "t":[3907,10056], // "l":["2302.18000","2302.18000"], // "h":["2621.14000","2860.01000"], // "o":"2571.56000" // } // const timestamp = this.milliseconds (); const symbol = this.safeSymbol (undefined, market); const v = this.safeValue (ticker, 'v', []); const baseVolume = this.safeString (v, 1); const p = this.safeValue (ticker, 'p', []); const vwap = this.safeString (p, 1); const quoteVolume = Precise.stringMul (baseVolume, vwap); const c = this.safeValue (ticker, 'c', []); const last = this.safeString (c, 0); const high = this.safeValue (ticker, 'h', []); const low = this.safeValue (ticker, 'l', []); const bid = this.safeValue (ticker, 'b', []); const ask = this.safeValue (ticker, 'a', []); return this.safeTicker ({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeString (high, 1), 'low': this.safeString (low, 1), 'bid': this.safeString (bid, 0), 'bidVolume': undefined, 'ask': this.safeString (ask, 0), 'askVolume': undefined, 'vwap': vwap, 'open': this.safeString (ticker, 'o'), 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }, market); } async fetchTickers (symbols = undefined, params = {}) { /** * @method * @name kraken#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @param {[string]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned * @param {object} params extra parameters specific to the kraken api endpoint * @returns {object} an array of [ticker structures]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const request = {}; if (symbols !== undefined) { symbols = this.marketSymbols (symbols); const marketIds = []; for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; const market = this.markets[symbol]; if (market['active'] && !market['darkpool']) { marketIds.push (market['id']); } } request['pair'] = marketIds.join (','); } const response = await this.publicGetTicker (this.extend (request, params)); const tickers = response['result']; const ids = Object.keys (tickers); const result = {}; for (let i = 0; i < ids.length; i++) { const id = ids[i]; const market = this.safeMarket (id); const symbol = market['symbol']; const ticker = tickers[id]; result[symbol] = this.parseTicker (ticker, market); } return this.filterByArray (result, 'symbol', symbols); } async fetchTicker (symbol, params = {}) { /** * @method * @name kraken#fetchTicker * @description fetches 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/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const darkpool = symbol.indexOf ('.d') >= 0; if (darkpool) { throw new ExchangeError (this.id + ' fetchTicker() does not provide a ticker for darkpool symbol ' + symbol); } const market = this.market (symbol); const request = { 'pair': market['id'], }; const response = await this.publicGetTicker (this.extend (request, params)); const ticker = response['result'][market['id']]; return this.parseTicker (ticker, market); } parseOHLCV (ohlcv, market = undefined) { // // [ // 1591475640, // "0.02500", // "0.02500", // "0.02500", // "0.02500", // "0.02500", // "9.12201000", // 5 // ] // return [ this.safeTimestamp (ohlcv, 0), this.safeNumber (ohlcv, 1), this.safeNumber (ohlcv, 2), this.safeNumber (ohlcv, 3), this.safeNumber (ohlcv, 4), this.safeNumber (ohlcv, 6), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name kraken#fetchOHLCV * @description fetches 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 market = this.market (symbol); const request = { 'pair': market['id'], 'interval': this.timeframes[timeframe], }; if (since !== undefined) { request['since'] = parseInt ((since - 1) / 1000); } const response = await this.publicGetOHLC (this.extend (request, params)); // // { // "error":[], // "result":{ // "XETHXXBT":[ // [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0], // [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5], // [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2], // [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1], // ], // "last":1591517580 // } // } const result = this.safeValue (response, 'result', {}); const ohlcvs = this.safeValue (result, market['id'], []); return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); } parseLedgerEntryType (type) { const types = { 'trade': 'trade', 'withdrawal': 'transaction', 'deposit': 'transaction', 'transfer': 'transfer', 'margin': 'margin', }; return this.safeString (types, type, type); } parseLedgerEntry (item, currency = undefined) { // // { // 'LTFK7F-N2CUX-PNY4SX': { // refid: "TSJTGT-DT7WN-GPPQMJ", // time: 1520102320.555, // type: "trade", // aclass: "currency", // asset: "XETH", // amount: "0.1087194600", // fee: "0.0000000000", // balance: "0.2855851000" // }, // ... // } // const id = this.safeString (item, 'id'); let direction = undefined; const account = undefined; const referenceId = this.safeString (item, 'refid'); const referenceAccount = undefined; const type = this.parseLedgerEntryType (this.safeString (item, 'type')); const code = this.safeCurrencyCode (this.safeString (item, 'asset'), currency); let amount = this.safeString (item, 'amount'); if (Precise.stringLt (amount, '0')) { direction = 'out'; amount = Precise.stringAbs (amount); } else { direction = 'in'; } const time = this.safeNumber (item, 'time'); let timestamp = undefined; if (time !== undefined) { timestamp = parseInt (time * 1000); } return { 'info': item, 'id': id, 'direction': direction, 'account': account, 'referenceId': referenceId, 'referenceAccount': referenceAccount, 'type': type, 'currency': code, 'amount': this.parseNumber (amount), 'before': undefined, 'after': this.safeNumber (item, 'balance'), 'status': 'ok', 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'fee': { 'cost': this.safeNumber (item, 'fee'), 'currency': code, }, }; } async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name kraken#fetchLedger * @description fetch the history of changes, actions done by the user or operations that altered balance of the user * @param {string|undefined} code unified currency code, default is undefined * @param {int|undefined} since timestamp in ms of the earliest ledger entry, default is undefined * @param {int|undefined} limit max number of ledger entrys to return, default is undefined * @param {object} params extra parameters specific to the kraken api endpoint * @returns {object} a [ledger structure]{@link https://docs.ccxt.com/en/latest/manual.html#ledger-structure} */ // https://www.kraken.com/features/api#get-ledgers-info await this.loadMarkets (); const request = {}; let currency = undefined; if (code !== undefined) { currency = this.currency (code); request['asset'] = currency['id']; } if (since !== undefined) { request['start'] = parseInt (since / 1000); } const response = await this.privatePostLedgers (this.extend (request, params)); // { error: [], // result: { ledger: { 'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N", // time: 1520103488.314, // type: "withdrawal", // aclass: "currency", // asset: "XETH", // amount: "-0.2805800000", // fee: "0.0050000000", // balance: "0.0000051000" }, const result = this.safeValue (response, 'result', {}); const ledger = this.safeValue (result, 'ledger', {}); const keys = Object.keys (ledger); const items = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = ledger[key]; value['id'] = key; items.push (value); } return this.parseLedger (items, currency, since, limit); } async fetchLedgerEntriesByIds (ids, code = undefined, params = {}) { // https://www.kraken.com/features/api#query-ledgers await this.loadMarkets (); ids = ids.join (','); const request = this.extend ({ 'id': ids, }, params); const response = await this.privatePostQueryLedgers (request); // { error: [], // result: { 'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N", // time: 1520103488.314, // type: "withdrawal", // aclass: "currency", // asset: "XETH", // amount: "-0.2805800000", // fee: "0.0050000000", // balance: "0.0000051000" } } } const result = response['result']; const keys = Object.keys (result); const items = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = result[key]; value['id'] = key; items.push (value); } return this.parseLedger (items); } async fetchLedgerEntry (id, code = undefined, params = {}) { const items = await this.fetchLedgerEntriesByIds ([ id ], code, params); return items[0]; } parseTrade (trade, market = undefined) { // // fetchTrades (public) // // [ // "0.032310", // price // "4.28169434", // amount // 1541390792.763, // timestamp // "s", // sell or buy // "l", // limit or market // "" // ] // // fetchOrderTrades (private) // // { // 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: '' // } // let timestamp = undefined; let side = undefined; let type = undefined; let price = undefined; let amount = undefined; let id = undefined; let orderId = undefined; let fee = undefined; let symbol = undefined; if (Array.isArray (trade)) { timestamp = this.safeTimestamp (trade, 2); side = (trade[3] === 's') ? 'sell' : 'buy'; type = (trade[4] === 'l') ? 'limit' : 'market'; price = this.safeString (trade, 0); amount = this.safeString (trade, 1); const tradeLength = trade.length; if (tradeLength > 6) { id = this.safeString (trade, 6); // artificially added as per #1794 } } else if (typeof trade === 'string') { id = trade; } else if ('ordertxid' in trade) { const marketId = this.safeString (trade, 'pair'); const foundMarket = this.findMarketByAltnameOrId (marketId); if (foundMarket !== undefined) { market = foundMarket; } else if (marketId !== undefined) { // delisted market ids go here market = this.getDelistedMarketById (marketId); } orderId = this.safeString (trade, 'ordertxid'); id = this.safeString2 (trade, 'id', 'postxid'); timestamp = this.safeTimestamp (trade, 'time'); side = this.safeString (trade, 'type'); type = this.safeString (trade, 'ordertype'); price = this.safeString (trade, 'price'); amount = this.safeString (trade, 'vol'); if ('fee' in trade) { let currency = undefined; if (market !== undefined) { currency = market['quote']; } fee = { 'cost': this.safeString (trade, 'fee'), 'currency': currency, }; } } if (market !== undefined) { symbol = market['symbol']; } const cost = this.safeString (trade, 'cost'); return this.safeTrade ({ '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, }, market); } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name kraken#fetchTrades * @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 (); const market = this.market (symbol); const id = market['id']; const request = { 'pair': id, }; // https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API // https://github.com