UNPKG

ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges

1,129 lines (1,127 loc) • 153 kB
'use strict'; var kraken$1 = require('./abstract/kraken.js'); var errors = require('./base/errors.js'); var Precise = require('./base/Precise.js'); var number = require('./base/functions/number.js'); var sha256 = require('./static_dependencies/noble-hashes/sha256.js'); var sha512 = require('./static_dependencies/noble-hashes/sha512.js'); // ---------------------------------------------------------------------------- // --------------------------------------------------------------------------- /** * @class kraken * @augments Exchange * @description Set rateLimit to 1000 if fully verified */ class kraken extends kraken$1 { describe() { return this.deepExtend(super.describe(), { 'id': 'kraken', 'name': 'Kraken', 'countries': ['US'], 'version': '0', // rate-limits: https://support.kraken.com/hc/en-us/articles/206548367-What-are-the-API-rate-limits-#1 // for public: 1 req/s // for private: every second 0.33 weight added to your allowed capacity (some private endpoints need 1 weight, some need 2) 'rateLimit': 1000, 'certified': false, 'pro': true, 'has': { 'CORS': undefined, 'spot': true, 'margin': true, 'swap': false, 'future': false, 'option': false, 'addMargin': false, 'cancelAllOrders': true, 'cancelAllOrdersAfter': true, 'cancelOrder': true, 'cancelOrders': true, 'createDepositAddress': true, 'createMarketBuyOrderWithCost': true, 'createMarketOrderWithCost': false, 'createMarketSellOrderWithCost': false, 'createOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'createTrailingAmountOrder': true, 'createTrailingPercentOrder': true, 'editOrder': true, 'fetchBalance': true, 'fetchBorrowInterest': false, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchClosedOrders': true, 'fetchCrossBorrowRate': false, 'fetchCrossBorrowRates': false, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDepositAddresses': false, 'fetchDepositAddressesByNetwork': false, 'fetchDeposits': true, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': false, 'fetchFundingRates': false, 'fetchIndexOHLCV': false, 'fetchIsolatedBorrowRate': false, 'fetchIsolatedBorrowRates': 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, 'fetchStatus': true, 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': true, 'fetchTrades': true, 'fetchTradingFee': true, 'fetchTradingFees': false, 'fetchWithdrawals': true, 'setLeverage': false, 'setMarginMode': false, 'transfer': true, 'withdraw': true, }, '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://docs.kraken.com/rest/', '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', '201893608', // -What-are-the-withdrawal-fees- ], }, 'public': { 'get': { // rate-limits explained in comment in the top of this file 'Assets': 1, 'AssetPairs': 1, 'Depth': 1.2, 'OHLC': 1.2, 'Spread': 1, 'SystemStatus': 1, 'Ticker': 1, 'Time': 1, 'Trades': 1.2, }, }, 'private': { 'post': { 'AddOrder': 0, 'AddOrderBatch': 0, 'AddExport': 3, 'AmendOrder': 0, 'Balance': 3, 'CancelAll': 3, 'CancelAllOrdersAfter': 3, 'CancelOrder': 0, 'CancelOrderBatch': 0, 'ClosedOrders': 3, '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, 'BalanceEx': 3, 'TradeBalance': 3, 'TradesHistory': 6, 'TradeVolume': 3, 'Withdraw': 3, 'WithdrawCancel': 3, 'WithdrawInfo': 3, 'WithdrawMethods': 3, 'WithdrawAddresses': 3, 'WithdrawStatus': 3, 'WalletTransfer': 3, // sub accounts 'CreateSubaccount': 3, 'AccountTransfer': 3, // earn 'Earn/Allocate': 3, 'Earn/Deallocate': 3, 'Earn/AllocateStatus': 3, 'Earn/DeallocateStatus': 3, 'Earn/Strategies': 3, 'Earn/Allocations': 3, }, }, }, 'commonCurrencies': { 'LUNA': 'LUNC', 'LUNA2': 'LUNA', 'REPV2': 'REP', 'REP': 'REPV1', 'UST': 'USTC', 'XBT': 'BTC', 'XBT.M': 'BTC.M', 'XDG': 'DOGE', }, 'options': { 'timeDifference': 0, 'adjustForTimeDifference': false, 'marketsByAltname': {}, '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', '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', 'SNX': 'Synthetix Network' + ' ' + '(SNX)', 'SRM': 'Serum', '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)', }, 'withdrawMethods': { 'Lightning': 'Lightning', 'Bitcoin': 'BTC', 'Ripple': 'XRP', 'Litecoin': 'LTC', 'Dogecoin': 'DOGE', 'Stellar': 'XLM', 'Ethereum': 'ERC20', 'Arbitrum One': 'Arbitrum', 'Polygon': 'MATIC', 'Arbitrum Nova': 'Arbitrum', 'Optimism': 'Optimism', 'zkSync Era': 'zkSync', 'Ethereum Classic': 'ETC', 'Zcash': 'ZEC', 'Monero': 'XMR', 'Tron': 'TRC20', 'Solana': 'SOL', 'EOS': 'EOS', 'Bitcoin Cash': 'BCH', 'Cardano': 'ADA', 'Qtum': 'QTUM', 'Tezos': 'XTZ', 'Cosmos': 'ATOM', 'Nano': 'NANO', 'Siacoin': 'SC', 'Lisk': 'LSK', 'Waves': 'WAVES', 'ICON': 'ICX', 'Algorand': 'ALGO', 'Polygon - USDC.e': 'MATIC', 'Arbitrum One - USDC.e': 'Arbitrum', 'Polkadot': 'DOT', 'Kava': 'KAVA', 'Filecoin': 'FIL', 'Kusama': 'KSM', 'Flow': 'FLOW', 'Energy Web': 'EW', 'Mina': 'MINA', 'Centrifuge': 'CFG', 'Karura': 'KAR', 'Moonriver': 'MOVR', 'Shiden': 'SDN', 'Khala': 'PHA', 'Bifrost Kusama': 'BNC', 'Songbird': 'SGB', 'Terra classic': 'LUNC', 'KILT': 'KILT', 'Basilisk': 'BSX', 'Flare': 'FLR', 'Avalanche C-Chain': 'AVAX', 'Kintsugi': 'KINT', 'Altair': 'AIR', 'Moonbeam': 'GLMR', 'Acala': 'ACA', 'Astar': 'ASTR', 'Akash': 'AKT', 'Robonomics': 'XRT', 'Fantom': 'FTM', 'Elrond': 'EGLD', 'THORchain': 'RUNE', 'Secret': 'SCRT', 'Near': 'NEAR', 'Internet Computer Protocol': 'ICP', 'Picasso': 'PICA', 'Crust Shadow': 'CSM', 'Integritee': 'TEER', 'Parallel Finance': 'PARA', 'HydraDX': 'HDX', 'Interlay': 'INTR', 'Fetch.ai': 'FET', 'NYM': 'NYM', 'Terra 2.0': 'LUNA2', 'Juno': 'JUNO', 'Nodle': 'NODL', 'Stacks': 'STX', 'Ethereum PoW': 'ETHW', 'Aptos': 'APT', 'Sui': 'SUI', 'Genshiro': 'GENS', 'Aventus': 'AVT', 'Sei': 'SEI', 'OriginTrail': 'OTP', 'Celestia': 'TIA', }, }, 'features': { 'spot': { 'sandbox': false, 'createOrder': { 'marginMode': false, 'triggerPrice': false, 'triggerPriceType': undefined, 'triggerDirection': false, 'stopLossPrice': true, 'takeProfitPrice': true, 'attachedStopLossTakeProfit': undefined, 'timeInForce': { 'IOC': true, 'FOK': true, 'PO': true, 'GTD': false, }, 'hedged': false, 'trailing': true, 'leverage': false, 'marketBuyByCost': true, 'marketBuyRequiresPrice': false, 'selfTradePrevention': true, 'iceberg': true, // todo implement }, 'createOrders': undefined, 'fetchMyTrades': { 'marginMode': false, 'limit': undefined, 'daysBack': undefined, 'untilDays': undefined, 'symbolRequired': false, }, 'fetchOrder': { 'marginMode': false, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOpenOrders': { 'marginMode': false, 'limit': undefined, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOrders': undefined, 'fetchClosedOrders': { 'marginMode': false, 'limit': undefined, 'daysBack': undefined, 'daysBackCanceled': undefined, 'untilDays': 100000, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOHLCV': { 'limit': 720, }, }, 'swap': { 'linear': undefined, 'inverse': undefined, }, 'future': { 'linear': undefined, 'inverse': undefined, }, }, 'precisionMode': number.TICK_SIZE, 'exceptions': { 'exact': { 'EQuery:Invalid asset pair': errors.BadSymbol, 'EAPI:Invalid key': errors.AuthenticationError, 'EFunding:Unknown withdraw key': errors.InvalidAddress, 'EFunding:Invalid amount': errors.InsufficientFunds, 'EService:Unavailable': errors.ExchangeNotAvailable, 'EDatabase:Internal error': errors.ExchangeNotAvailable, 'EService:Busy': errors.ExchangeNotAvailable, 'EQuery:Unknown asset': errors.BadSymbol, 'EAPI:Rate limit exceeded': errors.DDoSProtection, 'EOrder:Rate limit exceeded': errors.DDoSProtection, 'EGeneral:Internal error': errors.ExchangeNotAvailable, 'EGeneral:Temporary lockout': errors.DDoSProtection, 'EGeneral:Permission denied': errors.PermissionDenied, 'EGeneral:Invalid arguments:price': errors.InvalidOrder, 'EOrder:Unknown order': errors.InvalidOrder, 'EOrder:Invalid price:Invalid price argument': errors.InvalidOrder, 'EOrder:Order minimum not met': errors.InvalidOrder, 'EOrder:Insufficient funds': errors.InsufficientFunds, 'EGeneral:Invalid arguments': errors.BadRequest, 'ESession:Invalid session': errors.AuthenticationError, 'EAPI:Invalid nonce': errors.InvalidNonce, 'EFunding:No funding method': errors.BadRequest, 'EFunding:Unknown asset': errors.BadSymbol, 'EService:Market in post_only mode': errors.OnMaintenance, 'EGeneral:Too many requests': errors.DDoSProtection, 'ETrade:User Locked': errors.AccountSuspended, // {"error":["ETrade:User Locked"]} }, 'broad': { ':Invalid order': errors.InvalidOrder, ':Invalid arguments:volume': errors.InvalidOrder, ':Invalid arguments:viqc': errors.InvalidOrder, ':Invalid nonce': errors.InvalidNonce, ':IInsufficient funds': errors.InsufficientFunds, ':Cancel pending': errors.CancelPending, ':Rate limit exceeded': errors.RateLimitExceeded, }, }, }); } feeToPrecision(symbol, fee) { return this.decimalToPrecision(fee, number.TRUNCATE, this.markets[symbol]['precision']['amount'], this.precisionMode); } /** * @method * @name kraken#fetchMarkets * @description retrieves data on all markets for kraken * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTradableAssetPairs * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} an array of objects representing market data */ async fetchMarkets(params = {}) { if (this.options['adjustForTimeDifference']) { await this.loadTimeDifference(); } 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["default"].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["default"].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'))); const status = this.safeString(market, 'status'); const isActive = status === 'online'; result.push({ 'id': id, 'wsId': this.safeString(market, 'wsname'), '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': isActive, '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': this.safeNumber(market, 'costmin'), 'max': undefined, }, }, 'created': undefined, 'info': market, }); } result = this.appendInactiveMarkets(result); this.options['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) && (currencyId !== 'ZEUS')) { 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; } /** * @method * @name kraken#fetchStatus * @description the latest known information on the availability of the exchange API * @see https://docs.kraken.com/api/docs/rest-api/get-system-status/ * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [status structure]{@link https://docs.ccxt.com/#/?id=exchange-status-structure} */ async fetchStatus(params = {}) { const response = await this.publicGetSystemStatus(params); // // { // error: [], // result: { status: 'online', timestamp: '2024-07-22T16:34:44Z' } // } // const result = this.safeDict(response, 'result'); const statusRaw = this.safeString(result, 'status'); return { 'status': (statusRaw === 'online') ? 'ok' : 'maintenance', 'updated': undefined, 'eta': undefined, 'url': undefined, 'info': response, }; } /** * @method * @name kraken#fetchCurrencies * @description fetches all available currencies on an exchange * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getAssetInfo * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an associative dictionary of currencies */ async fetchCurrencies(params = {}) { const response = await this.publicGetAssets(params); // // { // "error": [], // "result": { // "BCH": { // "aclass": "currency", // "altname": "BCH", // "decimals": 10, // "display_decimals": 5 // "status": "enabled", // }, // ... // }, // } // 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(id); const precision = this.parseNumber(this.parsePrecision(this.safeString(currency, 'decimals'))); // assumes all currencies are active except those listed above const active = this.safeString(currency, 'status') === 'enabled'; result[code] = { 'id': id, 'code': code, 'info': currency, 'name': this.safeString(currency, 'altname'), 'active': active, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'precision': precision, 'limits': { 'amount': { 'min': precision, 'max': undefined, }, 'withdraw': { 'min': undefined, 'max': undefined, }, }, 'networks': {}, }; } return result; } /** * @method * @name kraken#fetchTradingFee * @description fetch the trading fees for a market * @see https://docs.kraken.com/rest/#tag/Account-Data/operation/getTradeVolume * @param {string} symbol unified market symbol * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [fee structure]{@link https://docs.ccxt.com/#/?id=fee-structure} */ async fetchTradingFee(symbol, params = {}) { 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.parseNumber(Precise["default"].stringDiv(this.safeString(symbolMakerFee, 'fee'), '100')), 'taker': this.parseNumber(Precise["default"].stringDiv(this.safeString(symbolTakerFee, 'fee'), '100')), 'percentage': true, 'tierBased': true, }; } parseBidAsk(bidask, priceKey = 0, amountKey = 1, countOrIdKey = 2) { const price = this.safeNumber(bidask, priceKey); const amount = this.safeNumber(bidask, amountKey); const timestamp = this.safeInteger(bidask, 2); return [price, amount, timestamp]; } /** * @method * @name kraken#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getOrderBook * @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 fetchOrderBook(symbol, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); if (market['darkpool']) { throw new errors.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 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["default"].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': undefined, 'datetime': undefined, '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); } /** * @method * @name kraken#fetchTickers * @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTickerInformation * @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 exchange API endpoint * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async fetchTickers(symbols = undefined, params = {}) { 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.filterByArrayTickers(result, 'symbol', symbols); } /** * @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 * @see https://docs.kraken.com/rest/#tag/Spot-Market-Data/operation/getTickerInformation * @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 fetchTicker(symbol, params = {}) { await this.loadMarkets(); const darkpool = symbol.indexOf('.d') >= 0; if (darkpool) { throw new errors.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), ]; } /** * @method * @name kraken#fetchOHLCV * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://docs.kraken.com/api/docs/rest-api/get-ohlc-data * @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 * @param {boolean} [params.paginate] default false, when true will automatically paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); let paginate = false; [paginate, params] = this.handleOptionAndParams(params, 'fetchOHLCV', 'paginate'); if (paginate) { return await this.fetchPaginatedCallDeterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 720); } const market = this.market(symbol); const parsedTimeframe = this.safeInteger(this.timeframes, timeframe); const request = { 'pair': market['id'], }; if (parsedTimeframe !== undefined) { request['interval'] = parsedTimeframe; } else { request['interval'] = timeframe; } if (since !== undefined) { const scaledSince = this.parseToInt(since / 1000); const timeFrameInSeconds = parsedTimeframe * 60; request['since'] = this.numberToString(scaledSince - timeFrameInSeconds); // expected to be in seconds } 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.safeList(result, market['id'], []); return this.parseOHLCVs(ohlcvs, market, timeframe, since, l