@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
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/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 = {}) {
/**