UNPKG

@proton/ccxt

Version:

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

1,146 lines (1,143 loc) 79.6 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/bitvavo.js'; import { ExchangeError, BadSymbol, AuthenticationError, InsufficientFunds, InvalidOrder, ArgumentsRequired, OrderNotFound, InvalidAddress, BadRequest, RateLimitExceeded, PermissionDenied, ExchangeNotAvailable, AccountSuspended, OnMaintenance } from './base/errors.js'; import { SIGNIFICANT_DIGITS, DECIMAL_PLACES, TRUNCATE, ROUND } from './base/functions/number.js'; import { Precise } from './base/Precise.js'; import { sha256 } from './static_dependencies/noble-hashes/sha256.js'; // ---------------------------------------------------------------------------- export default class bitvavo extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'bitvavo', 'name': 'Bitvavo', 'countries': ['NL'], 'rateLimit': 60, 'version': 'v2', 'certified': true, 'pro': true, 'has': { 'CORS': undefined, 'spot': true, 'margin': false, 'swap': false, 'future': false, 'option': false, 'addMargin': false, 'cancelAllOrders': true, 'cancelOrder': true, 'createOrder': true, 'createReduceOnlyOrder': false, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'editOrder': true, 'fetchBalance': true, 'fetchBorrowRate': false, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchBorrowRates': false, 'fetchBorrowRatesPerSymbol': false, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDeposits': true, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': true, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': false, 'fetchFundingRates': false, 'fetchIndexOHLCV': false, 'fetchLeverage': false, 'fetchLeverageTiers': false, 'fetchMarginMode': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenInterestHistory': false, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': true, 'fetchPosition': false, 'fetchPositionMode': false, 'fetchPositions': false, 'fetchPositionsRisk': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': true, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransfer': false, 'fetchTransfers': false, 'fetchWithdrawals': true, 'reduceMargin': false, 'setLeverage': false, 'setMarginMode': false, 'setPositionMode': false, 'transfer': false, 'withdraw': true, }, 'timeframes': { '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '2h': '2h', '4h': '4h', '6h': '6h', '8h': '8h', '12h': '12h', '1d': '1d', }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/169202626-bd130fc5-fcf9-41bb-8d97-6093225c73cd.jpg', 'api': { 'public': 'https://api.bitvavo.com', 'private': 'https://api.bitvavo.com', }, 'www': 'https://bitvavo.com/', 'doc': 'https://docs.bitvavo.com/', 'fees': 'https://bitvavo.com/en/fees', 'referral': 'https://bitvavo.com/?a=24F34952F7', }, 'api': { 'public': { 'get': { 'time': 1, 'markets': 1, 'assets': 1, '{market}/book': 1, '{market}/trades': 5, '{market}/candles': 1, 'ticker/price': 1, 'ticker/book': 1, 'ticker/24h': { 'cost': 1, 'noMarket': 25 }, }, }, 'private': { 'get': { 'account': 1, 'order': 1, 'orders': 5, 'ordersOpen': { 'cost': 1, 'noMarket': 25 }, 'trades': 5, 'balance': 5, 'deposit': 1, 'depositHistory': 5, 'withdrawalHistory': 5, }, 'post': { 'order': 1, 'withdrawal': 1, }, 'put': { 'order': 1, }, 'delete': { 'order': 1, 'orders': 1, }, }, }, 'fees': { 'trading': { 'tierBased': true, 'percentage': true, 'taker': this.parseNumber('0.0025'), 'maker': this.parseNumber('0.002'), 'tiers': { 'taker': [ [this.parseNumber('0'), this.parseNumber('0.0025')], [this.parseNumber('100000'), this.parseNumber('0.0020')], [this.parseNumber('250000'), this.parseNumber('0.0016')], [this.parseNumber('500000'), this.parseNumber('0.0012')], [this.parseNumber('1000000'), this.parseNumber('0.0010')], [this.parseNumber('2500000'), this.parseNumber('0.0008')], [this.parseNumber('5000000'), this.parseNumber('0.0006')], [this.parseNumber('10000000'), this.parseNumber('0.0005')], [this.parseNumber('25000000'), this.parseNumber('0.0004')], ], 'maker': [ [this.parseNumber('0'), this.parseNumber('0.0015')], [this.parseNumber('100000'), this.parseNumber('0.0010')], [this.parseNumber('250000'), this.parseNumber('0.0008')], [this.parseNumber('500000'), this.parseNumber('0.0006')], [this.parseNumber('1000000'), this.parseNumber('0.0005')], [this.parseNumber('2500000'), this.parseNumber('0.0004')], [this.parseNumber('5000000'), this.parseNumber('0.0004')], [this.parseNumber('10000000'), this.parseNumber('0.0003')], [this.parseNumber('25000000'), this.parseNumber('0.0003')], ], }, }, }, 'requiredCredentials': { 'apiKey': true, 'secret': true, }, 'exceptions': { 'exact': { '101': ExchangeError, '102': BadRequest, '103': RateLimitExceeded, '104': RateLimitExceeded, '105': PermissionDenied, '107': ExchangeNotAvailable, '108': ExchangeNotAvailable, '109': ExchangeNotAvailable, '110': BadRequest, '200': BadRequest, '201': BadRequest, '202': BadRequest, '203': BadSymbol, '204': BadRequest, '205': BadRequest, '206': BadRequest, '210': InvalidOrder, '211': InvalidOrder, '212': InvalidOrder, '213': InvalidOrder, '214': InvalidOrder, '215': InvalidOrder, '216': InsufficientFunds, '217': InvalidOrder, '230': ExchangeError, '231': ExchangeError, '232': BadRequest, '233': InvalidOrder, '234': InvalidOrder, '235': ExchangeError, '236': BadRequest, '240': OrderNotFound, '300': AuthenticationError, '301': AuthenticationError, '302': AuthenticationError, '303': AuthenticationError, '304': AuthenticationError, // '304': AuthenticationError, // Authentication is required for this endpoint. '305': AuthenticationError, '306': AuthenticationError, '307': PermissionDenied, '308': AuthenticationError, '309': AuthenticationError, '310': PermissionDenied, '311': PermissionDenied, '312': PermissionDenied, '315': BadRequest, '317': AccountSuspended, '400': ExchangeError, '401': ExchangeError, '402': PermissionDenied, '403': PermissionDenied, '404': OnMaintenance, '405': ExchangeError, '406': BadRequest, '407': ExchangeError, '408': InsufficientFunds, '409': InvalidAddress, '410': ExchangeError, '411': BadRequest, '412': InvalidAddress, '413': InvalidAddress, '414': ExchangeError, // You cannot withdraw assets within 2 minutes of logging in. }, 'broad': { 'start parameter is invalid': BadRequest, 'symbol parameter is invalid': BadSymbol, 'amount parameter is invalid': InvalidOrder, 'orderId parameter is invalid': InvalidOrder, // {"errorCode":205,"error":"orderId parameter is invalid."} }, }, 'options': { 'BITVAVO-ACCESS-WINDOW': 10000, 'fetchCurrencies': { 'expires': 1000, // 1 second }, 'networks': { 'ERC20': 'ETH', 'TRC20': 'TRX', }, 'networksById': { 'TRX': 'TRC20', 'ETH': 'ERC20', }, }, 'precisionMode': SIGNIFICANT_DIGITS, 'commonCurrencies': { 'MIOTA': 'IOTA', // https://github.com/ccxt/ccxt/issues/7487 }, }); } currencyToPrecision(code, fee, networkCode = undefined) { return this.decimalToPrecision(fee, 0, this.currencies[code]['precision'], DECIMAL_PLACES); } amountToPrecision(symbol, amount) { // https://docs.bitfinex.com/docs/introduction#amount-precision // The amount field allows up to 8 decimals. // Anything exceeding this will be rounded to the 8th decimal. return this.decimalToPrecision(amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); } priceToPrecision(symbol, price) { price = this.decimalToPrecision(price, ROUND, this.markets[symbol]['precision']['price'], this.precisionMode); // https://docs.bitfinex.com/docs/introduction#price-precision // The precision level of all trading prices is based on significant figures. // All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals (e.g. 1.2345, 123.45, 1234.5, 0.00012345). // Prices submit with a precision larger than 5 will be cut by the API. return this.decimalToPrecision(price, TRUNCATE, 8, DECIMAL_PLACES); } async fetchTime(params = {}) { /** * @method * @name bitvavo#fetchTime * @description fetches the current integer timestamp in milliseconds from the exchange server * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {int} the current integer timestamp in milliseconds from the exchange server */ const response = await this.publicGetTime(params); // // { "time": 1590379519148 } // return this.safeInteger(response, 'time'); } async fetchMarkets(params = {}) { /** * @method * @name bitvavo#fetchMarkets * @description retrieves data on all markets for bitvavo * @param {object} params extra parameters specific to the exchange api endpoint * @returns {[object]} an array of objects representing market data */ const response = await this.publicGetMarkets(params); const currencies = await this.fetchCurrenciesFromCache(params); const currenciesById = this.indexBy(currencies, 'symbol'); // // [ // { // "market":"ADA-BTC", // "status":"trading", // "trading" "halted" "auction" // "base":"ADA", // "quote":"BTC", // "pricePrecision":5, // "minOrderInBaseAsset":"100", // "minOrderInQuoteAsset":"0.001", // "orderTypes": [ "market", "limit" ] // } // ] // const result = []; for (let i = 0; i < response.length; i++) { const market = response[i]; const id = this.safeString(market, 'market'); const baseId = this.safeString(market, 'base'); const quoteId = this.safeString(market, 'quote'); const base = this.safeCurrencyCode(baseId); const quote = this.safeCurrencyCode(quoteId); const status = this.safeString(market, 'status'); const baseCurrency = this.safeValue(currenciesById, baseId); result.push({ 'id': id, 'symbol': base + '/' + quote, 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'type': 'spot', 'spot': true, 'margin': false, 'swap': false, 'future': false, 'option': false, 'active': (status === 'trading'), 'contract': false, 'linear': undefined, 'inverse': undefined, 'contractSize': undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': this.safeInteger(baseCurrency, 'decimals', 8), 'price': this.safeInteger(market, 'pricePrecision'), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': this.safeNumber(market, 'minOrderInBaseAsset'), 'max': undefined, }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': this.safeNumber(market, 'minOrderInQuoteAsset'), 'max': undefined, }, }, 'info': market, }); } return result; } async fetchCurrenciesFromCache(params = {}) { // this method is now redundant // currencies are now fetched before markets const options = this.safeValue(this.options, 'fetchCurrencies', {}); const timestamp = this.safeInteger(options, 'timestamp'); const expires = this.safeInteger(options, 'expires', 1000); const now = this.milliseconds(); if ((timestamp === undefined) || ((now - timestamp) > expires)) { const response = await this.publicGetAssets(params); this.options['fetchCurrencies'] = this.extend(options, { 'response': response, 'timestamp': now, }); } return this.safeValue(this.options['fetchCurrencies'], 'response'); } async fetchCurrencies(params = {}) { /** * @method * @name bitvavo#fetchCurrencies * @description fetches all available currencies on an exchange * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {object} an associative dictionary of currencies */ const response = await this.fetchCurrenciesFromCache(params); // // [ // { // "symbol":"ADA", // "name":"Cardano", // "decimals":6, // "depositFee":"0", // "depositConfirmations":15, // "depositStatus":"OK", // "OK", "MAINTENANCE", "DELISTED" // "withdrawalFee":"0.2", // "withdrawalMinAmount":"0.2", // "withdrawalStatus":"OK", // "OK", "MAINTENANCE", "DELISTED" // "networks": [ "Mainnet" ], // "ETH", "NEO", "ONT", "SEPA", "VET" // "message":"", // }, // ] // const result = {}; for (let i = 0; i < response.length; i++) { const currency = response[i]; const id = this.safeString(currency, 'symbol'); const code = this.safeCurrencyCode(id); const depositStatus = this.safeValue(currency, 'depositStatus'); const deposit = (depositStatus === 'OK'); const withdrawalStatus = this.safeValue(currency, 'withdrawalStatus'); const withdrawal = (withdrawalStatus === 'OK'); const active = deposit && withdrawal; const name = this.safeString(currency, 'name'); result[code] = { 'id': id, 'info': currency, 'code': code, 'name': name, 'active': active, 'deposit': deposit, 'withdraw': withdrawal, 'fee': this.safeNumber(currency, 'withdrawalFee'), 'precision': this.safeInteger(currency, 'decimals', 8), 'limits': { 'amount': { 'min': undefined, 'max': undefined, }, 'withdraw': { 'min': this.safeNumber(currency, 'withdrawalMinAmount'), 'max': undefined, }, }, }; } return result; } async fetchTicker(symbol, params = {}) { /** * @method * @name bitvavo#fetchTicker * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'market': market['id'], }; const response = await this.publicGetTicker24h(this.extend(request, params)); // // { // "market":"ETH-BTC", // "open":"0.022578", // "high":"0.023019", // "low":"0.022573", // "last":"0.023019", // "volume":"25.16366324", // "volumeQuote":"0.57333305", // "bid":"0.023039", // "bidSize":"0.53500578", // "ask":"0.023041", // "askSize":"0.47859202", // "timestamp":1590381666900 // } // return this.parseTicker(response, market); } parseTicker(ticker, market = undefined) { // // fetchTicker // // { // "market":"ETH-BTC", // "open":"0.022578", // "high":"0.023019", // "low":"0.022573", // "last":"0.023019", // "volume":"25.16366324", // "volumeQuote":"0.57333305", // "bid":"0.023039", // "bidSize":"0.53500578", // "ask":"0.023041", // "askSize":"0.47859202", // "timestamp":1590381666900 // } // const marketId = this.safeString(ticker, 'market'); const symbol = this.safeSymbol(marketId, market, '-'); const timestamp = this.safeInteger(ticker, 'timestamp'); const last = this.safeString(ticker, 'last'); const baseVolume = this.safeString(ticker, 'volume'); const quoteVolume = this.safeString(ticker, 'volumeQuote'); const open = this.safeString(ticker, 'open'); return this.safeTicker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'high': this.safeString(ticker, 'high'), 'low': this.safeString(ticker, 'low'), 'bid': this.safeString(ticker, 'bid'), 'bidVolume': this.safeString(ticker, 'bidSize'), 'ask': this.safeString(ticker, 'ask'), 'askVolume': this.safeString(ticker, 'askSize'), 'vwap': undefined, 'open': open, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }, market); } async fetchTickers(symbols = undefined, params = {}) { /** * @method * @name bitvavo#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @param {[string]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ await this.loadMarkets(); const response = await this.publicGetTicker24h(params); // // [ // { // "market":"ADA-BTC", // "open":"0.0000059595", // "high":"0.0000059765", // "low":"0.0000059595", // "last":"0.0000059765", // "volume":"2923.172", // "volumeQuote":"0.01743483", // "bid":"0.0000059515", // "bidSize":"1117.630919", // "ask":"0.0000059585", // "askSize":"809.999739", // "timestamp":1590382266324 // } // ] // return this.parseTickers(response, symbols); } async fetchTrades(symbol, since = undefined, limit = undefined, params = {}) { /** * @method * @name bitvavo#fetchTrades * @description get the list of most recent trades for a particular symbol * @param {string} symbol unified symbol of the market to fetch trades for * @param {int|undefined} since timestamp in ms of the earliest trade to fetch * @param {int|undefined} limit the maximum amount of trades to fetch * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades} */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'market': market['id'], // 'limit': 500, // default 500, max 1000 // 'start': since, // 'end': this.milliseconds (), // 'tradeIdFrom': '57b1159b-6bf5-4cde-9e2c-6bd6a5678baf', // 'tradeIdTo': '57b1159b-6bf5-4cde-9e2c-6bd6a5678baf', }; if (limit !== undefined) { request['limit'] = limit; } if (since !== undefined) { request['start'] = since; } const response = await this.publicGetMarketTrades(this.extend(request, params)); // // [ // { // "id":"94154c98-6e8b-4e33-92a8-74e33fc05650", // "timestamp":1590382761859, // "amount":"0.06026079", // "price":"8095.3", // "side":"buy" // } // ] // return this.parseTrades(response, market, since, limit); } parseTrade(trade, market = undefined) { // // fetchTrades (public) // // { // "id":"94154c98-6e8b-4e33-92a8-74e33fc05650", // "timestamp":1590382761859, // "amount":"0.06026079", // "price":"8095.3", // "side":"buy" // } // // createOrder, fetchOpenOrders, fetchOrders, editOrder (private) // // { // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", // "timestamp":1590505649245, // "amount":"0.249825", // "price":"183.49", // "taker":true, // "fee":"0.12038925", // "feeCurrency":"EUR", // "settled":true // } // // fetchMyTrades (private) // // { // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", // "orderId":"af76d6ce-9f7c-4006-b715-bb5d430652d0", // "timestamp":1590505649245, // "market":"ETH-EUR", // "side":"sell", // "amount":"0.249825", // "price":"183.49", // "taker":true, // "fee":"0.12038925", // "feeCurrency":"EUR", // "settled":true // } // // watchMyTrades (private) // // { // event: 'fill', // timestamp: 1590964470132, // market: 'ETH-EUR', // orderId: '85d082e1-eda4-4209-9580-248281a29a9a', // fillId: '861d2da5-aa93-475c-8d9a-dce431bd4211', // side: 'sell', // amount: '0.1', // price: '211.46', // taker: true, // fee: '0.056', // feeCurrency: 'EUR' // } // const priceString = this.safeString(trade, 'price'); const amountString = this.safeString(trade, 'amount'); const timestamp = this.safeInteger(trade, 'timestamp'); const side = this.safeString(trade, 'side'); const id = this.safeString2(trade, 'id', 'fillId'); const marketId = this.safeString(trade, 'market'); const symbol = this.safeSymbol(marketId, market, '-'); const taker = this.safeValue(trade, 'taker'); let takerOrMaker = undefined; if (taker !== undefined) { takerOrMaker = taker ? 'taker' : 'maker'; } const feeCostString = this.safeString(trade, 'fee'); let fee = undefined; if (feeCostString !== undefined) { const feeCurrencyId = this.safeString(trade, 'feeCurrency'); const feeCurrencyCode = this.safeCurrencyCode(feeCurrencyId); fee = { 'cost': feeCostString, 'currency': feeCurrencyCode, }; } const orderId = this.safeString(trade, 'orderId'); return this.safeTrade({ 'info': trade, 'id': id, 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'order': orderId, 'type': undefined, 'side': side, 'takerOrMaker': takerOrMaker, 'price': priceString, 'amount': amountString, 'cost': undefined, 'fee': fee, }, market); } async fetchTradingFees(params = {}) { /** * @method * @name bitvavo#fetchTradingFees * @description fetch the trading fees for multiple markets * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {object} a dictionary of [fee structures]{@link https://docs.ccxt.com/#/?id=fee-structure} indexed by market symbols */ await this.loadMarkets(); const response = await this.privateGetAccount(params); // // { // "fees": { // "taker": "0.0025", // "maker": "0.0015", // "volume": "10000.00" // } // } // const fees = this.safeValue(response, 'fees'); const maker = this.safeNumber(fees, 'maker'); const taker = this.safeNumber(fees, 'taker'); const result = {}; for (let i = 0; i < this.symbols.length; i++) { const symbol = this.symbols[i]; result[symbol] = { 'info': response, 'symbol': symbol, 'maker': maker, 'taker': taker, 'percentage': true, 'tierBased': true, }; } return result; } async fetchOrderBook(symbol, limit = undefined, params = {}) { /** * @method * @name bitvavo#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int|undefined} limit the maximum amount of order book entries to return * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'market': market['id'], }; if (limit !== undefined) { request['depth'] = limit; } const response = await this.publicGetMarketBook(this.extend(request, params)); // // { // "market":"BTC-EUR", // "nonce":35883831, // "bids":[ // ["8097.4","0.6229099"], // ["8097.2","0.64151283"], // ["8097.1","0.24966294"], // ], // "asks":[ // ["8097.5","1.36916911"], // ["8098.8","0.33462248"], // ["8099.3","1.12908646"], // ] // } // const orderbook = this.parseOrderBook(response, market['symbol']); orderbook['nonce'] = this.safeInteger(response, 'nonce'); return orderbook; } parseOHLCV(ohlcv, market = undefined) { // // [ // 1590383700000, // "8088.5", // "8088.5", // "8088.5", // "8088.5", // "0.04788623" // ] // return [ this.safeInteger(ohlcv, 0), this.safeNumber(ohlcv, 1), this.safeNumber(ohlcv, 2), this.safeNumber(ohlcv, 3), this.safeNumber(ohlcv, 4), this.safeNumber(ohlcv, 5), ]; } async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name bitvavo#fetchOHLCV * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @param {int|undefined} since timestamp in ms of the earliest candle to fetch * @param {int|undefined} limit the maximum amount of candles to fetch * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'market': market['id'], 'interval': this.safeString(this.timeframes, timeframe, timeframe), // 'limit': 1440, // default 1440, max 1440 // 'start': since, // 'end': this.milliseconds (), }; if (since !== undefined) { // https://github.com/ccxt/ccxt/issues/9227 const duration = this.parseTimeframe(timeframe); request['start'] = since; if (limit === undefined) { limit = 1440; } request['end'] = this.sum(since, limit * duration * 1000); } if (limit !== undefined) { request['limit'] = limit; // default 1440, max 1440 } const response = await this.publicGetMarketCandles(this.extend(request, params)); // // [ // [1590383700000,"8088.5","8088.5","8088.5","8088.5","0.04788623"], // [1590383580000,"8091.3","8091.5","8091.3","8091.5","0.04931221"], // [1590383520000,"8090.3","8092.7","8090.3","8092.5","0.04001286"], // ] // return this.parseOHLCVs(response, market, timeframe, since, limit); } parseBalance(response) { const result = { 'info': response, 'timestamp': undefined, 'datetime': undefined, }; for (let i = 0; i < response.length; i++) { const balance = response[i]; const currencyId = this.safeString(balance, 'symbol'); const code = this.safeCurrencyCode(currencyId); const account = this.account(); account['free'] = this.safeString(balance, 'available'); account['used'] = this.safeString(balance, 'inOrder'); result[code] = account; } return this.safeBalance(result); } async fetchBalance(params = {}) { /** * @method * @name bitvavo#fetchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ await this.loadMarkets(); const response = await this.privateGetBalance(params); // // [ // { // "symbol": "BTC", // "available": "1.57593193", // "inOrder": "0.74832374" // } // ] // return this.parseBalance(response); } async fetchDepositAddress(code, params = {}) { /** * @method * @name bitvavo#fetchDepositAddress * @description fetch the deposit address for a currency associated with this account * @param {string} code unified currency code * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {object} an [address structure]{@link https://docs.ccxt.com/#/?id=address-structure} */ await this.loadMarkets(); const currency = this.currency(code); const request = { 'symbol': currency['id'], }; const response = await this.privateGetDeposit(this.extend(request, params)); // // { // "address": "0x449889e3234514c45d57f7c5a571feba0c7ad567", // "paymentId": "10002653" // } // const address = this.safeString(response, 'address'); const tag = this.safeString(response, 'paymentId'); this.checkAddress(address); return { 'currency': code, 'address': address, 'tag': tag, 'network': undefined, 'info': response, }; } async createOrder(symbol, type, side, amount, price = undefined, params = {}) { /** * @method * @name bitvavo#createOrder * @description create a trade order * @see https://docs.bitvavo.com/#tag/Orders/paths/~1order/post * @param {string} symbol unified symbol of the market to create an order in * @param {string} type 'market' or 'limit' * @param {string} side 'buy' or 'sell' * @param {float} amount how much of currency you want to trade in units of base currency * @param {float|undefined} price the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders * @param {object} params extra parameters specific to the bitvavo api endpoint * @param {string|undefined} params.timeInForce "GTC", "IOC", or "PO" * @param {float|undefined} params.stopPrice The price at which a trigger order is triggered at * @param {float|undefined} params.triggerPrice The price at which a trigger order is triggered at * @param {bool|undefined} params.postOnly If true, the order will only be posted to the order book and not executed immediately * @param {float|undefined} params.stopLossPrice The price at which a stop loss order is triggered at * @param {float|undefined} params.takeProfitPrice The price at which a take profit order is triggered at * @param {string|undefined} params.triggerType "price" * @param {string|undefined} params.triggerReference "lastTrade", "bestBid", "bestAsk", "midPrice" Only for stop orders: Use this to determine which parameter will trigger the order * @param {string|undefined} params.selfTradePrevention "decrementAndCancel", "cancelOldest", "cancelNewest", "cancelBoth" * @param {bool|undefined} params.disableMarketProtection don't cancel if the next fill price is 10% worse than the best fill price * @param {bool|undefined} params.responseRequired Set this to 'false' when only an acknowledgement of success or failure is required, this is faster. * @returns {object} an [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ await this.loadMarkets(); const market = this.market(symbol); const request = { 'market': market['id'], 'side': side, 'orderType': type, }; const isMarketOrder = (type === 'market') || (type === 'stopLoss') || (type === 'takeProfit'); const isLimitOrder = (type === 'limit') || (type === 'stopLossLimit') || (type === 'takeProfitLimit'); const timeInForce = this.safeString(params, 'timeInForce'); let triggerPrice = this.safeStringN(params, ['triggerPrice', 'stopPrice', 'triggerAmount']); const postOnly = this.isPostOnly(isMarketOrder, false, params); const stopLossPrice = this.safeValue(params, 'stopLossPrice'); // trigger when price crosses from above to below this value const takeProfitPrice = this.safeValue(params, 'takeProfitPrice'); // trigger when price crosses from below to above this value params = this.omit(params, ['timeInForce', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice']); if (isMarketOrder) { let cost = undefined; if (price !== undefined) { const priceString = this.numberToString(price); const amountString = this.numberToString(amount); const quoteAmount = Precise.stringMul(amountString, priceString); cost = this.parseNumber(quoteAmount); } else { cost = this.safeNumber(params, 'cost'); } if (cost !== undefined) { const precision = this.currency(market['quote'])['precision']; request['amountQuote'] = this.decimalToPrecision(cost, TRUNCATE, precision, this.precisionMode); } else { request['amount'] = this.amountToPrecision(symbol, amount); } params = this.omit(params, ['cost']); } else if (isLimitOrder) { request['price'] = this.priceToPrecision(symbol, price); request['amount'] = this.amountToPrecision(symbol, amount); } const isTakeProfit = (takeProfitPrice !== undefined) || (type === 'takeProfit') || (type === 'takeProfitLimit'); const isStopLoss = (stopLossPrice !== undefined) || (triggerPrice !== undefined) && (!isTakeProfit) || (type === 'stopLoss') || (type === 'stopLossLimit'); if (isStopLoss) { if (stopLossPrice !== undefined) { triggerPrice = stopLossPrice; } request['orderType'] = isMarketOrder ? 'stopLoss' : 'stopLossLimit'; } else if (isTakeProfit) { if (takeProfitPrice !== undefined) { triggerPrice = takeProfitPrice; } request['orderType'] = isMarketOrder ? 'takeProfit' : 'takeProfitLimit'; } if (triggerPrice !== undefined) { request['triggerAmount'] = this.priceToPrecision(symbol, triggerPrice); request['triggerType'] = 'price'; request['triggerReference'] = 'lastTrade'; // 'bestBid', 'bestAsk', 'midPrice' } if ((timeInForce !== undefined) && (timeInForce !== 'PO')) { request['timeInForce'] = timeInForce; } if (postOnly) { request['postOnly'] = true; } const response = await this.privatePostOrder(this.extend(request, params)); // // { // "orderId":"dec6a640-5b4c-45bc-8d22-3b41c6716630", // "market":"DOGE-EUR", // "created":1654789135146, // "updated":1654789135153, // "status":"new", // "side":"buy", // "orderType":"stopLossLimit", // "amount":"200", // "amountRemaining":"200", // "price":"0.07471", // "triggerPrice":"0.0747", // "triggerAmount":"0.0747", // "triggerType":"price", // "triggerReference":"lastTrade", // "onHold":"14.98", // "onHoldCurrency":"EUR", // "filledAmount":"0", // "filledAmountQuote":"0", // "feePaid":"0", // "feeCurrency":"EUR", // "fills":[ // filled with market orders only // { // "id":"b0c86aa5-6ed3-4a2d-ba3a-be9a964220f4", // "timestamp":1590505649245, // "amount":"0.249825", // "price":"183.49", // "taker":true, // "fee":"0.12038925", // "feeCurrency":"EUR", // "settled":true // } // ], // "selfTradePrevention":"decrementAndCancel", // "visible":true, // "timeInForce":"GTC", // "postOnly":false // } // return this.parseOrder(response, market); } async editOrder(id, symbol, type, side, amount = undefined, price = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); let request = {}; const amountRemaining = this.safeNumber(params, 'amountRemaining'); params = this.omit(params, 'amountRemaining'); if (price !== undefined) { request['price'] = this.priceToPrecision(symbol, price); } if (amount !== undefined) { request['amount'] = this.amountToPrecision(symbol, amount); } if (amountRemaining !== undefined) { request['amountRemaining'] = this.amountToPrecision(symbol, amountRemaining); } request = this.extend(request, params); if (Object.keys(request).length) { request['orderId'] = id; request['market'] = market['id']; const response = await this.privatePutOrder(this.extend(request, params)); return this.parseOrder(response, market); } else { throw new ArgumentsRequired(this.id + ' editOrder() requires an amount argument, or a price argument, or non-empty params'); } } async cancelOrder(id, symbol = undefined, params = {}) { /** * @method * @name bitvavo#cancelOrder * @description cancels an open order * @param {string} id order id * @param {string} symbol unified symbol of the market the order was made in * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure} */ if (symbol === undefined) { throw new ArgumentsRequired(this.id + ' cancelOrder() requires a symbol argument'); } await this.loadMarkets(); const market = this.market(symbol); const request = { 'orderId': id, 'market': market['id'], }; const response = await this.privateDeleteOrder(this.extend(request, params)); // // { // "orderId": "2e7ce7fc-44e2-4d80-a4a7-d079c4750b61" // } // return this.parseOrder(response, market); } async cancelAllOrders(symbol = undefined, params = {}) { /** * @method * @name bitvavo#cancelAllOrders * @description cancel all open orders * @param {string|undefined} symbol unified market symbol, only orders in the market of this symbol are cancelled when symbol is not undefined * @param {object} params extra parameters specific to the bitvavo api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure} */ await this.loadMarkets(); const request = {}; let market = undefined; if (symbol !== undefined) { market = this.market(symbol); request['market'] = market['id']; } const response = await this.privateDeleteOrders(this.extend(request, params)); // // [ // { // "orderId": "1be6d0df-d5dc-4b53-a250-3376f3b393e6" // } // ] // return this.parseOrders(response, market); } async fetchOrder(id, symbol = undefined, params = {}) { /**