UNPKG

consequunturatque

Version:

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

1,237 lines (1,204 loc) 76.7 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { BadSymbol, ExchangeNotAvailable, ArgumentsRequired, PermissionDenied, AuthenticationError, ExchangeError, OrderNotFound, DDoSProtection, InvalidNonce, InsufficientFunds, CancelPending, InvalidOrder, InvalidAddress, RateLimitExceeded } = require ('./base/errors'); const { TRUNCATE, DECIMAL_PLACES } = require ('./base/functions/number'); const Precise = require ('./base/Precise'); // --------------------------------------------------------------------------- module.exports = class kraken extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'kraken', 'name': 'Kraken', 'countries': [ 'US' ], 'version': '0', 'rateLimit': 3000, 'certified': true, 'pro': true, 'has': { 'cancelAllOrders': true, 'cancelOrder': true, 'CORS': false, 'createDepositAddress': true, 'createOrder': true, 'fetchBalance': true, 'fetchClosedOrders': true, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDeposits': true, 'fetchLedger': true, 'fetchLedgerEntry': true, 'fetchMarkets': true, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrderTrades': 'emulated', 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': true, 'fetchTrades': true, 'fetchTradingFee': true, 'fetchTradingFees': true, 'fetchWithdrawals': true, 'withdraw': true, }, 'marketsByAltname': {}, 'timeframes': { '1m': 1, '5m': 5, '15m': 15, '30m': 30, '1h': 60, '4h': 240, '1d': 1440, '1w': 10080, '2w': 21600, }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/51840849/76173629-fc67fb00-61b1-11ea-84fe-f2de582f58a3.jpg', 'api': { 'public': 'https://api.kraken.com', 'private': 'https://api.kraken.com', 'zendesk': 'https://kraken.zendesk.com/api/v2/help_center/en-us/articles', // use the public zendesk api to receive article bodies and bypass new anti-spam protections }, 'www': 'https://www.kraken.com', 'doc': 'https://www.kraken.com/features/api', 'fees': 'https://www.kraken.com/en-us/features/fee-schedule', }, 'fees': { 'trading': { 'tierBased': true, 'percentage': true, 'taker': 0.26 / 100, 'maker': 0.16 / 100, 'tiers': { 'taker': [ [0, 0.0026], [50000, 0.0024], [100000, 0.0022], [250000, 0.0020], [500000, 0.0018], [1000000, 0.0016], [2500000, 0.0014], [5000000, 0.0012], [10000000, 0.0001], ], 'maker': [ [0, 0.0016], [50000, 0.0014], [100000, 0.0012], [250000, 0.0010], [500000, 0.0008], [1000000, 0.0006], [2500000, 0.0004], [5000000, 0.0002], [10000000, 0.0], ], }, }, // this is a bad way of hardcoding fees that change on daily basis // hardcoding is now considered obsolete, we will remove all of it eventually 'funding': { 'tierBased': false, 'percentage': false, 'withdraw': { 'BTC': 0.001, 'ETH': 0.005, 'XRP': 0.02, 'XLM': 0.00002, 'LTC': 0.02, 'DOGE': 2, 'ZEC': 0.00010, 'ICN': 0.02, 'REP': 0.01, 'ETC': 0.005, 'MLN': 0.003, 'XMR': 0.05, 'DASH': 0.005, 'GNO': 0.01, 'EOS': 0.5, 'BCH': 0.001, 'XTZ': 0.05, 'USD': 5, // if domestic wire 'EUR': 5, // if domestic wire 'CAD': 10, // CAD EFT Withdrawal 'JPY': 300, // if domestic wire }, 'deposit': { 'BTC': 0, 'ETH': 0, 'XRP': 0, 'XLM': 0, 'LTC': 0, 'DOGE': 0, 'ZEC': 0, 'ICN': 0, 'REP': 0, 'ETC': 0, 'MLN': 0, 'XMR': 0, 'DASH': 0, 'GNO': 0, 'EOS': 0, 'BCH': 0, 'XTZ': 0.05, 'USD': 5, // if domestic wire 'EUR': 0, // free deposit if EUR SEPA Deposit 'CAD': 5, // if domestic wire 'JPY': 0, // Domestic Deposit (Free, ¥5,000 deposit minimum) }, }, }, 'api': { 'zendesk': { 'get': [ // we should really refrain from putting fixed fee numbers and stop hardcoding // we will be using their web APIs to scrape all numbers from these articles '360000292886', // -What-are-the-deposit-fees- '201893608', // -What-are-the-withdrawal-fees- ], }, 'public': { 'get': [ 'Assets', 'AssetPairs', 'Depth', 'OHLC', 'Spread', 'Ticker', 'Time', 'Trades', ], }, 'private': { 'post': [ 'AddOrder', 'AddExport', 'Balance', 'CancelAll', 'CancelOrder', 'ClosedOrders', 'DepositAddresses', 'DepositMethods', 'DepositStatus', 'ExportStatus', 'GetWebSocketsToken', 'Ledgers', 'OpenOrders', 'OpenPositions', 'QueryLedgers', 'QueryOrders', 'QueryTrades', 'RetrieveExport', 'RemoveExport', 'TradeBalance', 'TradesHistory', 'TradeVolume', 'Withdraw', 'WithdrawCancel', 'WithdrawInfo', 'WithdrawStatus', ], }, }, 'commonCurrencies': { 'XBT': 'BTC', 'XBT.M': 'BTC.M', // https://support.kraken.com/hc/en-us/articles/360039879471-What-is-Asset-S-and-Asset-M- 'XDG': 'DOGE', 'REPV2': 'REP', 'REP': 'REPV1', }, 'options': { 'cacheDepositMethodsOnFetchDepositAddress': true, // will issue up to two calls in fetchDepositAddress 'depositMethods': {}, 'delistedMarketsById': {}, // cannot withdraw/deposit these 'inactiveCurrencies': [ 'CAD', 'USD', 'JPY', 'GBP' ], }, 'exceptions': { 'EQuery:Invalid asset pair': BadSymbol, // {"error":["EQuery:Invalid asset pair"]} 'EAPI:Invalid key': AuthenticationError, 'EFunding:Unknown withdraw key': ExchangeError, 'EFunding:Invalid amount': InsufficientFunds, 'EService:Unavailable': ExchangeNotAvailable, 'EDatabase:Internal error': ExchangeNotAvailable, 'EService:Busy': ExchangeNotAvailable, 'EQuery:Unknown asset': ExchangeError, 'EAPI:Rate limit exceeded': DDoSProtection, 'EOrder:Rate limit exceeded': DDoSProtection, 'EGeneral:Internal error': ExchangeNotAvailable, 'EGeneral:Temporary lockout': DDoSProtection, 'EGeneral:Permission denied': PermissionDenied, 'EOrder:Unknown order': InvalidOrder, 'EOrder:Order minimum not met': InvalidOrder, }, }); } costToPrecision (symbol, cost) { return this.decimalToPrecision (cost, TRUNCATE, this.markets[symbol]['precision']['price'], DECIMAL_PLACES); } feeToPrecision (symbol, fee) { return this.decimalToPrecision (fee, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); } async fetchMarkets (params = {}) { 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 symbol = darkpool ? altname : (base + '/' + quote); const makerFees = this.safeValue (market, 'fees_maker', []); const firstMakerFee = this.safeValue (makerFees, 0, []); const firstMakerFeeRate = this.safeNumber (firstMakerFee, 1); let maker = undefined; if (firstMakerFeeRate !== undefined) { maker = parseFloat (firstMakerFeeRate) / 100; } const takerFees = this.safeValue (market, 'fees', []); const firstTakerFee = this.safeValue (takerFees, 0, []); const firstTakerFeeRate = this.safeNumber (firstTakerFee, 1); let taker = undefined; if (firstTakerFeeRate !== undefined) { taker = parseFloat (firstTakerFeeRate) / 100; } const precision = { 'amount': this.safeInteger (market, 'lot_decimals'), 'price': this.safeInteger (market, 'pair_decimals'), }; const minAmount = this.safeNumber (market, 'ordermin'); result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'darkpool': darkpool, 'info': market, 'altname': market['altname'], 'maker': maker, 'taker': taker, 'active': true, 'precision': precision, 'limits': { 'amount': { 'min': minAmount, 'max': Math.pow (10, precision['amount']), }, 'price': { 'min': Math.pow (10, -precision['price']), 'max': undefined, }, 'cost': { 'min': 0, 'max': undefined, }, }, }); } result = this.appendInactiveMarkets (result); this.marketsByAltname = this.indexBy (result, 'altname'); return result; } safeCurrency (currencyId, currency = undefined) { if (currencyId.length > 3) { if ((currencyId.indexOf ('X') === 0) || (currencyId.indexOf ('Z') === 0)) { if (currencyId.indexOf ('.') > 0) { return super.safeCurrency (currencyId, currency); } else { currencyId = currencyId.slice (1); } } } return super.safeCurrency (currencyId, currency); } appendInactiveMarkets (result) { // result should be an array to append to const precision = { 'amount': 8, 'price': 8 }; const costLimits = { 'min': 0, 'max': undefined }; const priceLimits = { 'min': Math.pow (10, -precision['price']), 'max': undefined }; const amountLimits = { 'min': Math.pow (10, -precision['amount']), 'max': Math.pow (10, precision['amount']) }; const limits = { 'amount': amountLimits, 'price': priceLimits, 'cost': costLimits }; const defaults = { 'darkpool': false, 'info': undefined, 'maker': undefined, 'taker': undefined, 'active': false, 'precision': precision, 'limits': limits, }; const markets = [ // { 'id': 'XXLMZEUR', 'symbol': 'XLM/EUR', 'base': 'XLM', 'quote': 'EUR', 'altname': 'XLMEUR' }, ]; for (let i = 0; i < markets.length; i++) { result.push (this.extend (defaults, markets[i])); } return result; } async fetchCurrencies (params = {}) { const response = await this.publicGetAssets (params); // // { // "error": [], // "result": { // "ADA": { "aclass": "currency", "altname": "ADA", "decimals": 8, "display_decimals": 6 }, // "BCH": { "aclass": "currency", "altname": "BCH", "decimals": 10, "display_decimals": 5 }, // ... // }, // } // const currencies = this.safeValue (response, 'result'); const ids = Object.keys (currencies); const result = {}; for (let i = 0; i < ids.length; i++) { const id = ids[i]; const currency = currencies[id]; // todo: will need to rethink the fees // see: https://support.kraken.com/hc/en-us/articles/201893608-What-are-the-withdrawal-fees- // to add support for multiple withdrawal/deposit methods and // differentiated fees for each particular method const code = this.safeCurrencyCode (this.safeString (currency, 'altname')); const precision = this.safeInteger (currency, 'decimals'); // assumes all currencies are active except those listed above const active = !this.inArray (code, this.options['inactiveCurrencies']); result[code] = { 'id': id, 'code': code, 'info': currency, 'name': code, 'active': active, 'fee': undefined, 'precision': precision, 'limits': { 'amount': { 'min': Math.pow (10, -precision), 'max': Math.pow (10, precision), }, 'withdraw': { 'min': undefined, 'max': Math.pow (10, precision), }, }, }; } return result; } async fetchTradingFees (params = {}) { await this.loadMarkets (); this.checkRequiredCredentials (); const response = await this.privatePostTradeVolume (params); const tradedVolume = this.safeNumber (response['result'], 'volume'); const tiers = this.fees['trading']['tiers']; let taker = tiers['taker'][1]; let maker = tiers['maker'][1]; for (let i = 0; i < tiers['taker'].length; i++) { if (tradedVolume >= tiers['taker'][i][0]) { taker = tiers['taker'][i][1]; } } for (let i = 0; i < tiers['maker'].length; i++) { if (tradedVolume >= tiers['maker'][i][0]) { maker = tiers['maker'][i][1]; } } return { 'info': response, 'maker': maker, 'taker': taker, }; } parseBidAsk (bidask, priceKey = 0, amountKey = 1) { const price = this.safeNumber (bidask, priceKey); const amount = this.safeNumber (bidask, amountKey); const timestamp = this.safeInteger (bidask, 2); return [ price, amount, timestamp ]; } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); if (market['darkpool']) { throw new ExchangeError (this.id + ' 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) { const timestamp = this.milliseconds (); let symbol = undefined; if (market) { symbol = market['symbol']; } const baseVolume = parseFloat (ticker['v'][1]); const vwap = parseFloat (ticker['p'][1]); let quoteVolume = undefined; if (baseVolume !== undefined && vwap !== undefined) { quoteVolume = baseVolume * vwap; } const last = parseFloat (ticker['c'][0]); return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': parseFloat (ticker['h'][1]), 'low': parseFloat (ticker['l'][1]), 'bid': parseFloat (ticker['b'][0]), 'bidVolume': undefined, 'ask': parseFloat (ticker['a'][0]), 'askVolume': undefined, 'vwap': vwap, 'open': this.safeNumber (ticker, 'o'), 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }; } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); symbols = (symbols === undefined) ? this.symbols : 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']); } } const 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.markets_by_id[id]; const symbol = market['symbol']; const ticker = tickers[id]; result[symbol] = this.parseTicker (ticker, market); } return this.filterByArray (result, 'symbol', symbols); } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const darkpool = symbol.indexOf ('.d') >= 0; if (darkpool) { throw new ExchangeError (this.id + ' does not provide a ticker for darkpool symbol ' + symbol); } const market = this.market (symbol); const request = { 'pair': market['id'], }; const response = await this.publicGetTicker (this.extend (request, params)); const ticker = response['result'][market['id']]; return this.parseTicker (ticker, market); } parseOHLCV (ohlcv, market = undefined) { // // [ // 1591475640, // "0.02500", // "0.02500", // "0.02500", // "0.02500", // "0.02500", // "9.12201000", // 5 // ] // return [ this.safeTimestamp (ohlcv, 0), this.safeNumber (ohlcv, 1), this.safeNumber (ohlcv, 2), this.safeNumber (ohlcv, 3), this.safeNumber (ohlcv, 4), this.safeNumber (ohlcv, 6), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'pair': market['id'], 'interval': this.timeframes[timeframe], }; if (since !== undefined) { request['since'] = parseInt ((since - 1) / 1000); } const response = await this.publicGetOHLC (this.extend (request, params)); // // { // "error":[], // "result":{ // "XETHXXBT":[ // [1591475580,"0.02499","0.02499","0.02499","0.02499","0.00000","0.00000000",0], // [1591475640,"0.02500","0.02500","0.02500","0.02500","0.02500","9.12201000",5], // [1591475700,"0.02499","0.02499","0.02499","0.02499","0.02499","1.28681415",2], // [1591475760,"0.02499","0.02499","0.02499","0.02499","0.02499","0.08800000",1], // ], // "last":1591517580 // } // } const result = this.safeValue (response, 'result', {}); const ohlcvs = this.safeValue (result, market['id'], []); return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit); } parseLedgerEntryType (type) { const types = { 'trade': 'trade', 'withdrawal': 'transaction', 'deposit': 'transaction', 'transfer': 'transfer', 'margin': 'margin', }; return this.safeString (types, type, type); } parseLedgerEntry (item, currency = undefined) { // // { // 'LTFK7F-N2CUX-PNY4SX': { // refid: "TSJTGT-DT7WN-GPPQMJ", // time: 1520102320.555, // type: "trade", // aclass: "currency", // asset: "XETH", // amount: "0.1087194600", // fee: "0.0000000000", // balance: "0.2855851000" // }, // ... // } // const id = this.safeString (item, 'id'); let direction = undefined; const account = undefined; const referenceId = this.safeString (item, 'refid'); const referenceAccount = undefined; const type = this.parseLedgerEntryType (this.safeString (item, 'type')); const code = this.safeCurrencyCode (this.safeString (item, 'asset'), currency); let amount = this.safeNumber (item, 'amount'); if (amount < 0) { direction = 'out'; amount = Math.abs (amount); } else { direction = 'in'; } const time = this.safeNumber (item, 'time'); let timestamp = undefined; if (time !== undefined) { timestamp = parseInt (time * 1000); } const fee = { 'cost': this.safeNumber (item, 'fee'), 'currency': code, }; const before = undefined; const after = this.safeNumber (item, 'balance'); const status = 'ok'; return { 'info': item, 'id': id, 'direction': direction, 'account': account, 'referenceId': referenceId, 'referenceAccount': referenceAccount, 'type': type, 'currency': code, 'amount': amount, 'before': before, 'after': after, 'status': status, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'fee': fee, }; } async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { // https://www.kraken.com/features/api#get-ledgers-info await this.loadMarkets (); const request = {}; let currency = undefined; if (code !== undefined) { currency = this.currency (code); request['asset'] = currency['id']; } if (since !== undefined) { request['start'] = parseInt (since / 1000); } const response = await this.privatePostLedgers (this.extend (request, params)); // { error: [], // result: { ledger: { 'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N", // time: 1520103488.314, // type: "withdrawal", // aclass: "currency", // asset: "XETH", // amount: "-0.2805800000", // fee: "0.0050000000", // balance: "0.0000051000" }, const result = this.safeValue (response, 'result', {}); const ledger = this.safeValue (result, 'ledger', {}); const keys = Object.keys (ledger); const items = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = ledger[key]; value['id'] = key; items.push (value); } return this.parseLedger (items, currency, since, limit); } async fetchLedgerEntriesByIds (ids, code = undefined, params = {}) { // https://www.kraken.com/features/api#query-ledgers await this.loadMarkets (); ids = ids.join (','); const request = this.extend ({ 'id': ids, }, params); const response = await this.privatePostQueryLedgers (request); // { error: [], // result: { 'LPUAIB-TS774-UKHP7X': { refid: "A2B4HBV-L4MDIE-JU4N3N", // time: 1520103488.314, // type: "withdrawal", // aclass: "currency", // asset: "XETH", // amount: "-0.2805800000", // fee: "0.0050000000", // balance: "0.0000051000" } } } const result = response['result']; const keys = Object.keys (result); const items = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = result[key]; value['id'] = key; items.push (value); } return this.parseLedger (items); } async fetchLedgerEntry (id, code = undefined, params = {}) { const items = await this.fetchLedgerEntriesByIds ([ id ], code, params); return items[0]; } parseTrade (trade, market = undefined) { // // fetchTrades (public) // // [ // "0.032310", // price // "4.28169434", // amount // 1541390792.763, // timestamp // "s", // sell or buy // "l", // limit or market // "" // ] // // fetchOrderTrades (private) // // { // id: 'TIMIRG-WUNNE-RRJ6GT', // injected from outside // ordertxid: 'OQRPN2-LRHFY-HIFA7D', // postxid: 'TKH2SE-M7IF5-CFI7LT', // pair: 'USDCUSDT', // time: 1586340086.457, // type: 'sell', // ordertype: 'market', // price: '0.99860000', // cost: '22.16892001', // fee: '0.04433784', // vol: '22.20000000', // margin: '0.00000000', // misc: '' // } // let timestamp = undefined; let side = undefined; let type = undefined; let priceString = undefined; let amountString = undefined; let id = undefined; let orderId = undefined; let fee = undefined; let symbol = undefined; if (Array.isArray (trade)) { timestamp = this.safeTimestamp (trade, 2); side = (trade[3] === 's') ? 'sell' : 'buy'; type = (trade[4] === 'l') ? 'limit' : 'market'; priceString = this.safeString (trade, 0); amountString = this.safeString (trade, 1); const tradeLength = trade.length; if (tradeLength > 6) { id = this.safeString (trade, 6); // artificially added as per #1794 } } else if (typeof trade === 'string') { id = trade; } else if ('ordertxid' in trade) { const marketId = this.safeString (trade, 'pair'); const foundMarket = this.findMarketByAltnameOrId (marketId); if (foundMarket !== undefined) { market = foundMarket; } else if (marketId !== undefined) { // delisted market ids go here market = this.getDelistedMarketById (marketId); } orderId = this.safeString (trade, 'ordertxid'); id = this.safeString2 (trade, 'id', 'postxid'); timestamp = this.safeTimestamp (trade, 'time'); side = this.safeString (trade, 'type'); type = this.safeString (trade, 'ordertype'); priceString = this.safeString (trade, 'price'); amountString = this.safeString (trade, 'vol'); if ('fee' in trade) { let currency = undefined; if (market !== undefined) { currency = market['quote']; } fee = { 'cost': this.safeNumber (trade, 'fee'), 'currency': currency, }; } } if (market !== undefined) { symbol = market['symbol']; } const price = this.parseNumber (priceString); const amount = this.parseNumber (amountString); const cost = this.parseNumber (Precise.stringMul (priceString, amountString)); return { 'id': id, 'order': orderId, 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'type': type, 'side': side, 'takerOrMaker': undefined, 'price': price, 'amount': amount, 'cost': cost, 'fee': fee, }; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const id = market['id']; const request = { 'pair': id, }; // https://support.kraken.com/hc/en-us/articles/218198197-How-to-pull-all-trade-data-using-the-Kraken-REST-API // https://github.com/ccxt/ccxt/issues/5677 if (since !== undefined) { // php does not format it properly // therefore we use string concatenation here request['since'] = since * 1e6; request['since'] = since.toString () + '000000'; // expected to be in nanoseconds } // https://github.com/ccxt/ccxt/issues/5698 if (limit !== undefined && limit !== 1000) { const fetchTradesWarning = this.safeValue (this.options, 'fetchTradesWarning', true); if (fetchTradesWarning) { throw new ExchangeError (this.id + ' fetchTrades() cannot serve ' + limit.toString () + " trades without breaking the pagination, see https://github.com/ccxt/ccxt/issues/5698 for more details. Set exchange.options['fetchTradesWarning'] to acknowledge this warning and silence it."); } } const response = await this.publicGetTrades (this.extend (request, params)); // // { // "error": [], // "result": { // "XETHXXBT": [ // ["0.032310","4.28169434",1541390792.763,"s","l",""] // ], // "last": "1541439421200678657" // } // } // const result = response['result']; const trades = result[id]; // trades is a sorted array: last (most recent trade) goes last const length = trades.length; if (length <= 0) { return []; } const lastTrade = trades[length - 1]; const lastTradeId = this.safeString (result, 'last'); lastTrade.push (lastTradeId); return this.parseTrades (trades, market, since, limit); } async fetchBalance (params = {}) { await this.loadMarkets (); const response = await this.privatePostBalance (params); // // { // "error":[], // "result":{ // "ZUSD":"58.8649", // "KFEE":"4399.43", // "XXBT":"0.0000034506", // } // } // const balances = this.safeValue (response, 'result', {}); const result = { 'info': response, 'timestamp': undefined, 'datetime': undefined, }; const currencyIds = Object.keys (balances); for (let i = 0; i < currencyIds.length; i++) { const currencyId = currencyIds[i]; const code = this.safeCurrencyCode (currencyId); const account = this.account (); account['total'] = this.safeString (balances, currencyId); result[code] = account; } return this.parseBalance (result, false); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'pair': market['id'], 'type': side, 'ordertype': type, 'volume': this.amountToPrecision (symbol, amount), }; const clientOrderId = this.safeString2 (params, 'userref', 'clientOrderId'); params = this.omit (params, [ 'userref', 'clientOrderId' ]); if (clientOrderId !== undefined) { request['userref'] = clientOrderId; } // // market // limit (price = limit price) // stop-loss (price = stop loss price) // take-profit (price = take profit price) // stop-loss-limit (price = stop loss trigger price, price2 = triggered limit price) // take-profit-limit (price = take profit trigger price, price2 = triggered limit price) // settle-position // if (type === 'limit') { request['price'] = this.priceToPrecision (symbol, price); } else if ((type === 'stop-loss') || (type === 'take-profit')) { const stopPrice = this.safeNumber2 (params, 'price', 'stopPrice', price); if (stopPrice === undefined) { throw new ArgumentsRequired (this.id + ' createOrder() requires a price argument or a price/stopPrice parameter for a ' + type + ' order'); } else { request['price'] = this.priceToPrecision (symbol, stopPrice); } } else if ((type === 'stop-loss-limit') || (type === 'take-profit-limit')) { const stopPrice = this.safeNumber2 (params, 'price', 'stopPrice'); const limitPrice = this.safeNumber (params, 'price2'); const stopPriceDefined = (stopPrice !== undefined); const limitPriceDefined = (limitPrice !== undefined); if (stopPriceDefined && limitPriceDefined) { request['price'] = this.priceToPrecision (symbol, stopPrice); request['price2'] = this.priceToPrecision (symbol, limitPrice); } else if ((price === undefined) || (!(stopPriceDefined || limitPriceDefined))) { throw new ArgumentsRequired (this.id + ' createOrder() requires a price argument and/or price/stopPrice/price2 parameters for a ' + type + ' order'); } else { if (stopPriceDefined) { request['price'] = this.priceToPrecision (symbol, stopPrice); request['price2'] = this.priceToPrecision (symbol, price); } else if (limitPriceDefined) { request['price'] = this.priceToPrecision (symbol, price); request['price2'] = this.priceToPrecision (symbol, limitPrice); } } } params = this.omit (params, [ 'price', 'stopPrice', 'price2' ]); const response = await this.privatePostAddOrder (this.extend (request, params)); // // { // error: [], // result: { // descr: { order: 'buy 0.02100000 ETHUSDT @ limit 330.00' }, // txid: [ 'OEKVV2-IH52O-TPL6GZ' ] // } // } // const result = this.safeValue (response, 'result'); return this.parseOrder (result); } findMarketByAltnameOrId (id) { if (id in this.marketsByAltname) { return this.marketsByAltname[id]; } else if (id in this.markets_by_id) { return this.markets_by_id[id]; } return undefined; } getDelistedMarketById (id) { if (id === undefined) { return id; } let market = this.safeValue (this.options['delistedMarketsById'], id); if (market !== undefined) { return market; } const baseIdStart = 0; let baseIdEnd = 3; let quoteIdStart = 3; let quoteIdEnd = 6; if (id.length === 8) { baseIdEnd = 4; quoteIdStart = 4; quoteIdEnd = 8; } else if (id.length === 7) { baseIdEnd = 4; quoteIdStart = 4; quoteIdEnd = 7; } const baseId = id.slice (baseIdStart, baseIdEnd); const quoteId = id.slice (quoteIdStart, quoteIdEnd); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const symbol = base + '/' + quote; market = { 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, }; this.options['delistedMarketsById'][id] = market; return market; } parseOrderStatus (status) { const statuses = { 'pending': 'open', // order pending book entry 'open': 'open', 'closed': 'closed', 'canceled': 'canceled', 'expired': 'expired', }; return this.safeString (statuses, status, status); } parseOrder (order, market = undefined) { // // createOrder // // { // descr: { order: 'buy 0.02100000 ETHUSDT @ limit 330.00' }, // txid: [ 'OEKVV2-IH52O-TPL6GZ' ] // } // const description = this.safeValue (order, 'descr', {}); const orderDescription = this.safeString (description, 'order'); let side = undefined; let type = undefined; let marketId = undefined; let price = undefined; let amount = undefined; if (orderDescription !== undefined) { const parts = orderDescription.split (' '); side = this.safeString (parts, 0); amount = this.safeNumber (parts, 1); marketId = this.safeString (parts, 2); type = this.safeString (parts, 4); price = this.safeNumber (parts, 5); } side = this.safeString (description, 'type', side); type = this.safeString (description, 'ordertype', type); marketId = this.safeString (description, 'pair', marketId); const foundMarket = this.findMarketByAltnameOrId (marketId); let symbol = undefined; if (foundMarket !== undefined) { market = foundMarket; } else if (marketId !== undefined) { // delisted market ids go here market = this.getDelistedMarketById (marketId); } const timestamp = this.safeTimestamp (order, 'opentm'); amount = this.safeNumber (order, 'vol', amount); const filled = this.safeNumber (order, 'vol_exec'); let fee = undefined; const cost = this.safeNumber (order, 'cost'); price = this.safeNumber (description, 'price', price); if ((price === undefined) || (price === 0.0)) { price = this.safeNumber (description, 'price2'); } if ((price === undefined) || (price === 0.0)) { price = this.safeNumber (order, 'price', price); } const average = this.safeNumber (order, 'price'); if (market !== undefined) { symbol = market['symbol']; if ('fee' in order) { const flags = order['oflags']; const feeCost = this.safeNumber (order, 'fee'); fee = { 'cost': feeCost, 'rate': undefined, }; if (flags.indexOf ('fciq') >= 0) { fee['currency'] = market['quote']; } else if (flags.indexOf ('fcib') >= 0) { fee['currency'] = market['base']; } } } const status = this.parseOrderStatus (this.safeString (order, 'status')); let id = this.safeString (order, 'id'); if (id === undefined) { const txid = this.safeValue (order, 'txid'); id = this.safeString (txid, 0); } const clientOrderId = this.safeString (order, 'userref'); const rawTrades = this.safeValue (order, 'trades'); let trades = undefined; if (rawTrades !== undefined) { trades = this.parseTrades (rawTrades, market, undefined, undefined, { 'order': id }); } const stopPrice = this.safeNumber (order, 'stopprice'); return this.safeOrder ({ 'id': id, 'clientOrderId': clientOrderId, 'info': order, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': undefined, 'status': status, 'symbol': symbol, 'type': type, 'timeInForce': undefined, 'postOnly': undefined, 'side': side, 'price': price, 'stopPrice': stopPrice, 'cost': cost, 'amount': amount, 'filled': filled, 'average': average, 'remaining': undefined, 'fee': fee, 'trades': trades, }); } async fetchOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); const clientOrderId = this.safeValue2 (params, 'userref', 'clientOrderId'); const request = { 'trades': true, // whether or not to include trades in output (optional, default false) // 'txid': id, // do not comma separate a list of ids - use fetchOrdersByIds instead // 'userref': 'optional', // restrict results to given user reference id (optional) }; let query = params; if (clientOrderId !== undefined) { request['userref'] = clientOrderId; query = this.omit (params, [ 'userref', 'clientOrderId' ]); } else { request['txid'] = id; } const response = await this.privatePostQueryOrders (this.extend (request, query)); // // { // "error":[], // "result":{ // "OTLAS3-RRHUF-NDWH5A":{ // "refid":null, // "userref":null, // "status":"closed", // "reason":null, // "opentm":1586822919.3342, // "closetm":1586822919.365, // "starttm":0,