ccxt
Version:
1,134 lines (1,132 loc) • 157 kB
JavaScript
// ----------------------------------------------------------------------------
// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
// EDIT THE CORRESPONDENT .ts FILE INSTEAD
// ---------------------------------------------------------------------------
import 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