diff --git a/bisq b/bisq index 17abc0b..c737a20 160000 --- a/bisq +++ b/bisq @@ -1 +1 @@ -Subproject commit 17abc0bc8dc93ab836040662017f81a5c391c436 +Subproject commit c737a203673263c7c599ee6624e5803ec6af83df diff --git a/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/src/main/java/bisq/price/spot/ExchangeRateProvider.java index b483f97..2af7e36 100644 --- a/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -32,7 +32,9 @@ import org.knowm.xchange.service.marketdata.params.Params; import org.springframework.core.env.Environment; +import javax.annotation.Nullable; import java.io.IOException; +import java.math.BigDecimal; import java.time.Duration; import java.util.*; import java.util.function.Predicate; @@ -322,4 +324,31 @@ protected long getMarketDataCallDelay() { protected boolean requiresFilterDuringBulkTickerRetrieval() { return false; } + + /** + * + * @return true if the implementation of this ExchangeRateProvider already consider currencies + * blue markets if any. Defaults to false. + */ + public boolean alreadyConsidersBlueMarkets() { + return false; + } + + /** + * @param originalRate original official rate for a certain currency. E.g. rate for ARS for Argentina PESO + * @return a new exchange rate if the implementation of this price provider already tackles real vs official + * market prices for the given currency. + * e.g. for FIAT ARS official rates are not the ones using in the free trading world. + * Most currencies won't need this, so defaults to null. + */ + public @Nullable ExchangeRate maybeUpdateBlueMarketPriceGapForRate(ExchangeRate originalRate, Double blueMarketGapForCurrency) { + if ("ARS".equalsIgnoreCase(originalRate.getCurrency())) { + double blueRate = originalRate.getPrice() * blueMarketGapForCurrency; + return new ExchangeRate(originalRate.getCurrency(), + BigDecimal.valueOf(blueRate), + new Date(originalRate.getTimestamp()), + originalRate.getProvider()); + } + return null; + } } diff --git a/src/main/java/bisq/price/spot/ExchangeRateService.java b/src/main/java/bisq/price/spot/ExchangeRateService.java index 6005e10..16e8cab 100644 --- a/src/main/java/bisq/price/spot/ExchangeRateService.java +++ b/src/main/java/bisq/price/spot/ExchangeRateService.java @@ -21,6 +21,8 @@ import bisq.core.util.InlierUtil; +import bisq.price.util.bluelytics.BlueLyticsUSDRate; +import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; @@ -36,7 +38,6 @@ @Service class ExchangeRateService { protected final Logger log = LoggerFactory.getLogger(this.getClass()); - private final Environment env; private final List providers; @@ -57,9 +58,8 @@ public Map getAllMarketPrices() { Map aggregateExchangeRates = getAggregateExchangeRates(); providers.forEach(p -> { - if (p.get() == null) - return; - Set exchangeRates = p.get(); + Set exchangeRates = providerCurrentExchangeRates(p); + if (exchangeRates == null) return; // Specific metadata fields for specific providers are expected by the client, // mostly for historical reasons @@ -68,6 +68,7 @@ public Map getAllMarketPrices() { metadata.putAll(getMetadata(p, exchangeRates)); }); + LinkedHashMap result = new LinkedHashMap<>(metadata); // Use a sorted list by currency code to make comparison of json data between // different price nodes easier @@ -78,6 +79,55 @@ public Map getAllMarketPrices() { return result; } + /** + * Please do not call provider.get(), use this method instead to consider currencies with blue markets + * @param provider to get the rates from + * @return the exchange rates for the different currencies the provider supports considering bluemarket rates if any + */ + public Set providerCurrentExchangeRates(ExchangeRateProvider provider) { + Map blueMarketGapForCurrency = this.fetchBlueMarketGapForCurrencies(); + Set originalExchangeRates = provider.get(); + if (originalExchangeRates == null) + return null; + + Set exchangeRates = new HashSet<>(); + + if (provider.alreadyConsidersBlueMarkets() || + (Sets.intersection(originalExchangeRates.stream().map(ExchangeRate::getCurrency).collect(Collectors.toSet()), + blueMarketGapForCurrency.keySet())).isEmpty()) { + exchangeRates.addAll(originalExchangeRates); + } else { + this.addRatesUpdatingBlueGaps(blueMarketGapForCurrency, provider, exchangeRates, originalExchangeRates); + } + return exchangeRates; + } + + private void addRatesUpdatingBlueGaps(Map blueMarketGapForCurrency, ExchangeRateProvider provider, Set exchangeRates, Set originalExchangeRates) { + originalExchangeRates.forEach(er -> { + // default to original rate + ExchangeRate exchangeRateToUse = er; + if (blueMarketGapForCurrency.containsKey(er.getCurrency())) { + ExchangeRate updatedExchangeRate = provider.maybeUpdateBlueMarketPriceGapForRate(er, blueMarketGapForCurrency.get(er.getCurrency())); + if (updatedExchangeRate != null) { + exchangeRateToUse = updatedExchangeRate; +// this.log.info(String.format("Replaced original %s rate of $%s to $%s", er.getCurrency(), BigDecimal.valueOf(er.getPrice()).toEngineeringString(), BigDecimal.valueOf(updatedExchangeRate.getPrice()).toEngineeringString())); + } + } + exchangeRates.add(exchangeRateToUse); + }); + } + + private Map fetchBlueMarketGapForCurrencies() { + Map blueMarketGapForCurrencies = new HashMap<>(); + // ARS + Double arsBlueMultiplier = BlueLyticsUSDRate.getInstance().blueGapMultiplier(); + if (!arsBlueMultiplier.isNaN()) { +// this.log.info("Updated ARS/USD multiplier is " + arsBlueMultiplier); + blueMarketGapForCurrencies.put("ARS", arsBlueMultiplier); + } + return blueMarketGapForCurrencies; + } + /** * For each currency, create an aggregate {@link ExchangeRate} based on the currency's * rates from all providers. If multiple providers have rates for the currency, then @@ -164,9 +214,10 @@ private double getOutlierStdDeviation() { private Map> getCurrencyCodeToExchangeRates() { Map> currencyCodeToExchangeRates = new HashMap<>(); for (ExchangeRateProvider p : providers) { - if (p.get() == null) + Set exchangeRates = providerCurrentExchangeRates(p); + if (exchangeRates == null) continue; - for (ExchangeRate exchangeRate : p.get()) { + for (ExchangeRate exchangeRate : exchangeRates) { String currencyCode = exchangeRate.getCurrency(); if (currencyCodeToExchangeRates.containsKey(currencyCode)) { List l = new ArrayList<>(currencyCodeToExchangeRates.get(currencyCode)); diff --git a/src/main/java/bisq/price/spot/providers/CoinGecko.java b/src/main/java/bisq/price/spot/providers/CoinGecko.java index 556001f..5f70fda 100644 --- a/src/main/java/bisq/price/spot/providers/CoinGecko.java +++ b/src/main/java/bisq/price/spot/providers/CoinGecko.java @@ -17,10 +17,14 @@ package bisq.price.spot.providers; +import bisq.asset.Coin; import bisq.price.spot.ExchangeRate; import bisq.price.spot.ExchangeRateProvider; import bisq.price.util.coingecko.CoinGeckoMarketData; +import bisq.price.util.coingecko.CoinGeckoTicker; +import lombok.Getter; +import lombok.Setter; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.env.Environment; import org.springframework.http.RequestEntity; @@ -33,16 +37,13 @@ import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.Date; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @Component class CoinGecko extends ExchangeRateProvider { - + private static final String GET_EXCHANGE_RATES_URL = "https://api.coingecko.com/api/v3/exchange_rates"; private final RestTemplate restTemplate = new RestTemplate(); public CoinGecko(Environment env) { @@ -60,18 +61,16 @@ public Set doGet() { Predicate isDesiredFiatPair = t -> getSupportedFiatCurrencies().contains(t.getKey()); Predicate isDesiredCryptoPair = t -> getSupportedCryptoCurrencies().contains(t.getKey()); - getMarketData().getRates().entrySet().stream() + Map rates = getMarketData().getRates(); + rates.entrySet().stream() .filter(isDesiredFiatPair.or(isDesiredCryptoPair)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) .forEach((key, ticker) -> { - boolean useInverseRate = false; - if (getSupportedCryptoCurrencies().contains(key)) { - // Use inverse rate for alts, because the API returns the - // conversion rate in the opposite direction than what we need - // API returns the BTC/Alt rate, we need the Alt/BTC rate - useInverseRate = true; - } + boolean useInverseRate = getSupportedCryptoCurrencies().contains(key); + // Use inverse rate for alts, because the API returns the + // conversion rate in the opposite direction than what we need + // API returns the BTC/Alt rate, we need the Alt/BTC rate BigDecimal rate = ticker.getValue(); // Find the inverse rate, while using enough decimals to reflect very @@ -87,7 +86,6 @@ public Set doGet() { this.getName() )); }); - return result; } @@ -95,7 +93,7 @@ private CoinGeckoMarketData getMarketData() { return restTemplate.exchange( RequestEntity .get(UriComponentsBuilder - .fromUriString("https://api.coingecko.com/api/v3/exchange_rates").build() + .fromUriString(CoinGecko.GET_EXCHANGE_RATES_URL).build() .toUri()) .build(), new ParameterizedTypeReference() { diff --git a/src/main/java/bisq/price/spot/providers/CryptoYa.java b/src/main/java/bisq/price/spot/providers/CryptoYa.java index cfd431e..6a20073 100644 --- a/src/main/java/bisq/price/spot/providers/CryptoYa.java +++ b/src/main/java/bisq/price/spot/providers/CryptoYa.java @@ -51,6 +51,11 @@ public CryptoYa(Environment env) { super(env, "CRYPTOYA", "cryptoya", Duration.ofMinutes(1)); } + @Override + public boolean alreadyConsidersBlueMarkets() { + return true; + } + /** * * @return average price buy/sell price averaging different providers suported by cryptoya api diff --git a/src/main/java/bisq/price/util/bluelytics/BlueLyticsUSDRate.java b/src/main/java/bisq/price/util/bluelytics/BlueLyticsUSDRate.java new file mode 100644 index 0000000..62fe2e2 --- /dev/null +++ b/src/main/java/bisq/price/util/bluelytics/BlueLyticsUSDRate.java @@ -0,0 +1,86 @@ +package bisq.price.util.bluelytics; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.RequestEntity; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.time.Instant; +import java.util.Date; +import java.util.Objects; + +/** + * Util singleton object to update and provider ARS/USD blue market gap against the oficial rate. + * This is useful for example, to estimate ARS/BTC blue (real) market rate in a country with heavy currency controls + */ +public final class BlueLyticsUSDRate { + private static final long MIN_REFRESH_WINDOW = 3600000; // 1hr + private static final String GET_USD_EXCHANGE_RATES_ARG_URL = "https://api.bluelytics.com.ar/v2/latest"; + private static final BlueLyticsUSDRate instance = new BlueLyticsUSDRate(); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final RestTemplate restTemplate = new RestTemplate(); + private Double lastBlueGap; + private Long lastRefresh; + + private BlueLyticsUSDRate() { + lastRefresh = null; + lastBlueGap = null; + } + + public static BlueLyticsUSDRate getInstance() { + return BlueLyticsUSDRate.instance; + } + + /** + * + * @return current ARS/USD gap multiplier to get from official rate to free market rate. + * If not available returns Nan + */ + public Double blueGapMultiplier() { + if (this.lastRefresh == null) { + this.refreshBlueGap(); + } else { + this.maybeLaunchAsyncRefresh(); + } + return Objects.requireNonNullElse(this.lastBlueGap, Double.NaN); + } + + /** + * if enough time {@see BlueLyticsUSDRate.MIN_FRESH_WINDOW} has pass from the last refresh, launch async refresh + */ + private void maybeLaunchAsyncRefresh() { + if (this.lastRefresh != null && + Date.from(Instant.now()).getTime() > this.lastRefresh + BlueLyticsUSDRate.MIN_REFRESH_WINDOW) { + this.launchAsyncRefresh(); + } + } + + private synchronized void launchAsyncRefresh() { + new Thread(this::refreshBlueGap).start(); + } + + private void refreshBlueGap() { + try { + // the last_update value is different than the last one and also launch the update if 1 hour passed ? + this.lastBlueGap = Objects.requireNonNull(restTemplate.exchange( + RequestEntity + .get(UriComponentsBuilder + .fromUriString(BlueLyticsUSDRate.GET_USD_EXCHANGE_RATES_ARG_URL).build() + .toUri()) + .build(), + new ParameterizedTypeReference() { + } + ).getBody()).gapSellMultiplier(); + this.lastRefresh = new Date().getTime(); + } catch (Exception e) { + this.logger.error("Failed to fetch updated bluelytics gap multiplier", e); + } + } + + @Override + protected Object clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException("Cannot clone Singleton"); + } +} diff --git a/src/main/java/bisq/price/util/bluelytics/BlueLyticsUsdRates.java b/src/main/java/bisq/price/util/bluelytics/BlueLyticsUsdRates.java new file mode 100644 index 0000000..a4a2e0b --- /dev/null +++ b/src/main/java/bisq/price/util/bluelytics/BlueLyticsUsdRates.java @@ -0,0 +1,31 @@ +package bisq.price.util.bluelytics; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +public class BlueLyticsUsdRates { + @Getter + @Setter + public static class USDRate { + Double value_avg; + Double value_sell; + Double value_buy; + } + + BlueLyticsUsdRates.USDRate oficial; + BlueLyticsUsdRates.USDRate blue; + Date last_update; + + /** + * + * @return the sell multiplier to go from oficial to blue market for ARS/USD + * if its not available, returns NaN + */ + public Double gapSellMultiplier() { + return this.blue.value_sell / this.oficial.value_sell; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9564a1d..399ab37 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,7 +7,7 @@ bisq.price.mining.providers.mempoolHostname.2=mempool.emzy.de bisq.price.mining.providers.mempoolHostname.3=mempool.ninja bisq.price.mining.providers.mempoolHostname.4=mempool.bisq.services # bisq.price.mining.providers.mempoolHostname.5=someHostOrIP -bisq.price.fiatcurrency.excluded=LBP,ARS +bisq.price.fiatcurrency.excluded=LBP bisq.price.fiatcurrency.excludedByProvider=HUOBI:BRL bisq.price.cryptocurrency.excluded= bisq.price.outlierStdDeviation=2.2 diff --git a/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java b/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java index 80cd4be..78c2a1a 100644 --- a/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java +++ b/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java @@ -44,12 +44,12 @@ private void checkProviderCurrencyPairs(ExchangeRateProvider exchangeProvider, S .collect(Collectors.toSet()); Set supportedFiatCurrenciesRetrieved = exchangeProvider.getSupportedFiatCurrencies().stream() - .filter(f -> retrievedRatesCurrencies.contains(f)) + .filter(retrievedRatesCurrencies::contains) .collect(Collectors.toCollection(TreeSet::new)); log.info("Retrieved rates for supported fiat currencies: " + supportedFiatCurrenciesRetrieved); Set supportedCryptoCurrenciesRetrieved = exchangeProvider.getSupportedCryptoCurrencies().stream() - .filter(c -> retrievedRatesCurrencies.contains(c)) + .filter(retrievedRatesCurrencies::contains) .collect(Collectors.toCollection(TreeSet::new)); log.info("Retrieved rates for supported altcoins: " + supportedCryptoCurrenciesRetrieved); @@ -57,7 +57,7 @@ private void checkProviderCurrencyPairs(ExchangeRateProvider exchangeProvider, S exchangeProvider.getSupportedCryptoCurrencies(), exchangeProvider.getSupportedFiatCurrencies()); - Set unsupportedCurrencies = Sets.difference(retrievedRatesCurrencies, supportedCurrencies); + Set unsupportedCurrencies = Sets.difference(retrievedRatesCurrencies, supportedCurrencies); assertTrue(unsupportedCurrencies.isEmpty(), "Retrieved exchange rates contain unsupported currencies: " + unsupportedCurrencies); } diff --git a/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java b/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java index 5e8c50a..ce6c8c9 100644 --- a/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java +++ b/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java @@ -31,6 +31,7 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.StandardEnvironment; +import javax.annotation.Nullable; import java.time.Duration; import java.util.*; import java.util.stream.Collectors; @@ -71,7 +72,7 @@ public void getAllMarketPrices_withNoExchangeRates_logs_Exception() { Map retrievedData = service.getAllMarketPrices(); - doSanityChecksForRetrievedDataSingleProvider(retrievedData, dummyProvider, numberOfCurrencyPairsOnExchange); + doSanityChecksForRetrievedDataSingleProvider(service, retrievedData, dummyProvider, numberOfCurrencyPairsOnExchange); // No exchange rates provided by this exchange, two things should happen // A) the timestamp should be set to 0 @@ -97,7 +98,7 @@ public void getAllMarketPrices_withSingleExchangeRate() { Map retrievedData = service.getAllMarketPrices(); - doSanityChecksForRetrievedDataSingleProvider(retrievedData, dummyProvider, numberOfCurrencyPairsOnExchange); + doSanityChecksForRetrievedDataSingleProvider(service, retrievedData, dummyProvider, numberOfCurrencyPairsOnExchange); // One rate was provided by this provider, so the timestamp should not be 0 assertNotEquals(0L, retrievedData.get(dummyProvider.getPrefix() + "Ts")); @@ -112,7 +113,7 @@ public void getAllMarketPrices_withMultipleProviders_differentCurrencyCodes() { Map retrievedData = service.getAllMarketPrices(); - doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(dummyProvider1, dummyProvider2)); + doSanityChecksForRetrievedDataMultipleProviders(service, retrievedData, asList(dummyProvider1, dummyProvider2)); // One rate was provided by each provider in this service, so the timestamp // (for both providers) should not be 0 @@ -120,6 +121,27 @@ public void getAllMarketPrices_withMultipleProviders_differentCurrencyCodes() { assertNotEquals(0L, retrievedData.get(dummyProvider2.getPrefix() + "Ts")); } + /** + * Test scenario + */ + @Test + public void getAllMarketPrices_oneProvider_considerBlueUpdates() { + String excludedCcvString = "LBP"; + Set rateCurrencyCodes = Sets.newHashSet("ARS", "USD", "LBP", "EUR"); + String providerExcludedCcvString = "HUOBI:BRL,BINANCE:GBP,BINANCE:SEK"; + Environment mockedEnvironment = mock(Environment.class); + when(mockedEnvironment.getProperty(eq("bisq.price.cryptocurrency.excluded"), anyString())).thenReturn(""); + when(mockedEnvironment.getProperty(eq("bisq.price.fiatcurrency.excluded"), anyString())).thenReturn(excludedCcvString); + when(mockedEnvironment.getProperty(eq("bisq.price.fiatcurrency.excludedByProvider"), anyString())).thenReturn(providerExcludedCcvString); + ExchangeRateProvider dummyProvider = buildDummyExchangeRateProvider(rateCurrencyCodes, mockedEnvironment); + ExchangeRateService service = new ExchangeRateService(mockedEnvironment, List.of(dummyProvider)); + + Map retrievedData = service.getAllMarketPrices(); + + doSanityChecksForRetrievedDataMultipleProviders(service, retrievedData, List.of(dummyProvider)); + + } + /** * Tests the scenario when multiple providers have rates for the same currencies */ @@ -130,14 +152,14 @@ public void getAllMarketPrices_withMultipleProviders_overlappingCurrencyCodes() Set rateCurrencyCodes = Sets.newHashSet("CURRENCY-1", "CURRENCY-2", "CURRENCY-3"); // Create several dummy providers, each providing their own rates for the same set of currencies - ExchangeRateProvider dummyProvider1 = buildDummyExchangeRateProvider(rateCurrencyCodes); - ExchangeRateProvider dummyProvider2 = buildDummyExchangeRateProvider(rateCurrencyCodes); + ExchangeRateProvider dummyProvider1 = buildDummyExchangeRateProvider(rateCurrencyCodes, null); + ExchangeRateProvider dummyProvider2 = buildDummyExchangeRateProvider(rateCurrencyCodes, null); ExchangeRateService service = new ExchangeRateService(new StandardEnvironment(), asList(dummyProvider1, dummyProvider2)); Map retrievedData = service.getAllMarketPrices(); - doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(dummyProvider1, dummyProvider2)); + doSanityChecksForRetrievedDataMultipleProviders(service, retrievedData, asList(dummyProvider1, dummyProvider2)); // At least one rate was provided by each provider in this service, so the // timestamp (for both providers) should not be 0 @@ -216,17 +238,18 @@ public Set doGet() { /** * Performs generic sanity checks on the response format and contents. * + * @param service being tested {@link ExchangeRateService} * @param retrievedData Response data retrieved from the {@link ExchangeRateService} * @param provider {@link ExchangeRateProvider} available to the * {@link ExchangeRateService} * @param numberOfCurrencyPairsOnExchange Number of currency pairs this exchange was * initiated with */ - private void doSanityChecksForRetrievedDataSingleProvider(Map retrievedData, + private void doSanityChecksForRetrievedDataSingleProvider(ExchangeRateService service, Map retrievedData, ExchangeRateProvider provider, int numberOfCurrencyPairsOnExchange) { // Check response structure - doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(provider)); + doSanityChecksForRetrievedDataMultipleProviders(service, retrievedData, asList(provider)); // Check that the amount of provided exchange rates matches expected value // For one provider, the amount of rates of that provider should be the total @@ -238,11 +261,12 @@ private void doSanityChecksForRetrievedDataSingleProvider(Map re /** * Performs generic sanity checks on the response format and contents. * + * @param service being tested {@link ExchangeRateService} * @param retrievedData Response data retrieved from the {@link ExchangeRateService} * @param providers List of all {@link ExchangeRateProvider#getPrefix()} the * {@link ExchangeRateService} uses */ - private void doSanityChecksForRetrievedDataMultipleProviders(Map retrievedData, + private void doSanityChecksForRetrievedDataMultipleProviders(ExchangeRateService service, Map retrievedData, List providers) { // Check the correct amount of entries were present in the service response: // The timestamp and the count fields are per provider, so N providers means N @@ -279,7 +303,7 @@ private void doSanityChecksForRetrievedDataMultipleProviders(Map // Collect all ExchangeRates from all providers and group them by currency code Map> currencyCodeToExchangeRatesFromProviders = new HashMap<>(); for (ExchangeRateProvider p : providers) { - for (ExchangeRate exchangeRate : p.get()) { + for (ExchangeRate exchangeRate : service.providerCurrentExchangeRates(p)) { String currencyCode = exchangeRate.getCurrency(); if (currencyCodeToExchangeRatesFromProviders.containsKey(currencyCode)) { List l = new ArrayList<>(currencyCodeToExchangeRatesFromProviders.get(currencyCode)); @@ -355,9 +379,9 @@ protected Set doGet() { return dummyProvider; } - private ExchangeRateProvider buildDummyExchangeRateProvider(Set rateCurrencyCodes) { + private ExchangeRateProvider buildDummyExchangeRateProvider(Set rateCurrencyCodes, @Nullable Environment env) { ExchangeRateProvider dummyProvider = new ExchangeRateProvider( - new StandardEnvironment(), + env == null ? new StandardEnvironment() : env, "ExchangeName-" + getRandomAlphaNumericString(5), "EXCH-" + getRandomAlphaNumericString(3), Duration.ofDays(1)) { diff --git a/src/test/java/bisq/price/spot/providers/CoinGeckoTest.java b/src/test/java/bisq/price/spot/providers/CoinGeckoTest.java index ca453c2..8d13e21 100644 --- a/src/test/java/bisq/price/spot/providers/CoinGeckoTest.java +++ b/src/test/java/bisq/price/spot/providers/CoinGeckoTest.java @@ -29,5 +29,4 @@ public class CoinGeckoTest extends AbstractExchangeRateProviderTest { public void doGet_successfulCall() { doGet_successfulCall(new CoinGecko(new StandardEnvironment())); } - }