UNPKG

ccxt

Version:

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

1,134 lines (1,132 loc) • 157 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import Exchange from './abstract/kraken.js'; import { AccountSuspended, BadSymbol, BadRequest, ExchangeNotAvailable, ArgumentsRequired, PermissionDenied, AuthenticationError, ExchangeError, OrderNotFound, DDoSProtection, InvalidNonce, InsufficientFunds, CancelPending, InvalidOrder, InvalidAddress, RateLimitExceeded, OnMaintenance, NotSupported } from './base/errors.js'; import { Precise } from './base/Precise.js'; import { TRUNCATE, TICK_SIZE } from './base/functions/number.js'; import { sha256 } from './static_dependencies/noble-hashes/sha256.js'; import { sha512 } from './static_dependencies/noble-hashes/sha512.js'; // --------------------------------------------------------------------------- /** * @class kraken * @augments Exchange * @description Set rateLimit to 1000 if fully verified */ export default class kraken extends Exchange { 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': { // about X & Z prefixes and .S & .M suffixes, see comment under fetchCurrencies 'LUNA': 'LUNC', 'LUNA2': 'LUNA', 'REPV2': 'REP', 'REP': 'REPV1', 'UST': 'USTC', 'XBT': 'BTC', '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': TICK_SIZE, 'exceptions': { 'exact': { 'EQuery:Invalid asset pair': BadSymbol, 'EAPI:Invalid key': AuthenticationError, 'EFunding:Unknown withdraw key': InvalidAddress, 'EFunding:Invalid amount': InsufficientFunds, 'EService:Unavailable': ExchangeNotAvailable, 'EDatabase:Internal error': ExchangeNotAvailable, 'EService:Busy': ExchangeNotAvailable, 'EQuery:Unknown asset': BadSymbol, 'EAPI:Rate limit exceeded': DDoSProtection, 'EOrder:Rate limit exceeded': DDoSProtection, 'EGeneral:Internal error': ExchangeNotAvailable, 'EGeneral:Temporary lockout': DDoSProtection, 'EGeneral:Permission denied': PermissionDenied, 'EGeneral:Invalid arguments:price': InvalidOrder, 'EOrder:Unknown order': InvalidOrder, 'EOrder:Invalid price:Invalid price argument': InvalidOrder, 'EOrder:Order minimum not met': InvalidOrder, 'EOrder:Insufficient funds': InsufficientFunds, 'EGeneral:Invalid arguments': BadRequest, 'ESession:Invalid session': AuthenticationError, 'EAPI:Invalid nonce': InvalidNonce, 'EFunding:No funding method': BadRequest, 'EFunding:Unknown asset': BadSymbol, 'EService:Market in post_only mode': OnMaintenance, 'EGeneral:Too many requests': DDoSProtection, 'ETrade:User Locked': AccountSuspended, // {"error":["ETrade:User Locked"]} }, 'broad': { ':Invalid order': InvalidOrder, ':Invalid arguments:volume': InvalidOrder, ':Invalid arguments:viqc': InvalidOrder, ':Invalid nonce': InvalidNonce, ':IInsufficient funds': InsufficientFunds, ':Cancel pending': CancelPending, ':Rate limit exceeded': RateLimitExceeded, }, }, }); } feeToPrecision(symbol, fee) { return this.decimalToPrecision(fee, 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 = {}) { const promises = []; promises.push(this.publicGetAssetPairs(params)); if (this.options['adjustForTimeDifference']) { promises.push(this.loadTimeDifference()); } const responses = await Promise.all(promises); const assetsResponse = responses[0]; // // { // "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.safeDict(assetsResponse, 'result', {}); const cachedCurrencies = this.safeDict(this.options, 'cachedCurrencies', {}); const keys = Object.keys(markets); const 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 makerFees = this.safeList(market, 'fees_maker', []); const firstMakerFee = this.safeList(makerFees, 0, []); const firstMakerFeeRate = this.safeString(firstMakerFee, 1); let maker = undefined; if (firstMakerFeeRate !== undefined) { maker = this.parseNumber(Precise.stringDiv(firstMakerFeeRate, '100')); } const takerFees = this.safeList(market, 'fees', []); const firstTakerFee = this.safeList(takerFees, 0, []); const firstTakerFeeRate = this.safeString(firstTakerFee, 1); let taker = undefined; if (firstTakerFeeRate !== undefined) { taker = this.parseNumber(Precise.stringDiv(firstTakerFeeRate, '100')); } const leverageBuy = this.safeList(market, 'leverage_buy', []); const leverageBuyLength = leverageBuy.length; const precisionPrice = this.parseNumber(this.parsePrecision(this.safeString(market, 'pair_decimals'))); let precisionAmount = this.parseNumber(this.parsePrecision(this.safeString(market, 'lot_decimals'))); const spot = true; // fix https://github.com/freqtrade/freqtrade/issues/11765#issuecomment-2894224103 if (spot && (base in cachedCurrencies)) { const currency = cachedCurrencies[base]; const currencyPrecision = this.safeNumber(currency, 'precision'); // if currency precision is greater (e.g. 0.01) than market precision (e.g. 0.001) if (currencyPrecision > precisionAmount) { precisionAmount = currencyPrecision; } } const status = this.safeString(market, 'status'); const isActive = status === 'online'; result.push({ 'id': id, 'wsId': this.safeString(market, 'wsname'), 'symbol': base + '/' + quote, 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'altname': market['altname'], 'type': 'spot', 'spot': spot, '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': precisionAmount, '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': undefined, 'max': undefined, }, 'cost': { 'min': this.safeNumber(market, 'costmin'), 'max': undefined, }, }, 'created': undefined, 'info': market, }); } 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); } /** * @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": { // "ATOM": { // "aclass": "currency", // "altname": "ATOM", // "collateral_value": "0.7", // "decimals": 8, // "display_decimals": 6, // "margin_rate": 0.02, // "status": "enabled", // }, // "ATOM.S": { // "aclass": "currency", // "altname": "ATOM.S", // "decimals": 8, // "display_decimals": 6, // "status": "enabled", // }, // "XXBT": { // "aclass": "currency", // "altname": "XBT", // "decimals": 10, // "display_decimals": 5, // "margin_rate": 0.01, // "status": "enabled", // }, // "XETH": { // "aclass": "currency", // "altname": "ETH", // "decimals": 10, // "display_decimals": 5 // "margin_rate": 0.02, // "status": "enabled", // }, // "XBT.M": { // "aclass": "currency", // "altname": "XBT.M", // "decimals": 10, // "display_decimals": 5 // "status": "enabled", // }, // "ETH.M": { // "aclass": "currency", // "altname": "ETH.M", // "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 // // Notes about abbreviations: // Z and X prefixes: https://support.kraken.com/hc/en-us/articles/360001206766-Bitcoin-currency-code-XBT-vs-BTC // S and M suffixes: https://support.kraken.com/hc/en-us/articles/360039879471-What-is-Asset-S-and-Asset-M- // let code = this.safeCurrencyCode(id); // the below can not be reliable done in `safeCurrencyCode`, so we have to do it here if (id.indexOf('.') < 0) { const altName = this.safeString(currency, 'altname'); // handle cases like below: // // id | altname // --------------- // XXBT | XBT // ZUSD | USD if (id !== altName && (id.startsWith('X') || id.startsWith('Z'))) { code = this.safeCurrencyCode(altName); // also, add map in commonCurrencies: this.commonCurrencies[id] = code; } else { code = this.safeCurrencyCode(id); } } const isFiat = code.indexOf('.HOLD') >= 0; result[code] = this.safeCurrencyStructure({ 'id': id, 'code': code, 'info': currency, 'name': this.safeString(currency, 'altname'), 'active': this.safeString(currency, 'status') === 'enabled', 'type': isFiat ? 'fiat' : 'crypto', 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'precision': this.parseNumber(this.parsePrecision(this.safeString(currency, 'decimals'))), 'limits': { 'amount': { 'min': undefined, 'max': undefined, }, 'withdraw': { 'min': undefined, 'max': undefined, }, }, 'networks': {}, }); } return result; } safeCurrencyCode(currencyId, currency = undefined) { if (currencyId === undefined) { return currencyId; } if (currencyId.indexOf('.') > 0) { // if ID contains .M, .S or .F, then it can't contain X or Z prefix. in such case, ID equals to ALTNAME const parts = currencyId.split('.'); const firstPart = this.safeString(parts, 0); const secondPart = this.safeString(parts, 1); return super.safeCurrencyCode(firstPart, currency) + '.' + secondPart; } return super.safeCurrencyCode(currencyId, currency); } /** * @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.stringDiv(this.safeString(symbolMakerFee, 'fee'), '100')), 'taker': this.parseNumber(Precise.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); 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.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': this.safeString(bid, 2), 'ask': this.safeString(ask, 0), 'askVolume': this.safeString(ask, 2), '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']) { 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 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