From f2085b4bf0b22f13e74314551a716c618f946bad Mon Sep 17 00:00:00 2001 From: cd2357 Date: Tue, 16 Jun 2020 09:06:53 +0200 Subject: [PATCH 01/26] Simplify validation in ExchangeRateServiceTest Update sanity check methods to allow for deeper and more comprehensive validations of the input data. Accept full ExchangeRateProviders in the method signatures, instead of just the provider prefix, to allow for more complex sanity checks within those validation methods. --- .../price/spot/ExchangeRateServiceTest.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java index 43473974647..a3f34853917 100644 --- a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java +++ b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.slf4j.LoggerFactory; @@ -74,8 +75,7 @@ public void getAllMarketPrices_withNoExchangeRates_logs_Exception() { Map retrievedData = service.getAllMarketPrices(); - doSanityChecksForRetrievedDataSingleProvider( - retrievedData, dummyProvider.getPrefix(), numberOfCurrencyPairsOnExchange); + doSanityChecksForRetrievedDataSingleProvider(retrievedData, dummyProvider, numberOfCurrencyPairsOnExchange); // No exchange rates provided by this exchange, two things should happen // A) the timestamp should be set to 0 @@ -101,8 +101,7 @@ public void getAllMarketPrices_withSingleExchangeRate() { Map retrievedData = service.getAllMarketPrices(); - doSanityChecksForRetrievedDataSingleProvider( - retrievedData, dummyProvider.getPrefix(), numberOfCurrencyPairsOnExchange); + doSanityChecksForRetrievedDataSingleProvider(retrievedData, dummyProvider, numberOfCurrencyPairsOnExchange); // One rate was provided by this provider, so the timestamp should not be 0 assertNotEquals(0L, retrievedData.get(dummyProvider.getPrefix() + "Ts")); @@ -117,8 +116,7 @@ public void getAllMarketPrices_withMultipleProviders() { Map retrievedData = service.getAllMarketPrices(); - doSanityChecksForRetrievedDataMultipleProviders(retrievedData, - asList(dummyProvider1.getPrefix(), dummyProvider2.getPrefix())); + doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(dummyProvider1, dummyProvider2)); // One rate was provided by each provider in this service, so the timestamp // (for both providers) should not be 0 @@ -130,14 +128,14 @@ public void getAllMarketPrices_withMultipleProviders() { * Performs generic sanity checks on the response format and contents. * * @param retrievedData Response data retrieved from the {@link ExchangeRateService} - * @param providerPrefix {@link ExchangeRateProvider#getPrefix()} + * @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, - String providerPrefix, + ExchangeRateProvider provider, int numberOfCurrencyPairsOnExchange) { // Check response structure - doSanityChecksForRetrievedDataMultipleProviders(retrievedData, asList(providerPrefix)); + doSanityChecksForRetrievedDataMultipleProviders(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 amount of rates in the response @@ -149,18 +147,19 @@ private void doSanityChecksForRetrievedDataSingleProvider(Map re * Performs generic sanity checks on the response format and contents. * * @param retrievedData Response data retrieved from the {@link ExchangeRateService} - * @param providerPrefixes List of all {@link ExchangeRateProvider#getPrefix()} the + * @param providers List of all {@link ExchangeRateProvider#getPrefix()} the * {@link ExchangeRateService} uses */ private void doSanityChecksForRetrievedDataMultipleProviders(Map retrievedData, - List providerPrefixes) { + 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 // times those fields timestamp (x N) + count (x N) + price data (stored as a list // under the key "data"). So expected size is Nx2 + 1. - int n = providerPrefixes.size(); + int n = providers.size(); assertEquals(n * 2 + 1, retrievedData.size()); - for (String providerPrefix : providerPrefixes) { + for (ExchangeRateProvider provider : providers) { + String providerPrefix = provider.getPrefix(); assertNotNull(retrievedData.get(providerPrefix + "Ts")); assertNotNull(retrievedData.get(providerPrefix + "Count")); } From f6501155805b2524d1b0a88fb167e01615df2885 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Tue, 16 Jun 2020 15:08:44 +0200 Subject: [PATCH 02/26] ExchangeRateService: Support aggregate rates Add support for aggregate rates in the ExchangeRateService. If multiple ExchangeRateProviders contain rates for the same currency, then these rates will be automatically aggregated (averaged) into one. This allows the service to transparently scale to multiple providers for any specific currency. The clients index the rates received from the pricenode by currency code, which means they expect at most a single rate per currency. By aggregating rates from multiple providers into one per currency, the ExchangeRateService provides more accurate price data. At the same time, the service API data structure remains intact, thus preserving backward compatibility with all clients. --- .../bisq/price/spot/ExchangeRateService.java | 79 ++++++++++- .../price/spot/ExchangeRateServiceTest.java | 124 +++++++++++++++++- 2 files changed, 194 insertions(+), 9 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java index 63a29633b71..eff6abe9c6c 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java @@ -21,16 +21,23 @@ import org.springframework.stereotype.Service; +import java.math.BigDecimal; + import java.util.ArrayList; import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.OptionalDouble; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static java.util.Arrays.asList; + /** * High-level {@link ExchangeRate} data operations. */ @@ -53,26 +60,88 @@ public ExchangeRateService(List providers) { public Map getAllMarketPrices() { Map metadata = new LinkedHashMap<>(); - Map allExchangeRates = new LinkedHashMap<>(); + Map aggregateExchangeRates = getAggregateExchangeRates(); providers.forEach(p -> { Set exchangeRates = p.get(); + + // Specific metadata fields for specific providers are expected by the client, mostly for historical reasons + // Therefore, add metadata fields for all known providers + // Rates are encapsulated in the "data" map below metadata.putAll(getMetadata(p, exchangeRates)); - exchangeRates.forEach(e -> - allExchangeRates.put(e.getCurrency(), e) - ); }); return new LinkedHashMap() {{ putAll(metadata); // Use a sorted list by currency code to make comparision of json data between different // price nodes easier - List values = new ArrayList<>(allExchangeRates.values()); + List values = new ArrayList<>(aggregateExchangeRates.values()); values.sort(Comparator.comparing(ExchangeRate::getCurrency)); put("data", values); }}; } + /** + * 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 aggregate price = average of retrieved prices. + * If a single provider has rates for the currency, then aggregate price = the rate from that provider. + * + * @return Aggregate {@link ExchangeRate}s based on info from all providers, indexed by currency code + */ + private Map getAggregateExchangeRates() { + Map aggregateExchangeRates = new HashMap<>(); + + // Query all known providers and collect all exchange rates, grouped by currency code + Map> currencyCodeToExchangeRates = getCurrencyCodeToExchangeRates(); + + // For each currency code, calculate aggregate rate + currencyCodeToExchangeRates.forEach((currencyCode, exchangeRateList) -> { + ExchangeRate aggregateExchangeRate; + if (exchangeRateList.size() == 1) { + // If a single provider has rates for this currency, then aggregate = rate from that provider + aggregateExchangeRate = exchangeRateList.get(0); + } + else if (exchangeRateList.size() > 1) { + // If multiple providers have rates for this currency, then aggregate = average of the rates + OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average(); + double priceAvg = opt.orElseThrow(IllegalStateException::new); // List size > 1, so opt is always set + + aggregateExchangeRate = new ExchangeRate( + currencyCode, + BigDecimal.valueOf(priceAvg), + new Date(), // timestamp = time when avg is calculated + "Bisq-Aggregate"); + } + else { + // If the map was built incorrectly and this currency points to an empty list of rates, skip it + return; + } + aggregateExchangeRates.put(aggregateExchangeRate.getCurrency(), aggregateExchangeRate); + }); + + return aggregateExchangeRates; + } + + /** + * @return All {@link ExchangeRate}s from all providers, grouped by currency code + */ + private Map> getCurrencyCodeToExchangeRates() { + Map> currencyCodeToExchangeRates = new HashMap<>(); + for (ExchangeRateProvider p : providers) { + for (ExchangeRate exchangeRate : p.get()) { + String currencyCode = exchangeRate.getCurrency(); + if (currencyCodeToExchangeRates.containsKey(currencyCode)) { + List l = new ArrayList<>(currencyCodeToExchangeRates.get(currencyCode)); + l.add(exchangeRate); + currencyCodeToExchangeRates.put(currencyCode, l); + } else { + currencyCodeToExchangeRates.put(currencyCode, asList(exchangeRate)); + } + } + } + return currencyCodeToExchangeRates; + } + private Map getMetadata(ExchangeRateProvider provider, Set exchangeRates) { Map metadata = new LinkedHashMap<>(); diff --git a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java index a3f34853917..a697ad8574e 100644 --- a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java +++ b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java @@ -17,14 +17,20 @@ package bisq.price.spot; +import com.google.common.collect.Sets; + import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; import java.time.Duration; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.OptionalDouble; import java.util.Set; import java.util.stream.Collectors; @@ -108,7 +114,7 @@ public void getAllMarketPrices_withSingleExchangeRate() { } @Test - public void getAllMarketPrices_withMultipleProviders() { + public void getAllMarketPrices_withMultipleProviders_differentCurrencyCodes() { int numberOfCurrencyPairsOnExchange = 1; ExchangeRateProvider dummyProvider1 = buildDummyExchangeRateProvider(numberOfCurrencyPairsOnExchange); ExchangeRateProvider dummyProvider2 = buildDummyExchangeRateProvider(numberOfCurrencyPairsOnExchange); @@ -124,6 +130,31 @@ public void getAllMarketPrices_withMultipleProviders() { assertNotEquals(0L, retrievedData.get(dummyProvider2.getPrefix() + "Ts")); } + /** + * Tests the scenario when multiple providers have rates for the same currencies + */ + @Test + public void getAllMarketPrices_withMultipleProviders_overlappingCurrencyCodes() { + + // List of currencies for which multiple providers will have exchange rates + 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); + + ExchangeRateService service = new ExchangeRateService(asList(dummyProvider1, dummyProvider2)); + + Map retrievedData = service.getAllMarketPrices(); + + doSanityChecksForRetrievedDataMultipleProviders(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 + assertNotEquals(0L, retrievedData.get(dummyProvider1.getPrefix() + "Ts")); + assertNotEquals(0L, retrievedData.get(dummyProvider2.getPrefix() + "Ts")); + } + /** * Performs generic sanity checks on the response format and contents. * @@ -163,11 +194,61 @@ private void doSanityChecksForRetrievedDataMultipleProviders(Map assertNotNull(retrievedData.get(providerPrefix + "Ts")); assertNotNull(retrievedData.get(providerPrefix + "Count")); } - assertNotNull(retrievedData.get("data")); - // TODO Add checks for the case when rates for the same currency pair is retrieved from multiple providers + // Check validity of the data field + List retrievedRates = (List) retrievedData.get("data"); + assertNotNull(retrievedRates); + + // It should contain no duplicate ExchangeRate objects + int uniqueRates = Sets.newHashSet(retrievedRates).size(); + int totalRates = retrievedRates.size(); + assertEquals(uniqueRates, totalRates, "Found duplicate rates in data field"); + + // There should be only one ExchangeRate per currency + // In other words, even if multiple providers return rates for the same currency, the ExchangeRateService + // should expose only one (aggregate) ExchangeRate for that currency + Map currencyCodeToExchangeRateFromService = retrievedRates.stream() + .collect(Collectors.toMap( + ExchangeRate::getCurrency, exchangeRate -> exchangeRate + )); + int uniqueCurrencyCodes = currencyCodeToExchangeRateFromService.keySet().size(); + assertEquals(uniqueCurrencyCodes, uniqueRates, "Found currency code with multiple exchange rates"); + + // 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()) { + String currencyCode = exchangeRate.getCurrency(); + if (currencyCodeToExchangeRatesFromProviders.containsKey(currencyCode)) { + List l = new ArrayList<>(currencyCodeToExchangeRatesFromProviders.get(currencyCode)); + l.add(exchangeRate); + currencyCodeToExchangeRatesFromProviders.put(currencyCode, l); + } else { + currencyCodeToExchangeRatesFromProviders.put(currencyCode, asList(exchangeRate)); + } + } + } + + // For each ExchangeRate which is covered by multiple providers, ensure the rate value is an average + currencyCodeToExchangeRatesFromProviders.forEach((currencyCode, exchangeRateList) -> { + ExchangeRate rateFromService = currencyCodeToExchangeRateFromService.get(currencyCode); + double priceFromService = rateFromService.getPrice(); + + OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average(); + double priceAvgFromProviders = opt.getAsDouble(); + + // Ensure that the ExchangeRateService correctly aggregates exchange rates from multiple providers + // If multiple providers contain rates for a currency, the service should return a single aggregate rate + // Expected value for aggregate rate = avg(provider rates) + // This formula works for one, as well as many, providers for a specific currency + assertEquals(priceFromService, priceAvgFromProviders, "Service returned incorrect aggregate rate"); + }); } + /** + * @param numberOfRatesAvailable Number of exchange rates this provider returns + * @return Dummy {@link ExchangeRateProvider} providing rates for "numberOfRatesAvailable" random currency codes + */ private ExchangeRateProvider buildDummyExchangeRateProvider(int numberOfRatesAvailable) { ExchangeRateProvider dummyProvider = new ExchangeRateProvider( "ExchangeName-" + getRandomAlphaNumericString(5), @@ -187,7 +268,42 @@ protected Set doGet() { for (int i = 0; i < numberOfRatesAvailable; i++) { exchangeRates.add(new ExchangeRate( "DUM-" + getRandomAlphaNumericString(3), // random symbol, avoid duplicates - 0, + RandomUtils.nextDouble(1, 1000), // random price + System.currentTimeMillis(), + getName())); // ExchangeRateProvider name + } + + return exchangeRates; + } + }; + + // Initialize provider + dummyProvider.start(); + dummyProvider.stop(); + + return dummyProvider; + } + + private ExchangeRateProvider buildDummyExchangeRateProvider(Set rateCurrencyCodes) { + ExchangeRateProvider dummyProvider = new ExchangeRateProvider( + "ExchangeName-" + getRandomAlphaNumericString(5), + "EXCH-" + getRandomAlphaNumericString(3), + Duration.ofDays(1)) { + + @Override + public boolean isRunning() { + return true; + } + + @Override + protected Set doGet() { + HashSet exchangeRates = new HashSet<>(); + + // Simulate the required amount of rates + for (String rateCurrencyCode : rateCurrencyCodes) { + exchangeRates.add(new ExchangeRate( + rateCurrencyCode, + RandomUtils.nextDouble(1, 1000), // random price System.currentTimeMillis(), getName())); // ExchangeRateProvider name } From 671e80929ade3c6036daba548fef2cb6a323f98a Mon Sep 17 00:00:00 2001 From: cd2357 Date: Tue, 16 Jun 2020 21:23:31 +0200 Subject: [PATCH 03/26] Integrate initial set of ExchangeRateProviders Add support for a few exchanges to demonstrate and test the pricenode aggregate rates. The chose exchanges were selected because they each provide a varied list of fiat and altcoins, with a substantial overlap between them. This provides a robust initial set of datapoints and scenarios for aggregate rates. --- build.gradle | 11 ++- .../bisq/price/spot/ExchangeRateProvider.java | 93 +++++++++++++++++++ .../bisq/price/spot/providers/Binance.java | 46 +++++++++ .../bisq/price/spot/providers/Bitfinex.java | 46 +++++++++ .../bisq/price/spot/providers/Kraken.java | 46 +++++++++ .../bisq/price/spot/providers/Poloniex.java | 58 +----------- .../java/bisq/price/ExchangeTestBase.java | 74 +++++++++++++++ .../price/spot/providers/BinanceTest.java | 34 +++++++ .../price/spot/providers/BitfinexTest.java | 34 +++++++ .../bisq/price/spot/providers/KrakenTest.java | 34 +++++++ .../price/spot/providers/PoloniexTest.java | 34 +++++++ 11 files changed, 456 insertions(+), 54 deletions(-) create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Binance.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Kraken.java create mode 100644 pricenode/src/test/java/bisq/price/ExchangeTestBase.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/BinanceTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/BitfinexTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/KrakenTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/PoloniexTest.java diff --git a/build.gradle b/build.gradle index 82d5cc2ffb1..2949a3e65ec 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ configure(subprojects) { junitVersion = '4.12' jupiterVersion = '5.3.2' kotlinVersion = '1.3.41' - knowmXchangeVersion = '4.3.3' + knowmXchangeVersion = '5.0.0' langVersion = '3.8' logbackVersion = '1.1.11' loggingVersion = '1.2' @@ -458,6 +458,10 @@ configure(project(':pricenode')) { dependencies { compile project(":core") + + compileOnly "org.projectlombok:lombok:$lombokVersion" + annotationProcessor "org.projectlombok:lombok:$lombokVersion" + implementation "com.google.code.gson:gson:$gsonVersion" implementation "commons-codec:commons-codec:$codecVersion" implementation "org.apache.httpcomponents:httpcore:$httpcoreVersion" @@ -465,13 +469,18 @@ configure(project(':pricenode')) { exclude(module: 'commons-codec') } compile("org.knowm.xchange:xchange-bitcoinaverage:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-binance:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-bitfinex:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-coinmarketcap:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-kraken:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-poloniex:$knowmXchangeVersion") compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") compile("org.springframework.boot:spring-boot-starter-actuator") testCompile "org.junit.jupiter:junit-jupiter-api:$jupiterVersion" testCompile "org.junit.jupiter:junit-jupiter-params:$jupiterVersion" testRuntime("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion") + testCompileOnly "org.projectlombok:lombok:$lombokVersion" + testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion" } test { diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index 554fb0130ce..aa60ddb4e44 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -19,9 +19,24 @@ import bisq.price.PriceProvider; +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.TradeCurrency; + +import org.knowm.xchange.Exchange; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.exceptions.CurrencyPairNotValidException; +import org.knowm.xchange.service.marketdata.MarketDataService; + import java.time.Duration; +import java.util.Date; +import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * Abstract base class for providers of bitcoin {@link ExchangeRate} data. Implementations @@ -60,4 +75,82 @@ protected void onRefresh() { .filter(e -> "USD".equals(e.getCurrency()) || "LTC".equals(e.getCurrency())) .forEach(e -> log.info("BTC/{}: {}", e.getCurrency(), e.getPrice())); } + + /** + * @param exchangeClass Class of the {@link Exchange} for which the rates should be polled + * @return Exchange rates for Bisq-supported fiat currencies and altcoins in the specified {@link Exchange} + * + * @see CurrencyUtil#getAllSortedFiatCurrencies() + * @see CurrencyUtil#getAllSortedCryptoCurrencies() + */ + protected Set doGet(Class exchangeClass) { + Set result = new HashSet(); + + // Initialize XChange objects + Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeClass.getName()); + MarketDataService marketDataService = exchange.getMarketDataService(); + + // Retrieve all currency pairs supported by the exchange + List currencyPairs = exchange.getExchangeSymbols(); + + Set supportedCryptoCurrencies = CurrencyUtil.getAllSortedCryptoCurrencies().stream() + .map(TradeCurrency::getCode) + .collect(Collectors.toSet()); + + Set supportedFiatCurrencies = CurrencyUtil.getAllSortedFiatCurrencies().stream() + .map(TradeCurrency::getCode) + .collect(Collectors.toSet()); + + // Filter the supported fiat currencies (currency pair format is BTC-FIAT) + currencyPairs.stream() + .filter(cp -> cp.base.equals(Currency.BTC)) + .filter(cp -> supportedFiatCurrencies.contains(cp.counter.getCurrencyCode())) + .forEach(cp -> { + try { + Ticker t = marketDataService.getTicker(new CurrencyPair(cp.base, cp.counter)); + + result.add(new ExchangeRate( + cp.counter.getCurrencyCode(), + t.getLast(), + // Some exchanges do not provide timestamps + t.getTimestamp() == null ? new Date() : t.getTimestamp(), + this.getName() + )); + } catch (CurrencyPairNotValidException cpnve) { + // Some exchanges support certain currency pairs for other services but not for spot markets + // In that case, trying to retrieve the market ticker for that pair may fail with this specific type of exception + log.info("Currency pair " + cp + " not supported in Spot Markets: " + cpnve.getMessage()); + } catch (Exception e) { + // Catch any other type of generic exception (IO, network level, rate limit reached, etc) + log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + e.getMessage()); + } + }); + + // Filter the supported altcoins (currency pair format is ALT-BTC) + currencyPairs.stream() + .filter(cp -> cp.counter.equals(Currency.BTC)) + .filter(cp -> supportedCryptoCurrencies.contains(cp.base.getCurrencyCode())) + .forEach(cp -> { + try { + Ticker t = marketDataService.getTicker(new CurrencyPair(cp.base, cp.counter)); + + result.add(new ExchangeRate( + cp.base.getCurrencyCode(), + t.getLast(), + // Some exchanges do not provide timestamps + t.getTimestamp() == null ? new Date() : t.getTimestamp(), + this.getName() + )); + } catch (CurrencyPairNotValidException cpnve) { + // Some exchanges support certain currency pairs for other services but not for spot markets + // In that case, trying to retrieve the market ticker for that pair may fail with this specific type of exception + log.info("Currency pair " + cp + " not supported in Spot Markets: " + cpnve.getMessage()); + } catch (Exception e) { + // Catch any other type of generic exception (IO, network level, rate limit reached, etc) + log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + e.getMessage()); + } + }); + + return result; + } } diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java new file mode 100644 index 00000000000..21f2f75903b --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java @@ -0,0 +1,46 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.binance.BinanceExchange; + +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +@Order(5) +public class Binance extends ExchangeRateProvider { + + public Binance() { + super("BINANCE", "binance", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: EUR, NGN, RUB, TRY, ZAR + // Supported alts: BEAM, DASH, DCR, DOGE, ETC, ETH, LTC, NAV, PIVX, XMR, XZC, ZEC, ZEN + return doGet(BinanceExchange.class); + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java new file mode 100644 index 00000000000..e51aa5dfd8d --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java @@ -0,0 +1,46 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.bitfinex.BitfinexExchange; + +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +@Order(6) +public class Bitfinex extends ExchangeRateProvider { + + public Bitfinex() { + super("BITFINEX", "bitfinex", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: EUR, GBP, JPY, USD + // Supported alts: DAI, ETC, ETH, LTC, XMR, ZEC + return doGet(BitfinexExchange.class); + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java b/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java new file mode 100644 index 00000000000..f9167f02ed6 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java @@ -0,0 +1,46 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.kraken.KrakenExchange; + +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +@Order(7) +public class Kraken extends ExchangeRateProvider { + + public Kraken() { + super("KRAKEN", "kraken", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: AUD, CAD, CHF, EUR, GBP, JPY, USD + // Supported alts: DASH, DOGE, ETC, ETH, LTC, XMR, ZEC + return doGet(KrakenExchange.class); + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java b/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java index 684f867b25e..dbf302c3de3 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java @@ -19,33 +19,19 @@ import bisq.price.spot.ExchangeRate; import bisq.price.spot.ExchangeRateProvider; -import bisq.price.util.Altcoins; -import org.knowm.xchange.currency.Currency; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.poloniex.dto.marketdata.PoloniexMarketData; -import org.knowm.xchange.poloniex.dto.marketdata.PoloniexTicker; +import org.knowm.xchange.poloniex.PoloniexExchange; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.annotation.Order; -import org.springframework.http.RequestEntity; import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; import java.time.Duration; -import java.util.Date; -import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Component @Order(4) -class Poloniex extends ExchangeRateProvider { - - private final RestTemplate restTemplate = new RestTemplate(); +public class Poloniex extends ExchangeRateProvider { public Poloniex() { super("POLO", "poloniex", Duration.ofMinutes(1)); @@ -53,42 +39,8 @@ public Poloniex() { @Override public Set doGet() { - Date timestamp = new Date(); // Poloniex tickers don't include their own timestamp - - return getTickers() - .filter(t -> t.getCurrencyPair().base.equals(Currency.BTC)) - .filter(t -> Altcoins.ALL_SUPPORTED.contains(t.getCurrencyPair().counter.getCurrencyCode())) - .map(t -> - new ExchangeRate( - t.getCurrencyPair().counter.getCurrencyCode(), - t.getPoloniexMarketData().getLast(), - timestamp, - this.getName() - ) - ) - .collect(Collectors.toSet()); - } - - private Stream getTickers() { - - return getTickersKeyedByCurrencyPair().entrySet().stream() - .map(e -> { - String pair = e.getKey(); - PoloniexMarketData data = e.getValue(); - String[] symbols = pair.split("_"); // e.g. BTC_USD => [BTC, USD] - return new PoloniexTicker(data, new CurrencyPair(symbols[0], symbols[1])); - }); - } - - private Map getTickersKeyedByCurrencyPair() { - return restTemplate.exchange( - RequestEntity - .get(UriComponentsBuilder - .fromUriString("https://poloniex.com/public?command=returnTicker").build() - .toUri()) - .build(), - new ParameterizedTypeReference>() { - } - ).getBody(); + // Supported fiat: - + // Supported alts: DASH, DCR, DOGE, ETC, ETH, GRIN, LTC, XMR, ZEC + return doGet(PoloniexExchange.class); } } diff --git a/pricenode/src/test/java/bisq/price/ExchangeTestBase.java b/pricenode/src/test/java/bisq/price/ExchangeTestBase.java new file mode 100644 index 00000000000..6a31a87864f --- /dev/null +++ b/pricenode/src/test/java/bisq/price/ExchangeTestBase.java @@ -0,0 +1,74 @@ +package bisq.price; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import bisq.core.locale.CurrencyUtil; +import bisq.core.locale.TradeCurrency; + +import com.google.common.collect.Sets; + +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +import static org.junit.Assert.assertTrue; + +@Slf4j +public class ExchangeTestBase { + + protected void doGet_successfulCall(ExchangeRateProvider exchangeProvider) { + + // Use the XChange library to call the provider API, in order to retrieve the + // exchange rates. If the API call fails, or the response body cannot be parsed, + // the test will fail with an exception + Set retrievedExchangeRates = exchangeProvider.doGet(); + + // Log the valid exchange rates which were retrieved + // Useful when running the tests, to easily identify which exchanges provide useful pairs + retrievedExchangeRates.forEach(e -> log.info("Found exchange rate " + e.toString())); + + // Sanity checks + assertTrue(retrievedExchangeRates.size() > 0); + checkProviderCurrencyPairs(retrievedExchangeRates); + } + + /** + * Check that every retrieved currency pair is between BTC and either + * A) a fiat currency on the list of Bisq-supported fiat currencies, or + * B) an altcoin on the list of Bisq-supported altcoins + * + * @param retrievedExchangeRates Exchange rates retrieved from the provider + */ + private void checkProviderCurrencyPairs(Set retrievedExchangeRates) { + Set retrievedRatesCurrencies = retrievedExchangeRates.stream() + .map(ExchangeRate::getCurrency) + .collect(Collectors.toSet()); + + Set supportedCryptoCurrencies = CurrencyUtil.getAllSortedCryptoCurrencies().stream() + .map(TradeCurrency::getCode) + .collect(Collectors.toSet()); + + Set supportedFiatCurrencies = CurrencyUtil.getAllSortedFiatCurrencies().stream() + .map(TradeCurrency::getCode) + .collect(Collectors.toSet()); + + Set supportedFiatCurrenciesRetrieved = supportedFiatCurrencies.stream() + .filter(f -> retrievedRatesCurrencies.contains(f)) + .collect(Collectors.toCollection(TreeSet::new)); + log.info("Retrieved rates for supported fiat currencies: " + supportedFiatCurrenciesRetrieved); + + Set supportedCryptoCurrenciesRetrieved = supportedCryptoCurrencies.stream() + .filter(c -> retrievedRatesCurrencies.contains(c)) + .collect(Collectors.toCollection(TreeSet::new)); + log.info("Retrieved rates for supported altcoins: " + supportedCryptoCurrenciesRetrieved); + + Set supportedCurrencies = Sets.union(supportedCryptoCurrencies, supportedFiatCurrencies); + + Set unsupportedCurrencies = Sets.difference(retrievedRatesCurrencies, supportedCurrencies); + assertTrue("Retrieved exchange rates contain unsupported currencies: " + unsupportedCurrencies, + unsupportedCurrencies.isEmpty()); + } +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BinanceTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BinanceTest.java new file mode 100644 index 00000000000..85bb49e2028 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/BinanceTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.ExchangeTestBase; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class BinanceTest extends ExchangeTestBase { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Binance()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BitfinexTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BitfinexTest.java new file mode 100644 index 00000000000..44b38cb61f1 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/BitfinexTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.ExchangeTestBase; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class BitfinexTest extends ExchangeTestBase { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Bitfinex()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/KrakenTest.java b/pricenode/src/test/java/bisq/price/spot/providers/KrakenTest.java new file mode 100644 index 00000000000..c60278b62c4 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/KrakenTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.ExchangeTestBase; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class KrakenTest extends ExchangeTestBase { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Kraken()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/PoloniexTest.java b/pricenode/src/test/java/bisq/price/spot/providers/PoloniexTest.java new file mode 100644 index 00000000000..aea986d013e --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/PoloniexTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.ExchangeTestBase; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class PoloniexTest extends ExchangeTestBase { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Poloniex()); + } + +} From c6ef40e5e42a12b8f88e8b1713002ce927c87429 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Wed, 17 Jun 2020 09:24:31 +0200 Subject: [PATCH 04/26] Revert XChange version to keep jdk10 compatibility Revert from latest v5.0.0 to v4.2.2, since the newer version libraries are compiled with Java 11, so they cannot be used as part of the Bisq build process which still partially relies on Java 10. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2949a3e65ec..f0568e7e04d 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ configure(subprojects) { junitVersion = '4.12' jupiterVersion = '5.3.2' kotlinVersion = '1.3.41' - knowmXchangeVersion = '5.0.0' + knowmXchangeVersion = '4.4.2' langVersion = '3.8' logbackVersion = '1.1.11' loggingVersion = '1.2' From 141ead0b2fc06af91e8484ad9407062f6b9f0fb3 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 11 Jul 2020 17:39:06 +0200 Subject: [PATCH 05/26] Wrap comments at 90 characters Update comments to reflect bisq-network/style#5 guideline --- .../bisq/price/spot/ExchangeRateProvider.java | 30 ++++++++++----- .../bisq/price/spot/ExchangeRateService.java | 38 +++++++++++-------- .../bisq/price/spot/providers/Binance.java | 3 +- .../java/bisq/price/ExchangeTestBase.java | 3 +- .../price/spot/ExchangeRateServiceTest.java | 34 ++++++++++------- 5 files changed, 68 insertions(+), 40 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index aa60ddb4e44..7badfd51c89 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -77,8 +77,10 @@ protected void onRefresh() { } /** - * @param exchangeClass Class of the {@link Exchange} for which the rates should be polled - * @return Exchange rates for Bisq-supported fiat currencies and altcoins in the specified {@link Exchange} + * @param exchangeClass Class of the {@link Exchange} for which the rates should be + * polled + * @return Exchange rates for Bisq-supported fiat currencies and altcoins in the + * specified {@link Exchange} * * @see CurrencyUtil#getAllSortedFiatCurrencies() * @see CurrencyUtil#getAllSortedCryptoCurrencies() @@ -117,12 +119,16 @@ protected Set doGet(Class exchangeClass) { this.getName() )); } catch (CurrencyPairNotValidException cpnve) { - // Some exchanges support certain currency pairs for other services but not for spot markets - // In that case, trying to retrieve the market ticker for that pair may fail with this specific type of exception + // Some exchanges support certain currency pairs for other + // services but not for spot markets. In that case, trying to + // retrieve the market ticker for that pair may fail with this + // specific type of exception log.info("Currency pair " + cp + " not supported in Spot Markets: " + cpnve.getMessage()); } catch (Exception e) { - // Catch any other type of generic exception (IO, network level, rate limit reached, etc) - log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + e.getMessage()); + // Catch any other type of generic exception (IO, network level, + // rate limit reached, etc) + log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + + e.getMessage()); } }); @@ -142,12 +148,16 @@ protected Set doGet(Class exchangeClass) { this.getName() )); } catch (CurrencyPairNotValidException cpnve) { - // Some exchanges support certain currency pairs for other services but not for spot markets - // In that case, trying to retrieve the market ticker for that pair may fail with this specific type of exception + // Some exchanges support certain currency pairs for other + // services but not for spot markets. In that case, trying to + // retrieve the market ticker for that pair may fail with this + // specific type of exception log.info("Currency pair " + cp + " not supported in Spot Markets: " + cpnve.getMessage()); } catch (Exception e) { - // Catch any other type of generic exception (IO, network level, rate limit reached, etc) - log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + e.getMessage()); + // Catch any other type of generic exception (IO, network level, + // rate limit reached, etc) + log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + + e.getMessage()); } }); diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java index eff6abe9c6c..a682dfc97f3 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java @@ -65,7 +65,8 @@ public Map getAllMarketPrices() { providers.forEach(p -> { Set exchangeRates = p.get(); - // Specific metadata fields for specific providers are expected by the client, mostly for historical reasons + // Specific metadata fields for specific providers are expected by the client, + // mostly for historical reasons // Therefore, add metadata fields for all known providers // Rates are encapsulated in the "data" map below metadata.putAll(getMetadata(p, exchangeRates)); @@ -73,8 +74,8 @@ public Map getAllMarketPrices() { return new LinkedHashMap() {{ putAll(metadata); - // Use a sorted list by currency code to make comparision of json data between different - // price nodes easier + // Use a sorted list by currency code to make comparision of json data between + // different price nodes easier List values = new ArrayList<>(aggregateExchangeRates.values()); values.sort(Comparator.comparing(ExchangeRate::getCurrency)); put("data", values); @@ -82,29 +83,34 @@ public Map getAllMarketPrices() { } /** - * 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 aggregate price = average of retrieved prices. - * If a single provider has rates for the currency, then aggregate price = the rate from that provider. + * 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 + * aggregate price = average of retrieved prices. If a single provider has rates for + * the currency, then aggregate price = the rate from that provider. * - * @return Aggregate {@link ExchangeRate}s based on info from all providers, indexed by currency code + * @return Aggregate {@link ExchangeRate}s based on info from all providers, indexed + * by currency code */ private Map getAggregateExchangeRates() { Map aggregateExchangeRates = new HashMap<>(); - // Query all known providers and collect all exchange rates, grouped by currency code + // Query all providers and collect all exchange rates, grouped by currency code Map> currencyCodeToExchangeRates = getCurrencyCodeToExchangeRates(); // For each currency code, calculate aggregate rate currencyCodeToExchangeRates.forEach((currencyCode, exchangeRateList) -> { ExchangeRate aggregateExchangeRate; if (exchangeRateList.size() == 1) { - // If a single provider has rates for this currency, then aggregate = rate from that provider + // If a single provider has rates for this currency, then aggregate = rate + // from that provider aggregateExchangeRate = exchangeRateList.get(0); } else if (exchangeRateList.size() > 1) { - // If multiple providers have rates for this currency, then aggregate = average of the rates + // If multiple providers have rates for this currency, then + // aggregate = average of the rates OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average(); - double priceAvg = opt.orElseThrow(IllegalStateException::new); // List size > 1, so opt is always set + // List size > 1, so opt is always set + double priceAvg = opt.orElseThrow(IllegalStateException::new); aggregateExchangeRate = new ExchangeRate( currencyCode, @@ -113,7 +119,8 @@ else if (exchangeRateList.size() > 1) { "Bisq-Aggregate"); } else { - // If the map was built incorrectly and this currency points to an empty list of rates, skip it + // If the map was built incorrectly and this currency points to an empty + // list of rates, skip it return; } aggregateExchangeRates.put(aggregateExchangeRate.getCurrency(), aggregateExchangeRate); @@ -145,9 +152,10 @@ private Map> getCurrencyCodeToExchangeRates() { private Map getMetadata(ExchangeRateProvider provider, Set exchangeRates) { Map metadata = new LinkedHashMap<>(); - // In case a provider is not available we still want to deliver the data of the other providers, so we catch - // a possible exception and leave timestamp at 0. The Bisq app will check if the timestamp is in a tolerance - // window and if it is too old it will show that the price is not available. + // In case a provider is not available we still want to deliver the data of the + // other providers, so we catch a possible exception and leave timestamp at 0. The + // Bisq app will check if the timestamp is in a tolerance window and if it is too + // old it will show that the price is not available. long timestamp = 0; try { timestamp = getTimestamp(provider, exchangeRates); diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java index 21f2f75903b..ea7ac5b3fad 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java @@ -40,7 +40,8 @@ public Binance() { @Override public Set doGet() { // Supported fiat: EUR, NGN, RUB, TRY, ZAR - // Supported alts: BEAM, DASH, DCR, DOGE, ETC, ETH, LTC, NAV, PIVX, XMR, XZC, ZEC, ZEN + // Supported alts: BEAM, DASH, DCR, DOGE, ETC, ETH, LTC, NAV, PIVX, XMR, XZC, ZEC, + // ZEN return doGet(BinanceExchange.class); } } diff --git a/pricenode/src/test/java/bisq/price/ExchangeTestBase.java b/pricenode/src/test/java/bisq/price/ExchangeTestBase.java index 6a31a87864f..64bd0e0cf2e 100644 --- a/pricenode/src/test/java/bisq/price/ExchangeTestBase.java +++ b/pricenode/src/test/java/bisq/price/ExchangeTestBase.java @@ -27,7 +27,8 @@ protected void doGet_successfulCall(ExchangeRateProvider exchangeProvider) { Set retrievedExchangeRates = exchangeProvider.doGet(); // Log the valid exchange rates which were retrieved - // Useful when running the tests, to easily identify which exchanges provide useful pairs + // Useful when running the tests, to easily identify which exchanges provide + // useful pairs retrievedExchangeRates.forEach(e -> log.info("Found exchange rate " + e.toString())); // Sanity checks diff --git a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java index a697ad8574e..61d2ffbee6a 100644 --- a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java +++ b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java @@ -149,8 +149,8 @@ public void getAllMarketPrices_withMultipleProviders_overlappingCurrencyCodes() doSanityChecksForRetrievedDataMultipleProviders(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 + // At least one rate was provided by each provider in this service, so the + // timestamp (for both providers) should not be 0 assertNotEquals(0L, retrievedData.get(dummyProvider1.getPrefix() + "Ts")); assertNotEquals(0L, retrievedData.get(dummyProvider2.getPrefix() + "Ts")); } @@ -159,8 +159,10 @@ public void getAllMarketPrices_withMultipleProviders_overlappingCurrencyCodes() * Performs generic sanity checks on the response format and contents. * * @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 + * @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, ExchangeRateProvider provider, @@ -169,7 +171,8 @@ private void doSanityChecksForRetrievedDataSingleProvider(Map re doSanityChecksForRetrievedDataMultipleProviders(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 amount of rates in the response + // For one provider, the amount of rates of that provider should be the total + // amount of rates in the response List retrievedMarketPricesData = (List) retrievedData.get("data"); assertEquals(numberOfCurrencyPairsOnExchange, retrievedMarketPricesData.size()); } @@ -205,8 +208,9 @@ private void doSanityChecksForRetrievedDataMultipleProviders(Map assertEquals(uniqueRates, totalRates, "Found duplicate rates in data field"); // There should be only one ExchangeRate per currency - // In other words, even if multiple providers return rates for the same currency, the ExchangeRateService - // should expose only one (aggregate) ExchangeRate for that currency + // In other words, even if multiple providers return rates for the same currency, + // the ExchangeRateService should expose only one (aggregate) ExchangeRate for + // that currency Map currencyCodeToExchangeRateFromService = retrievedRates.stream() .collect(Collectors.toMap( ExchangeRate::getCurrency, exchangeRate -> exchangeRate @@ -229,7 +233,8 @@ private void doSanityChecksForRetrievedDataMultipleProviders(Map } } - // For each ExchangeRate which is covered by multiple providers, ensure the rate value is an average + // For each ExchangeRate which is covered by multiple providers, ensure the rate + // value is an average currencyCodeToExchangeRatesFromProviders.forEach((currencyCode, exchangeRateList) -> { ExchangeRate rateFromService = currencyCodeToExchangeRateFromService.get(currencyCode); double priceFromService = rateFromService.getPrice(); @@ -237,17 +242,19 @@ private void doSanityChecksForRetrievedDataMultipleProviders(Map OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average(); double priceAvgFromProviders = opt.getAsDouble(); - // Ensure that the ExchangeRateService correctly aggregates exchange rates from multiple providers - // If multiple providers contain rates for a currency, the service should return a single aggregate rate + // Ensure that the ExchangeRateService correctly aggregates exchange rates + // from multiple providers. If multiple providers contain rates for a + // currency, the service should return a single aggregate rate // Expected value for aggregate rate = avg(provider rates) - // This formula works for one, as well as many, providers for a specific currency + // This formula works for any number of providers for a specific currency assertEquals(priceFromService, priceAvgFromProviders, "Service returned incorrect aggregate rate"); }); } /** * @param numberOfRatesAvailable Number of exchange rates this provider returns - * @return Dummy {@link ExchangeRateProvider} providing rates for "numberOfRatesAvailable" random currency codes + * @return Dummy {@link ExchangeRateProvider} providing rates for + * "numberOfRatesAvailable" random currency codes */ private ExchangeRateProvider buildDummyExchangeRateProvider(int numberOfRatesAvailable) { ExchangeRateProvider dummyProvider = new ExchangeRateProvider( @@ -267,7 +274,8 @@ protected Set doGet() { // Simulate the required amount of rates for (int i = 0; i < numberOfRatesAvailable; i++) { exchangeRates.add(new ExchangeRate( - "DUM-" + getRandomAlphaNumericString(3), // random symbol, avoid duplicates + // random symbol, avoid duplicates + "DUM-" + getRandomAlphaNumericString(3), RandomUtils.nextDouble(1, 1000), // random price System.currentTimeMillis(), getName())); // ExchangeRateProvider name From 3e314a9d24131e2985d52eb531589327807b0126 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 11 Jul 2020 17:52:01 +0200 Subject: [PATCH 06/26] Rename exception variables to ex Update the name of exception variables to ex for consistency and better readability. --- .../bisq/price/spot/ExchangeRateProvider.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index 7badfd51c89..bf0dfd1b999 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -118,17 +118,17 @@ protected Set doGet(Class exchangeClass) { t.getTimestamp() == null ? new Date() : t.getTimestamp(), this.getName() )); - } catch (CurrencyPairNotValidException cpnve) { + } catch (CurrencyPairNotValidException ex) { // Some exchanges support certain currency pairs for other // services but not for spot markets. In that case, trying to // retrieve the market ticker for that pair may fail with this // specific type of exception - log.info("Currency pair " + cp + " not supported in Spot Markets: " + cpnve.getMessage()); - } catch (Exception e) { + log.info("Currency pair " + cp + " not supported in Spot Markets: " + ex.getMessage()); + } catch (Exception ex) { // Catch any other type of generic exception (IO, network level, // rate limit reached, etc) log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + - e.getMessage()); + ex.getMessage()); } }); @@ -147,17 +147,17 @@ protected Set doGet(Class exchangeClass) { t.getTimestamp() == null ? new Date() : t.getTimestamp(), this.getName() )); - } catch (CurrencyPairNotValidException cpnve) { + } catch (CurrencyPairNotValidException ex) { // Some exchanges support certain currency pairs for other // services but not for spot markets. In that case, trying to // retrieve the market ticker for that pair may fail with this // specific type of exception - log.info("Currency pair " + cp + " not supported in Spot Markets: " + cpnve.getMessage()); - } catch (Exception e) { + log.info("Currency pair " + cp + " not supported in Spot Markets: " + ex.getMessage()); + } catch (Exception ex) { // Catch any other type of generic exception (IO, network level, // rate limit reached, etc) log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + - e.getMessage()); + ex.getMessage()); } }); From 5cffddc9aecdf461ecd51573066582d5ea75c621 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 11 Jul 2020 18:11:21 +0200 Subject: [PATCH 07/26] Rewrite else-if clause Simplify if-else block to avoid redundant use of else-if in combination with an empty check and a return statement. --- .../java/bisq/price/spot/ExchangeRateService.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java index a682dfc97f3..f7bc35f8912 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java @@ -95,17 +95,24 @@ private Map getAggregateExchangeRates() { Map aggregateExchangeRates = new HashMap<>(); // Query all providers and collect all exchange rates, grouped by currency code + // key = currency code + // value = list of exchange rates Map> currencyCodeToExchangeRates = getCurrencyCodeToExchangeRates(); // For each currency code, calculate aggregate rate currencyCodeToExchangeRates.forEach((currencyCode, exchangeRateList) -> { + if (exchangeRateList.isEmpty()) + // If the map was built incorrectly and this currency points to an empty + // list of rates, skip it + return; + ExchangeRate aggregateExchangeRate; if (exchangeRateList.size() == 1) { // If a single provider has rates for this currency, then aggregate = rate // from that provider aggregateExchangeRate = exchangeRateList.get(0); } - else if (exchangeRateList.size() > 1) { + else { // If multiple providers have rates for this currency, then // aggregate = average of the rates OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average(); @@ -118,11 +125,6 @@ else if (exchangeRateList.size() > 1) { new Date(), // timestamp = time when avg is calculated "Bisq-Aggregate"); } - else { - // If the map was built incorrectly and this currency points to an empty - // list of rates, skip it - return; - } aggregateExchangeRates.put(aggregateExchangeRate.getCurrency(), aggregateExchangeRate); }); From 020547e19bfc25938d4225c3552b166fe792f00f Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 11 Jul 2020 18:40:41 +0200 Subject: [PATCH 08/26] Remove Order annotation from ExchangeRateProviders Remove Order annotation from rate providers, which was used in the case that multiple providers would retrieve rates for the same currency. The ExchangeRateService now handles such scenarios, thus eliminating the need for deciding provider precedence via the Order annotation. --- .../java/bisq/price/spot/ExchangeRateProvider.java | 11 ++++------- .../main/java/bisq/price/spot/providers/Binance.java | 2 -- .../main/java/bisq/price/spot/providers/Bitfinex.java | 2 -- .../java/bisq/price/spot/providers/CoinMarketCap.java | 2 -- .../main/java/bisq/price/spot/providers/Kraken.java | 2 -- .../main/java/bisq/price/spot/providers/Poloniex.java | 2 -- 6 files changed, 4 insertions(+), 17 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index bf0dfd1b999..fd7d9b3fc62 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -41,14 +41,11 @@ /** * Abstract base class for providers of bitcoin {@link ExchangeRate} data. Implementations * are marked with the {@link org.springframework.stereotype.Component} annotation in - * order to be discovered via classpath scanning. Implementations are also marked with the - * {@link org.springframework.core.annotation.Order} annotation to determine their - * precedence over each other in the case of two or more services returning exchange rate - * data for the same currency pair. In such cases, results from the provider with the - * higher order value will take precedence over the provider with a lower value, - * presuming that such providers are being iterated over in an ordered list. + * order to be discovered via classpath scanning. If multiple + * {@link ExchangeRateProvider}s retrieve rates for the same currency, then the + * {@link ExchangeRateService} will average them out and expose an aggregate rate. * - * @see ExchangeRateService#ExchangeRateService(java.util.List) + * @see ExchangeRateService#getAllMarketPrices() */ public abstract class ExchangeRateProvider extends PriceProvider> { diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java index ea7ac5b3fad..3edf0291e91 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java @@ -22,7 +22,6 @@ import org.knowm.xchange.binance.BinanceExchange; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.time.Duration; @@ -30,7 +29,6 @@ import java.util.Set; @Component -@Order(5) public class Binance extends ExchangeRateProvider { public Binance() { diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java index e51aa5dfd8d..42aa21b1423 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java @@ -22,7 +22,6 @@ import org.knowm.xchange.bitfinex.BitfinexExchange; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.time.Duration; @@ -30,7 +29,6 @@ import java.util.Set; @Component -@Order(6) public class Bitfinex extends ExchangeRateProvider { public Bitfinex() { diff --git a/pricenode/src/main/java/bisq/price/spot/providers/CoinMarketCap.java b/pricenode/src/main/java/bisq/price/spot/providers/CoinMarketCap.java index 1d99d16e0db..39239c9e97e 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/CoinMarketCap.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/CoinMarketCap.java @@ -20,7 +20,6 @@ import bisq.price.spot.ExchangeRate; import bisq.price.spot.ExchangeRateProvider; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.time.Duration; @@ -32,7 +31,6 @@ * Stub implementation of CoinMarketCap price provider to prevent NullPointerExceptions within legacy clients */ @Component -@Order(3) class CoinMarketCap extends ExchangeRateProvider { public CoinMarketCap() { diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java b/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java index f9167f02ed6..912f2deab4f 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java @@ -22,7 +22,6 @@ import org.knowm.xchange.kraken.KrakenExchange; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.time.Duration; @@ -30,7 +29,6 @@ import java.util.Set; @Component -@Order(7) public class Kraken extends ExchangeRateProvider { public Kraken() { diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java b/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java index dbf302c3de3..ec3521d5f78 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java @@ -22,7 +22,6 @@ import org.knowm.xchange.poloniex.PoloniexExchange; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.time.Duration; @@ -30,7 +29,6 @@ import java.util.Set; @Component -@Order(4) public class Poloniex extends ExchangeRateProvider { public Poloniex() { From 75a0a47a1e4113f0a569597293c9032243c56e42 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 11 Jul 2020 18:47:34 +0200 Subject: [PATCH 09/26] Mark new ExchangeRateProviders as package-private Remove public modifier in their class definitions to preserve their package-private scope. --- pricenode/src/main/java/bisq/price/spot/providers/Binance.java | 2 +- pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java | 2 +- pricenode/src/main/java/bisq/price/spot/providers/Kraken.java | 2 +- pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java index 3edf0291e91..c76f9175ee1 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java @@ -29,7 +29,7 @@ import java.util.Set; @Component -public class Binance extends ExchangeRateProvider { +class Binance extends ExchangeRateProvider { public Binance() { super("BINANCE", "binance", Duration.ofMinutes(1)); diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java index 42aa21b1423..e958d522320 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java @@ -29,7 +29,7 @@ import java.util.Set; @Component -public class Bitfinex extends ExchangeRateProvider { +class Bitfinex extends ExchangeRateProvider { public Bitfinex() { super("BITFINEX", "bitfinex", Duration.ofMinutes(1)); diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java b/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java index 912f2deab4f..34db16a80ff 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java @@ -29,7 +29,7 @@ import java.util.Set; @Component -public class Kraken extends ExchangeRateProvider { +class Kraken extends ExchangeRateProvider { public Kraken() { super("KRAKEN", "kraken", Duration.ofMinutes(1)); diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java b/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java index ec3521d5f78..e61ec36e6e1 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java @@ -29,7 +29,7 @@ import java.util.Set; @Component -public class Poloniex extends ExchangeRateProvider { +class Poloniex extends ExchangeRateProvider { public Poloniex() { super("POLO", "poloniex", Duration.ofMinutes(1)); From aceb7eef1513769d10acb304162ad339b418c372 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 11 Jul 2020 18:53:12 +0200 Subject: [PATCH 10/26] Renamed ExchangeRateProvider test class Give a more accurate name to the abstract test class which contains common methods used by all ExchangeRateProvider tests, like BinanceTest or KrakenTest. Mark this test class as abstract, to indicate that it should not be run as a standalone test. --- ...ngeTestBase.java => AbstractExchangeRateProviderTest.java} | 2 +- .../src/test/java/bisq/price/spot/providers/BinanceTest.java | 4 ++-- .../src/test/java/bisq/price/spot/providers/BitfinexTest.java | 4 ++-- .../src/test/java/bisq/price/spot/providers/KrakenTest.java | 4 ++-- .../src/test/java/bisq/price/spot/providers/PoloniexTest.java | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) rename pricenode/src/test/java/bisq/price/{ExchangeTestBase.java => AbstractExchangeRateProviderTest.java} (98%) diff --git a/pricenode/src/test/java/bisq/price/ExchangeTestBase.java b/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java similarity index 98% rename from pricenode/src/test/java/bisq/price/ExchangeTestBase.java rename to pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java index 64bd0e0cf2e..47855f0be5d 100644 --- a/pricenode/src/test/java/bisq/price/ExchangeTestBase.java +++ b/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java @@ -17,7 +17,7 @@ import static org.junit.Assert.assertTrue; @Slf4j -public class ExchangeTestBase { +public abstract class AbstractExchangeRateProviderTest { protected void doGet_successfulCall(ExchangeRateProvider exchangeProvider) { diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BinanceTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BinanceTest.java index 85bb49e2028..2a72e67a713 100644 --- a/pricenode/src/test/java/bisq/price/spot/providers/BinanceTest.java +++ b/pricenode/src/test/java/bisq/price/spot/providers/BinanceTest.java @@ -17,14 +17,14 @@ package bisq.price.spot.providers; -import bisq.price.ExchangeTestBase; +import bisq.price.AbstractExchangeRateProviderTest; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @Slf4j -public class BinanceTest extends ExchangeTestBase { +public class BinanceTest extends AbstractExchangeRateProviderTest { @Test public void doGet_successfulCall() { diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BitfinexTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BitfinexTest.java index 44b38cb61f1..a653b6484af 100644 --- a/pricenode/src/test/java/bisq/price/spot/providers/BitfinexTest.java +++ b/pricenode/src/test/java/bisq/price/spot/providers/BitfinexTest.java @@ -17,14 +17,14 @@ package bisq.price.spot.providers; -import bisq.price.ExchangeTestBase; +import bisq.price.AbstractExchangeRateProviderTest; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @Slf4j -public class BitfinexTest extends ExchangeTestBase { +public class BitfinexTest extends AbstractExchangeRateProviderTest { @Test public void doGet_successfulCall() { diff --git a/pricenode/src/test/java/bisq/price/spot/providers/KrakenTest.java b/pricenode/src/test/java/bisq/price/spot/providers/KrakenTest.java index c60278b62c4..6f8a84a5ca3 100644 --- a/pricenode/src/test/java/bisq/price/spot/providers/KrakenTest.java +++ b/pricenode/src/test/java/bisq/price/spot/providers/KrakenTest.java @@ -17,14 +17,14 @@ package bisq.price.spot.providers; -import bisq.price.ExchangeTestBase; +import bisq.price.AbstractExchangeRateProviderTest; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @Slf4j -public class KrakenTest extends ExchangeTestBase { +public class KrakenTest extends AbstractExchangeRateProviderTest { @Test public void doGet_successfulCall() { diff --git a/pricenode/src/test/java/bisq/price/spot/providers/PoloniexTest.java b/pricenode/src/test/java/bisq/price/spot/providers/PoloniexTest.java index aea986d013e..70cb3b7d7ca 100644 --- a/pricenode/src/test/java/bisq/price/spot/providers/PoloniexTest.java +++ b/pricenode/src/test/java/bisq/price/spot/providers/PoloniexTest.java @@ -17,14 +17,14 @@ package bisq.price.spot.providers; -import bisq.price.ExchangeTestBase; +import bisq.price.AbstractExchangeRateProviderTest; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @Slf4j -public class PoloniexTest extends ExchangeTestBase { +public class PoloniexTest extends AbstractExchangeRateProviderTest { @Test public void doGet_successfulCall() { From 329188db1d2e8f66efc575baf69b429cbe483a73 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sun, 12 Jul 2020 23:49:25 +0200 Subject: [PATCH 11/26] Reduce number of exchange API calls when polling Retrieve the exchange rates in bulk, when possible. This reduces the number of calls the pricenode makes to the exchange API from N = "number of exchange rates to retrieve" to N = 1. The replaced approach, which made a separate call to the exchange API for each exchange rate, was sometimes failing due to reaching API rate limits. --- .../bisq/price/spot/ExchangeRateProvider.java | 179 ++++++++++++------ .../bisq/price/spot/providers/Kraken.java | 5 + 2 files changed, 125 insertions(+), 59 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index fd7d9b3fc62..85dc7d71da4 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -27,16 +27,23 @@ import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; -import org.knowm.xchange.exceptions.CurrencyPairNotValidException; import org.knowm.xchange.service.marketdata.MarketDataService; +import org.knowm.xchange.service.marketdata.params.CurrencyPairsParam; +import org.knowm.xchange.service.marketdata.params.Params; import java.time.Duration; +import java.io.IOException; + +import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Abstract base class for providers of bitcoin {@link ExchangeRate} data. Implementations @@ -49,6 +56,14 @@ */ public abstract class ExchangeRateProvider extends PriceProvider> { + private static final Set supportedCryptoCurrencies = CurrencyUtil.getAllSortedCryptoCurrencies().stream() + .map(TradeCurrency::getCode) + .collect(Collectors.toSet()); + + private static final Set supportedFiatCurrencies = CurrencyUtil.getAllSortedFiatCurrencies().stream() + .map(TradeCurrency::getCode) + .collect(Collectors.toSet()); + private final String name; private final String prefix; @@ -90,74 +105,120 @@ protected Set doGet(Class exchangeClass) { MarketDataService marketDataService = exchange.getMarketDataService(); // Retrieve all currency pairs supported by the exchange - List currencyPairs = exchange.getExchangeSymbols(); + List allCurrencyPairsOnExchange = exchange.getExchangeSymbols(); - Set supportedCryptoCurrencies = CurrencyUtil.getAllSortedCryptoCurrencies().stream() - .map(TradeCurrency::getCode) - .collect(Collectors.toSet()); + // Find out which currency pairs we are interested in polling ("desired pairs") + // This will be the intersection of: + // 1) the pairs available on the exchange, and + // 2) the pairs Bisq considers relevant / valid + // This will result in two lists of desired pairs (fiat and alts) - Set supportedFiatCurrencies = CurrencyUtil.getAllSortedFiatCurrencies().stream() - .map(TradeCurrency::getCode) - .collect(Collectors.toSet()); - - // Filter the supported fiat currencies (currency pair format is BTC-FIAT) - currencyPairs.stream() + // Find the desired fiat pairs (pair format is BTC-FIAT) + List desiredFiatPairs = allCurrencyPairsOnExchange.stream() .filter(cp -> cp.base.equals(Currency.BTC)) .filter(cp -> supportedFiatCurrencies.contains(cp.counter.getCurrencyCode())) - .forEach(cp -> { - try { - Ticker t = marketDataService.getTicker(new CurrencyPair(cp.base, cp.counter)); - - result.add(new ExchangeRate( - cp.counter.getCurrencyCode(), - t.getLast(), - // Some exchanges do not provide timestamps - t.getTimestamp() == null ? new Date() : t.getTimestamp(), - this.getName() - )); - } catch (CurrencyPairNotValidException ex) { - // Some exchanges support certain currency pairs for other - // services but not for spot markets. In that case, trying to - // retrieve the market ticker for that pair may fail with this - // specific type of exception - log.info("Currency pair " + cp + " not supported in Spot Markets: " + ex.getMessage()); - } catch (Exception ex) { - // Catch any other type of generic exception (IO, network level, - // rate limit reached, etc) - log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + - ex.getMessage()); - } - }); + .collect(Collectors.toList()); - // Filter the supported altcoins (currency pair format is ALT-BTC) - currencyPairs.stream() + // Find the desired altcoin pairs (pair format is ALT-BTC) + List desiredCryptoPairs = allCurrencyPairsOnExchange.stream() .filter(cp -> cp.counter.equals(Currency.BTC)) .filter(cp -> supportedCryptoCurrencies.contains(cp.base.getCurrencyCode())) - .forEach(cp -> { - try { - Ticker t = marketDataService.getTicker(new CurrencyPair(cp.base, cp.counter)); - - result.add(new ExchangeRate( - cp.base.getCurrencyCode(), - t.getLast(), - // Some exchanges do not provide timestamps - t.getTimestamp() == null ? new Date() : t.getTimestamp(), - this.getName() - )); - } catch (CurrencyPairNotValidException ex) { - // Some exchanges support certain currency pairs for other - // services but not for spot markets. In that case, trying to - // retrieve the market ticker for that pair may fail with this - // specific type of exception - log.info("Currency pair " + cp + " not supported in Spot Markets: " + ex.getMessage()); - } catch (Exception ex) { - // Catch any other type of generic exception (IO, network level, - // rate limit reached, etc) - log.info("Exception encountered while retrieving rate for currency pair " + cp + ": " + - ex.getMessage()); + .collect(Collectors.toList()); + + // Retrieve in bulk all tickers offered by the exchange + // The benefits of this approach (vs polling each ticker) are twofold: + // 1) the polling of the exchange is faster (one HTTP call vs several) + // 2) it's easier to stay below any API rate limits the exchange might have + List tickersRetrievedFromExchange; + try { + tickersRetrievedFromExchange = marketDataService.getTickers(new CurrencyPairsParam() { + + /** + * The {@link MarketDataService#getTickers(Params)} interface requires a + * {@link CurrencyPairsParam} argument when polling for tickers in bulk. + * This parameter is meant to indicate a list of currency pairs for which + * the tickers should be polled. However, the actual implementations for + * the different exchanges differ, for example: + * - some will ignore it (and retrieve all available tickers) + * - some will require it (and will fail if a null or empty list is given) + * - some will properly handle it + * + * We take a simplistic approach, namely: + * - for providers that require such a filter, specify one + * - for all others, do not specify one + * + * We make this distinction using + * {@link ExchangeRateProvider#requiresFilterDuringBulkTickerRetrieval} + * + * @return Filter (list of desired currency pairs) to be used during bulk + * ticker retrieval + */ + @Override + public Collection getCurrencyPairs() { + // If required by the exchange implementation, specify a filter + // (list of pairs which should be retrieved) + if (requiresFilterDuringBulkTickerRetrieval()) + return Stream.of(desiredFiatPairs, desiredCryptoPairs) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + + // Otherwise, specify an empty list, indicating that the API should + // simply return all available tickers + return Collections.emptyList(); + } + }); + } catch (IOException e) { + // If there was a problem with polling this exchange, return right away, + // since there are no results to parse and process + log.error("Could not query tickers for provider " + getName(), e); + return result; + } + + // Create an ExchangeRate for each desired currency pair ticker that was retrieved + Predicate isDesiredFiatPair = t -> desiredFiatPairs.contains(t.getCurrencyPair()); + Predicate isDesiredCryptoPair = t -> desiredCryptoPairs.contains(t.getCurrencyPair()); + tickersRetrievedFromExchange.stream() + .filter(isDesiredFiatPair.or(isDesiredCryptoPair)) // Only consider desired pairs + .forEach(t -> { + // All tickers here match all requirements + + // We have two kinds of currency pairs, BTC-FIAT and ALT-BTC + // In the first one, BTC is the first currency of the pair + // In the second type, BTC is listed as the second currency + // Distinguish between the two and create ExchangeRates accordingly + + // In every Bisq ExchangeRate, BTC is one currency in the pair + // Extract the other currency from the ticker, to create ExchangeRates + String otherExchangeRateCurrency; + if (t.getCurrencyPair().base.equals(Currency.BTC)) { + otherExchangeRateCurrency = t.getCurrencyPair().counter.getCurrencyCode(); } + else { + otherExchangeRateCurrency = t.getCurrencyPair().base.getCurrencyCode(); + } + + result.add(new ExchangeRate( + otherExchangeRateCurrency, + t.getLast(), + // Some exchanges do not provide timestamps + t.getTimestamp() == null ? new Date() : t.getTimestamp(), + this.getName() + )); }); return result; } + + /** + * @return Whether or not the bulk retrieval of tickers from the exchange requires an + * explicit filter (list of desired pairs) or not. If true, the + * {@link MarketDataService#getTickers(Params)} call will be constructed and given as + * argument, which acts as a filter indicating for which pairs the ticker should be + * retrieved. If false, {@link MarketDataService#getTickers(Params)} will be called + * with an empty argument, indicating that the API should simply return all available + * tickers on the exchange + */ + protected boolean requiresFilterDuringBulkTickerRetrieval() { + return false; + } } diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java b/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java index 34db16a80ff..d28645cfdb5 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Kraken.java @@ -41,4 +41,9 @@ public Set doGet() { // Supported alts: DASH, DOGE, ETC, ETH, LTC, XMR, ZEC return doGet(KrakenExchange.class); } + + @Override + protected boolean requiresFilterDuringBulkTickerRetrieval() { + return true; + } } From 7fc519179875d9458d26bf3bfa9b866758bc543d Mon Sep 17 00:00:00 2001 From: cd2357 Date: Mon, 13 Jul 2020 20:39:24 +0200 Subject: [PATCH 12/26] Reuse sets of supported currencies Reuse sets of supported currencies between pricenode classes and tests. --- .../bisq/price/spot/ExchangeRateProvider.java | 8 ++++---- .../price/AbstractExchangeRateProviderTest.java | 16 +++++----------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index 85dc7d71da4..b753bc8bfd4 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -56,11 +56,11 @@ */ public abstract class ExchangeRateProvider extends PriceProvider> { - private static final Set supportedCryptoCurrencies = CurrencyUtil.getAllSortedCryptoCurrencies().stream() + public static final Set SUPPORTED_CRYPTO_CURRENCIES = CurrencyUtil.getAllSortedCryptoCurrencies().stream() .map(TradeCurrency::getCode) .collect(Collectors.toSet()); - private static final Set supportedFiatCurrencies = CurrencyUtil.getAllSortedFiatCurrencies().stream() + public static final Set SUPPORTED_FIAT_CURRENCIES = CurrencyUtil.getAllSortedFiatCurrencies().stream() .map(TradeCurrency::getCode) .collect(Collectors.toSet()); @@ -116,13 +116,13 @@ protected Set doGet(Class exchangeClass) { // Find the desired fiat pairs (pair format is BTC-FIAT) List desiredFiatPairs = allCurrencyPairsOnExchange.stream() .filter(cp -> cp.base.equals(Currency.BTC)) - .filter(cp -> supportedFiatCurrencies.contains(cp.counter.getCurrencyCode())) + .filter(cp -> SUPPORTED_FIAT_CURRENCIES.contains(cp.counter.getCurrencyCode())) .collect(Collectors.toList()); // Find the desired altcoin pairs (pair format is ALT-BTC) List desiredCryptoPairs = allCurrencyPairsOnExchange.stream() .filter(cp -> cp.counter.equals(Currency.BTC)) - .filter(cp -> supportedCryptoCurrencies.contains(cp.base.getCurrencyCode())) + .filter(cp -> SUPPORTED_CRYPTO_CURRENCIES.contains(cp.base.getCurrencyCode())) .collect(Collectors.toList()); // Retrieve in bulk all tickers offered by the exchange diff --git a/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java b/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java index 47855f0be5d..8bf1f2d78d9 100644 --- a/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java +++ b/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java @@ -48,25 +48,19 @@ private void checkProviderCurrencyPairs(Set retrievedExchangeRates .map(ExchangeRate::getCurrency) .collect(Collectors.toSet()); - Set supportedCryptoCurrencies = CurrencyUtil.getAllSortedCryptoCurrencies().stream() - .map(TradeCurrency::getCode) - .collect(Collectors.toSet()); - - Set supportedFiatCurrencies = CurrencyUtil.getAllSortedFiatCurrencies().stream() - .map(TradeCurrency::getCode) - .collect(Collectors.toSet()); - - Set supportedFiatCurrenciesRetrieved = supportedFiatCurrencies.stream() + Set supportedFiatCurrenciesRetrieved = ExchangeRateProvider.SUPPORTED_FIAT_CURRENCIES.stream() .filter(f -> retrievedRatesCurrencies.contains(f)) .collect(Collectors.toCollection(TreeSet::new)); log.info("Retrieved rates for supported fiat currencies: " + supportedFiatCurrenciesRetrieved); - Set supportedCryptoCurrenciesRetrieved = supportedCryptoCurrencies.stream() + Set supportedCryptoCurrenciesRetrieved = ExchangeRateProvider.SUPPORTED_CRYPTO_CURRENCIES.stream() .filter(c -> retrievedRatesCurrencies.contains(c)) .collect(Collectors.toCollection(TreeSet::new)); log.info("Retrieved rates for supported altcoins: " + supportedCryptoCurrenciesRetrieved); - Set supportedCurrencies = Sets.union(supportedCryptoCurrencies, supportedFiatCurrencies); + Set supportedCurrencies = Sets.union( + ExchangeRateProvider.SUPPORTED_CRYPTO_CURRENCIES, + ExchangeRateProvider.SUPPORTED_FIAT_CURRENCIES); Set unsupportedCurrencies = Sets.difference(retrievedRatesCurrencies, supportedCurrencies); assertTrue("Retrieved exchange rates contain unsupported currencies: " + unsupportedCurrencies, From 637378b58a09d53feb85f3a765eef2ac5bb73111 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sun, 26 Jul 2020 22:22:08 +0200 Subject: [PATCH 13/26] Integrate more exchanges using knowm xchange Add more exchange providers supported by the knowm xchange library. This extends support for a few new currencies. --- build.gradle | 13 ++++ .../bisq/price/spot/ExchangeRateProvider.java | 60 ++++++++++++++++++- .../bisq/price/spot/providers/BTCMarkets.java | 45 ++++++++++++++ .../bisq/price/spot/providers/Binance.java | 6 +- .../bisq/price/spot/providers/Bitbay.java | 44 ++++++++++++++ .../bisq/price/spot/providers/Bitflyer.java | 44 ++++++++++++++ .../bisq/price/spot/providers/Bitstamp.java | 44 ++++++++++++++ .../java/bisq/price/spot/providers/CexIO.java | 49 +++++++++++++++ .../bisq/price/spot/providers/Coinmate.java | 45 ++++++++++++++ .../bisq/price/spot/providers/Coinone.java | 45 ++++++++++++++ .../java/bisq/price/spot/providers/Exmo.java | 46 ++++++++++++++ .../spot/providers/IndependentReserve.java | 45 ++++++++++++++ .../java/bisq/price/spot/providers/Luno.java | 52 ++++++++++++++++ .../price/spot/providers/MercadoBitcoin.java | 45 ++++++++++++++ .../bisq/price/spot/providers/Paribu.java | 44 ++++++++++++++ .../bisq/price/spot/providers/Poloniex.java | 2 +- .../bisq/price/spot/providers/Quoine.java | 49 +++++++++++++++ .../price/spot/providers/BTCMarketsTest.java | 34 +++++++++++ .../bisq/price/spot/providers/BitbayTest.java | 34 +++++++++++ .../price/spot/providers/BitflyerTest.java | 34 +++++++++++ .../price/spot/providers/BitstampTest.java | 34 +++++++++++ .../bisq/price/spot/providers/CexIOTest.java | 34 +++++++++++ .../price/spot/providers/CoinmateTest.java | 34 +++++++++++ .../price/spot/providers/CoinoneTest.java | 34 +++++++++++ .../bisq/price/spot/providers/ExmoTest.java | 34 +++++++++++ .../providers/IndependentReserveTest.java | 34 +++++++++++ .../bisq/price/spot/providers/LunoTest.java | 34 +++++++++++ .../spot/providers/MercadoBitcoinTest.java | 34 +++++++++++ .../bisq/price/spot/providers/ParibuTest.java | 34 +++++++++++ .../bisq/price/spot/providers/QuoineTest.java | 34 +++++++++++ 30 files changed, 1114 insertions(+), 6 deletions(-) create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/BTCMarkets.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Bitbay.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Bitflyer.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Bitstamp.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/CexIO.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Coinmate.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Coinone.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Exmo.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/IndependentReserve.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Luno.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/MercadoBitcoin.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Paribu.java create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Quoine.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/BTCMarketsTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/BitbayTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/BitflyerTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/BitstampTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/CexIOTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/CoinmateTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/CoinoneTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/ExmoTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/IndependentReserveTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/LunoTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/MercadoBitcoinTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/ParibuTest.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/QuoineTest.java diff --git a/build.gradle b/build.gradle index f0568e7e04d..fd6d13ab5d8 100644 --- a/build.gradle +++ b/build.gradle @@ -468,12 +468,25 @@ configure(project(':pricenode')) { implementation("org.apache.httpcomponents:httpclient:$httpclientVersion") { exclude(module: 'commons-codec') } + compile("org.knowm.xchange:xchange-bitbay:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-bitcoinaverage:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-btcmarkets:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-binance:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-bitfinex:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-bitflyer:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-bitstamp:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-cexio:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-coinmate:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-coinmarketcap:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-coinone:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-exmo:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-independentreserve:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-kraken:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-luno:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-mercadobitcoin:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-paribu:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-poloniex:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-quoine:$knowmXchangeVersion") compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") compile("org.springframework.boot:spring-boot-starter-actuator") testCompile "org.junit.jupiter:junit-jupiter-api:$jupiterVersion" diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index b753bc8bfd4..a53f7b49dc8 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -27,6 +27,7 @@ import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; import org.knowm.xchange.service.marketdata.MarketDataService; import org.knowm.xchange.service.marketdata.params.CurrencyPairsParam; import org.knowm.xchange.service.marketdata.params.Params; @@ -35,6 +36,7 @@ import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -129,7 +131,7 @@ protected Set doGet(Class exchangeClass) { // The benefits of this approach (vs polling each ticker) are twofold: // 1) the polling of the exchange is faster (one HTTP call vs several) // 2) it's easier to stay below any API rate limits the exchange might have - List tickersRetrievedFromExchange; + List tickersRetrievedFromExchange = new ArrayList<>(); try { tickersRetrievedFromExchange = marketDataService.getTickers(new CurrencyPairsParam() { @@ -167,7 +169,49 @@ public Collection getCurrencyPairs() { return Collections.emptyList(); } }); - } catch (IOException e) { + + if (tickersRetrievedFromExchange.isEmpty()) { + // If the bulk ticker retrieval went through, but no tickers were + // retrieved, this is a strong indication that this specific exchange + // needs a specific list of pairs given as argument, for bulk retrieval to + // work. See requiresFilterDuringBulkTickerRetrieval() + throw new IllegalArgumentException("No tickers retrieved, " + + "exchange requires explicit filter argument during bulk retrieval?"); + } + } + catch (NotYetImplementedForExchangeException e) { + // Thrown when a provider has no marketDataService.getTickers() implementation + // either because the exchange API does not provide it, or because it has not + // been implemented yet in the knowm xchange library + + // In this case (retrieval of bulk tickers is not possible) retrieve the + // tickers one by one + List finalTickersRetrievedFromExchange = tickersRetrievedFromExchange; + Stream.of(desiredFiatPairs, desiredCryptoPairs) + .flatMap(Collection::stream) + .collect(Collectors.toList()) + .forEach(cp -> { + try { + + // This is done in a loop, and can therefore result in a burst + // of API calls. Some exchanges do not allow bursts + // A simplistic solution is to delay every call by 1 second + // TODO Switch to using a more elegant solution (per exchange) + // like ResilienceSpecification (needs knowm xchange libs v5) + if (getMarketDataCallDelay() > 0) { + Thread.sleep(getMarketDataCallDelay()); + } + + Ticker ticker = marketDataService.getTicker(cp); + finalTickersRetrievedFromExchange.add(ticker); + + } catch (IOException | InterruptedException ioException) { + ioException.printStackTrace(); + log.error("Could not query tickers for " + getName(), e); + } + }); + } + catch (Exception e) { // If there was a problem with polling this exchange, return right away, // since there are no results to parse and process log.error("Could not query tickers for provider " + getName(), e); @@ -209,6 +253,18 @@ public Collection getCurrencyPairs() { return result; } + /** + * Specifies optional delay between certain kind of API calls that can result in + * bursts. We want to avoid bursts, because this can cause certain exchanges to + * temporarily restrict access to the pricenode IP. + * + * @return Amount of milliseconds of delay between marketDataService.getTicker calls. + * By default 0, but can be overwritten by each provider. + */ + protected long getMarketDataCallDelay() { + return 0; + } + /** * @return Whether or not the bulk retrieval of tickers from the exchange requires an * explicit filter (list of desired pairs) or not. If true, the diff --git a/pricenode/src/main/java/bisq/price/spot/providers/BTCMarkets.java b/pricenode/src/main/java/bisq/price/spot/providers/BTCMarkets.java new file mode 100644 index 00000000000..41efdbd58e8 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/BTCMarkets.java @@ -0,0 +1,45 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.btcmarkets.BTCMarketsExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class BTCMarkets extends ExchangeRateProvider { + + public BTCMarkets() { + super("BTCMARKETS", "btcmarkets", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: AUD + // Supported alts: ETH, LTC + return doGet(BTCMarketsExchange.class); + } + +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java index c76f9175ee1..c94107d1ea8 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Binance.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Binance.java @@ -37,9 +37,9 @@ public Binance() { @Override public Set doGet() { - // Supported fiat: EUR, NGN, RUB, TRY, ZAR - // Supported alts: BEAM, DASH, DCR, DOGE, ETC, ETH, LTC, NAV, PIVX, XMR, XZC, ZEC, - // ZEN + // Supported fiat: EUR, GBP, NGN, RUB, TRY, UAH, ZAR + // Supported alts: BEAM, DAI, DASH, DCR, DOGE, ETC, ETH, LTC, NAV, PIVX, XMR, XZC, + // ZEC, ZEN return doGet(BinanceExchange.class); } } diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitbay.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitbay.java new file mode 100644 index 00000000000..32e82b377fb --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitbay.java @@ -0,0 +1,44 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.bitbay.BitbayExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Bitbay extends ExchangeRateProvider { + + public Bitbay() { + super("BITBAY", "bitbay", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: EUR, GBP, PLN, USD + // Supported alts: DASH, ETH, LTC + return doGet(BitbayExchange.class); + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitflyer.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitflyer.java new file mode 100644 index 00000000000..e5a78f78496 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitflyer.java @@ -0,0 +1,44 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.bitflyer.BitflyerExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Bitflyer extends ExchangeRateProvider { + + public Bitflyer() { + super("BITFLYER", "bitflyer", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: JPY + // Supported alts: ETH + return doGet(BitflyerExchange.class); + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitstamp.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitstamp.java new file mode 100644 index 00000000000..ed130aa15d3 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitstamp.java @@ -0,0 +1,44 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.bitstamp.BitstampExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Bitstamp extends ExchangeRateProvider { + + public Bitstamp() { + super("BITSTAMP", "bitstamp", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: EUR, GBP, USD + // Supported alts: ETH, LTC + return doGet(BitstampExchange.class); + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/CexIO.java b/pricenode/src/main/java/bisq/price/spot/providers/CexIO.java new file mode 100644 index 00000000000..254eadb9eed --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/CexIO.java @@ -0,0 +1,49 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.cexio.CexIOExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class CexIO extends ExchangeRateProvider { + + public CexIO() { + super("CexIO", "cexio", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: EUR, GBP, RUB, USD + // Supported alts: DASH, ETH, LTC + return doGet(CexIOExchange.class); + } + + @Override + protected boolean requiresFilterDuringBulkTickerRetrieval() { + return true; + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Coinmate.java b/pricenode/src/main/java/bisq/price/spot/providers/Coinmate.java new file mode 100644 index 00000000000..8f059271132 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Coinmate.java @@ -0,0 +1,45 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.coinmate.CoinmateExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Coinmate extends ExchangeRateProvider { + + public Coinmate() { + super("Coinmate", "coinmate", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: CZK, EUR + // Supported alts: DASH, ETH, LTC + return doGet(CoinmateExchange.class); + } + +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Coinone.java b/pricenode/src/main/java/bisq/price/spot/providers/Coinone.java new file mode 100644 index 00000000000..6ae54aef253 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Coinone.java @@ -0,0 +1,45 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.coinone.CoinoneExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Coinone extends ExchangeRateProvider { + + public Coinone() { + super("COINONE", "coinone", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: KRW + // Supported alts: - + return doGet(CoinoneExchange.class); + } + +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Exmo.java b/pricenode/src/main/java/bisq/price/spot/providers/Exmo.java new file mode 100644 index 00000000000..51d0daf5b93 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Exmo.java @@ -0,0 +1,46 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.exmo.ExmoExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Exmo extends ExchangeRateProvider { + + public Exmo() { + // API rate limit = 10 calls / second from the same IP ( see https://exmo.com/en/api ) + super("EXMO", "exmo", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: EUR, PLN, RUB, UAH (Ukrainian hryvnia), USD + // Supported alts: DASH, DOGE, ETC, ETH, LTC, XMR, ZEC + return doGet(ExmoExchange.class); + } + +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/IndependentReserve.java b/pricenode/src/main/java/bisq/price/spot/providers/IndependentReserve.java new file mode 100644 index 00000000000..d0d6c9604cb --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/IndependentReserve.java @@ -0,0 +1,45 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.independentreserve.IndependentReserveExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class IndependentReserve extends ExchangeRateProvider { + + public IndependentReserve() { + super("IndependentReserve", "independentreserve", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: AUD, NZD (New Zealand Dollar), USD + // Supported alts: - + return doGet(IndependentReserveExchange.class); + } + +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Luno.java b/pricenode/src/main/java/bisq/price/spot/providers/Luno.java new file mode 100644 index 00000000000..b642fc47ceb --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Luno.java @@ -0,0 +1,52 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.luno.LunoExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Luno extends ExchangeRateProvider { + + public Luno() { + super("LUNO", "luno", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: IDR (Indonesian rupiah), MYR (Malaysian ringgit), + // NGN (Nigerian Naira), ZAR (South African rand) + // Supported alts: - + return doGet(LunoExchange.class); + } + + @Override + protected long getMarketDataCallDelay() { + // Luno allows only 1 MarketData call per second + // (see https://www.luno.com/en/developers/api ) + return 1000; + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/MercadoBitcoin.java b/pricenode/src/main/java/bisq/price/spot/providers/MercadoBitcoin.java new file mode 100644 index 00000000000..d790dd43277 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/MercadoBitcoin.java @@ -0,0 +1,45 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.mercadobitcoin.MercadoBitcoinExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class MercadoBitcoin extends ExchangeRateProvider { + + public MercadoBitcoin() { + super("MercadoBitcoin", "mercadobitcoin", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: BRL (Brazilian Real) + // Supported alts: - + return doGet(MercadoBitcoinExchange.class); + } + +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Paribu.java b/pricenode/src/main/java/bisq/price/spot/providers/Paribu.java new file mode 100644 index 00000000000..b9929a62df3 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Paribu.java @@ -0,0 +1,44 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.paribu.ParibuExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Paribu extends ExchangeRateProvider { + + public Paribu() { + super("PARIBU", "paribu", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: TRY (Turkish Lira) + // Supported alts: - + return doGet(ParibuExchange.class); + } +} diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java b/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java index e61ec36e6e1..5d5e2be6c4d 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java @@ -38,7 +38,7 @@ public Poloniex() { @Override public Set doGet() { // Supported fiat: - - // Supported alts: DASH, DCR, DOGE, ETC, ETH, GRIN, LTC, XMR, ZEC + // Supported alts: DASH, DCR, DOGE, ETC, ETH, LTC, XMR, ZEC return doGet(PoloniexExchange.class); } } diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Quoine.java b/pricenode/src/main/java/bisq/price/spot/providers/Quoine.java new file mode 100644 index 00000000000..e04a76798b5 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Quoine.java @@ -0,0 +1,49 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.quoine.QuoineExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Quoine extends ExchangeRateProvider { + + public Quoine() { + super("QUOINE", "quoine", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + // Supported fiat: AUD, CNY, EUR, HKD, IDR, INR, JPY, PHP, SGD, USD + // Supported alts: ETH + return doGet(QuoineExchange.class); + } + + @Override + protected boolean requiresFilterDuringBulkTickerRetrieval() { + return true; + } +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BTCMarketsTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BTCMarketsTest.java new file mode 100644 index 00000000000..1bd1278e56a --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/BTCMarketsTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class BTCMarketsTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new BTCMarkets()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BitbayTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BitbayTest.java new file mode 100644 index 00000000000..fc02ee7a628 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/BitbayTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class BitbayTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Bitbay()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BitflyerTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BitflyerTest.java new file mode 100644 index 00000000000..e43d192b366 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/BitflyerTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class BitflyerTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Bitflyer()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BitstampTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BitstampTest.java new file mode 100644 index 00000000000..69e3cba6041 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/BitstampTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class BitstampTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Bitstamp()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/CexIOTest.java b/pricenode/src/test/java/bisq/price/spot/providers/CexIOTest.java new file mode 100644 index 00000000000..cf9dc6dcbe0 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/CexIOTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class CexIOTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new CexIO()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/CoinmateTest.java b/pricenode/src/test/java/bisq/price/spot/providers/CoinmateTest.java new file mode 100644 index 00000000000..fa8bd1f73f8 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/CoinmateTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class CoinmateTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Coinmate()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/CoinoneTest.java b/pricenode/src/test/java/bisq/price/spot/providers/CoinoneTest.java new file mode 100644 index 00000000000..c722c188acd --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/CoinoneTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class CoinoneTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Coinone()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/ExmoTest.java b/pricenode/src/test/java/bisq/price/spot/providers/ExmoTest.java new file mode 100644 index 00000000000..68148f45b39 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/ExmoTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class ExmoTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Exmo()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/IndependentReserveTest.java b/pricenode/src/test/java/bisq/price/spot/providers/IndependentReserveTest.java new file mode 100644 index 00000000000..ccf7cb8b873 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/IndependentReserveTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class IndependentReserveTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new IndependentReserve()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/LunoTest.java b/pricenode/src/test/java/bisq/price/spot/providers/LunoTest.java new file mode 100644 index 00000000000..eaa95115778 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/LunoTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class LunoTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Luno()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/MercadoBitcoinTest.java b/pricenode/src/test/java/bisq/price/spot/providers/MercadoBitcoinTest.java new file mode 100644 index 00000000000..92585acb28b --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/MercadoBitcoinTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class MercadoBitcoinTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new MercadoBitcoin()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/ParibuTest.java b/pricenode/src/test/java/bisq/price/spot/providers/ParibuTest.java new file mode 100644 index 00000000000..148a1291cb0 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/ParibuTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class ParibuTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Paribu()); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/QuoineTest.java b/pricenode/src/test/java/bisq/price/spot/providers/QuoineTest.java new file mode 100644 index 00000000000..2067453cce8 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/QuoineTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class QuoineTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Quoine()); + } + +} From 9be2a5bbb4be8c1ee81824e62039443b18814c52 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Mon, 27 Jul 2020 13:22:53 +0200 Subject: [PATCH 14/26] Integrate Bitpay exchange rate API Add a Bitpay exchange rate provider and corresponding unit test. --- .../bisq/price/spot/providers/Bitpay.java | 84 +++++++++++++++++++ .../price/util/bitpay/BitpayMarketData.java | 13 +++ .../bisq/price/util/bitpay/BitpayTicker.java | 18 ++++ .../bisq/price/spot/providers/BitpayTest.java | 34 ++++++++ 4 files changed, 149 insertions(+) create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java create mode 100644 pricenode/src/main/java/bisq/price/util/bitpay/BitpayMarketData.java create mode 100644 pricenode/src/main/java/bisq/price/util/bitpay/BitpayTicker.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/BitpayTest.java diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java new file mode 100644 index 00000000000..0cf92a78086 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java @@ -0,0 +1,84 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; +import bisq.price.util.bitpay.BitpayMarketData; +import bisq.price.util.bitpay.BitpayTicker; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.RequestEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.time.Duration; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; + +@Component +class Bitpay extends ExchangeRateProvider { + + private final RestTemplate restTemplate = new RestTemplate(); + + public Bitpay() { + super("BITPAY", "bitpay", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + + Set result = new HashSet(); + + Predicate isDesiredFiatPair = t -> SUPPORTED_FIAT_CURRENCIES.contains(t.getCode()); + Predicate isDesiredCryptoPair = t -> SUPPORTED_CRYPTO_CURRENCIES.contains(t.getCode()); + + getTickers() + .filter(isDesiredFiatPair.or(isDesiredCryptoPair)) + .forEach(ticker -> { + result.add(new ExchangeRate( + ticker.getCode(), + ticker.getRate(), + new Date(), + this.getName() + )); + }); + + return result; + } + + private Stream getTickers() { + BitpayMarketData marketData = restTemplate.exchange( + RequestEntity + .get(UriComponentsBuilder + .fromUriString("https://bitpay.com/rates").build() + .toUri()) + .build(), + new ParameterizedTypeReference() { + } + ).getBody(); + + return Arrays.stream(marketData.getData()); + } +} diff --git a/pricenode/src/main/java/bisq/price/util/bitpay/BitpayMarketData.java b/pricenode/src/main/java/bisq/price/util/bitpay/BitpayMarketData.java new file mode 100644 index 00000000000..73ab946a9bc --- /dev/null +++ b/pricenode/src/main/java/bisq/price/util/bitpay/BitpayMarketData.java @@ -0,0 +1,13 @@ +package bisq.price.util.bitpay; + + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BitpayMarketData { + + private BitpayTicker[] data; + +} diff --git a/pricenode/src/main/java/bisq/price/util/bitpay/BitpayTicker.java b/pricenode/src/main/java/bisq/price/util/bitpay/BitpayTicker.java new file mode 100644 index 00000000000..8cc63773ac3 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/util/bitpay/BitpayTicker.java @@ -0,0 +1,18 @@ +package bisq.price.util.bitpay; + +import java.math.BigDecimal; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BitpayTicker { + + private String code; + + private String name; + + private BigDecimal rate; + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/BitpayTest.java b/pricenode/src/test/java/bisq/price/spot/providers/BitpayTest.java new file mode 100644 index 00000000000..38569e1ce84 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/BitpayTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class BitpayTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Bitpay()); + } + +} From 399f65d404b44cbaff069817efb7fc9cfdc17d11 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Mon, 27 Jul 2020 14:49:41 +0200 Subject: [PATCH 15/26] Integrate CoinGecko API Add CoinGecko ExchangeRateProvider and corresponding unit test. --- .../bisq/price/spot/providers/CoinGecko.java | 87 +++++++++++++++++++ .../util/coingecko/CoinGeckoMarketData.java | 22 +++++ .../price/util/coingecko/CoinGeckoTicker.java | 20 +++++ .../price/spot/providers/CoinGeckoTest.java | 34 ++++++++ 4 files changed, 163 insertions(+) create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java create mode 100644 pricenode/src/main/java/bisq/price/util/coingecko/CoinGeckoMarketData.java create mode 100644 pricenode/src/main/java/bisq/price/util/coingecko/CoinGeckoTicker.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/CoinGeckoTest.java diff --git a/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java b/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java new file mode 100644 index 00000000000..7b9412545c0 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java @@ -0,0 +1,87 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; +import bisq.price.util.coingecko.CoinGeckoMarketData; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.RequestEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.time.Duration; + +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@Component +class CoinGecko extends ExchangeRateProvider { + + private final RestTemplate restTemplate = new RestTemplate(); + + public CoinGecko() { + super("COINGECKO", "coingecko", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + + // Rate limit for the CoinGecko API is 10 calls each second per IP address + // We retrieve all rates in bulk, so we only make 1 call per provider poll + + Set result = new HashSet(); + + Predicate isDesiredFiatPair = t -> SUPPORTED_FIAT_CURRENCIES.contains(t.getKey()); + Predicate isDesiredCryptoPair = t -> SUPPORTED_CRYPTO_CURRENCIES.contains(t.getKey()); + + getMarketData().getRates().entrySet().stream() + .filter(isDesiredFiatPair.or(isDesiredCryptoPair)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) + .forEach((key, ticker) -> { + + result.add(new ExchangeRate( + key, + ticker.getValue(), + new Date(), + this.getName() + )); + + }); + + return result; + } + + private CoinGeckoMarketData getMarketData() { + return restTemplate.exchange( + RequestEntity + .get(UriComponentsBuilder + .fromUriString("https://api.coingecko.com/api/v3/exchange_rates").build() + .toUri()) + .build(), + new ParameterizedTypeReference() { + } + ).getBody(); + } +} diff --git a/pricenode/src/main/java/bisq/price/util/coingecko/CoinGeckoMarketData.java b/pricenode/src/main/java/bisq/price/util/coingecko/CoinGeckoMarketData.java new file mode 100644 index 00000000000..b96ba10dcaa --- /dev/null +++ b/pricenode/src/main/java/bisq/price/util/coingecko/CoinGeckoMarketData.java @@ -0,0 +1,22 @@ +package bisq.price.util.coingecko; + + +import java.util.Map; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CoinGeckoMarketData { + + private Map rates; + + public void setRates(Map rates) { + // Convert keys to uppercase ("usd" -> "USD") when deserializing API response + this.rates = rates.entrySet().stream() + .collect(Collectors.toMap(entry -> entry.getKey().toUpperCase(), entry -> entry.getValue())); + } + +} diff --git a/pricenode/src/main/java/bisq/price/util/coingecko/CoinGeckoTicker.java b/pricenode/src/main/java/bisq/price/util/coingecko/CoinGeckoTicker.java new file mode 100644 index 00000000000..2326203a100 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/util/coingecko/CoinGeckoTicker.java @@ -0,0 +1,20 @@ +package bisq.price.util.coingecko; + +import java.math.BigDecimal; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CoinGeckoTicker { + + private String name; + + private String unit; + + private BigDecimal value; + + private String type; + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/CoinGeckoTest.java b/pricenode/src/test/java/bisq/price/spot/providers/CoinGeckoTest.java new file mode 100644 index 00000000000..43a1a865c87 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/CoinGeckoTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class CoinGeckoTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new CoinGecko()); + } + +} From 5a194420eddb1c44fa440e94589ca0c3b5992f47 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Mon, 27 Jul 2020 16:15:20 +0200 Subject: [PATCH 16/26] Integrate Coinpaprika API Add Coinpaprika ExchangeRateProvider and corresponding unit test. --- .../price/spot/providers/Coinpaprika.java | 102 ++++++++++++++++++ .../coinpaprika/CoinpaprikaMarketData.java | 18 ++++ .../util/coinpaprika/CoinpaprikaTicker.java | 16 +++ .../price/spot/providers/CoinpaprikaTest.java | 34 ++++++ 4 files changed, 170 insertions(+) create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Coinpaprika.java create mode 100644 pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaMarketData.java create mode 100644 pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaTicker.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/CoinpaprikaTest.java diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Coinpaprika.java b/pricenode/src/main/java/bisq/price/spot/providers/Coinpaprika.java new file mode 100644 index 00000000000..423436e400e --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Coinpaprika.java @@ -0,0 +1,102 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; +import bisq.price.util.coinpaprika.CoinpaprikaMarketData; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.RequestEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.time.Duration; + +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@Component +class Coinpaprika extends ExchangeRateProvider { + + private final RestTemplate restTemplate = new RestTemplate(); + + /** + * Used to determine the currencies in which the BTC price can be quoted. There seems + * to be no programatic way to retrieve it, so we get the value from the API + * documentation (see "quotes" param decsribed at + * https://api.coinpaprika.com/#operation/getTickersById ). The hardcoded value below + * is the list of allowed values as per the API documentation, but without BTC and ETH + * as it makes no sense to quote the BTC price in them. + */ + private final static String SUPPORTED_CURRENCIES = + ("USD, EUR, PLN, KRW, GBP, CAD, JPY, RUB, TRY, NZD, AUD, CHF, UAH, HKD, " + + "SGD, NGN, PHP, MXN, BRL, THB, CLP, CNY, CZK, DKK, HUF, IDR, ILS," + + "INR, MYR, NOK, PKR, SEK, TWD, ZAR, VND, BOB, COP, PEN, ARS, ISK") + .replace(" " , ""); // Strip any spaces + + public Coinpaprika() { + super("COINPAPRIKA", "coinpaprika", Duration.ofMinutes(1)); + } + + @Override + public Set doGet() { + + // Single IP address can send less than 10 requests per second + // We make only 1 API call per provider poll, so we're not at risk of reaching it + + Set result = new HashSet(); + + Predicate isDesiredFiatPair = t -> SUPPORTED_FIAT_CURRENCIES.contains(t.getKey()); + + getMarketData().getQuotes().entrySet().stream() + .filter(isDesiredFiatPair) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) + .forEach((key, ticker) -> { + + result.add(new ExchangeRate( + key, + ticker.getPrice(), + new Date(), + this.getName() + )); + + }); + + return result; + } + + private CoinpaprikaMarketData getMarketData() { + return restTemplate.exchange( + RequestEntity + .get(UriComponentsBuilder + .fromUriString( + "https://api.coinpaprika.com/v1/tickers/btc-bitcoin?quotes=" + + SUPPORTED_CURRENCIES).build() + .toUri()) + .build(), + new ParameterizedTypeReference() { + } + ).getBody(); + } +} diff --git a/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaMarketData.java b/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaMarketData.java new file mode 100644 index 00000000000..23c64cdc58f --- /dev/null +++ b/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaMarketData.java @@ -0,0 +1,18 @@ +package bisq.price.util.coinpaprika; + + +import java.util.Map; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CoinpaprikaMarketData { + + // All other json fields can be ignored, we don't need them + + private Map quotes; + +} diff --git a/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaTicker.java b/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaTicker.java new file mode 100644 index 00000000000..64bb3183120 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaTicker.java @@ -0,0 +1,16 @@ +package bisq.price.util.coinpaprika; + +import java.math.BigDecimal; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CoinpaprikaTicker { + + // All other json fields can be ignored, we don't need them + + private BigDecimal price; + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/CoinpaprikaTest.java b/pricenode/src/test/java/bisq/price/spot/providers/CoinpaprikaTest.java new file mode 100644 index 00000000000..27eb4a642b7 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/CoinpaprikaTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class CoinpaprikaTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Coinpaprika()); + } + +} From b362b4c8d28659ddcfde77a384cdbdf3924dbc58 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Mon, 27 Jul 2020 18:35:51 +0200 Subject: [PATCH 17/26] Integrate Huobi exchange API Add Huobi ExchangeRateProvider and corresponding unit test. --- build.gradle | 1 + .../java/bisq/price/spot/providers/Huobi.java | 43 +++++++++++++++++++ .../bisq/price/spot/providers/HuobiTest.java | 34 +++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Huobi.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/HuobiTest.java diff --git a/build.gradle b/build.gradle index fd6d13ab5d8..786822d4018 100644 --- a/build.gradle +++ b/build.gradle @@ -480,6 +480,7 @@ configure(project(':pricenode')) { compile("org.knowm.xchange:xchange-coinmarketcap:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-coinone:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-exmo:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-huobi:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-independentreserve:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-kraken:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-luno:$knowmXchangeVersion") diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Huobi.java b/pricenode/src/main/java/bisq/price/spot/providers/Huobi.java new file mode 100644 index 00000000000..48be4f4930e --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Huobi.java @@ -0,0 +1,43 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.huobi.HuobiExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Huobi extends ExchangeRateProvider { + + public Huobi() { super("HUOBI", "huobi", Duration.ofMinutes(1)); } + + @Override + public Set doGet() { + // Supported fiat: - + // Supported alts: BTM, DASH, DCR, DOGE, ETC, ETH, FAIR, LTC, XMR, XZC, ZEC, ZEN + return doGet(HuobiExchange.class); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/HuobiTest.java b/pricenode/src/test/java/bisq/price/spot/providers/HuobiTest.java new file mode 100644 index 00000000000..2ad87856f8e --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/HuobiTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class HuobiTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Huobi()); + } + +} From efda45fa78a8d7bdb64c1d4459ba0dc75c060799 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Mon, 27 Jul 2020 18:40:13 +0200 Subject: [PATCH 18/26] Integrate Hitbtc exchange API Add Hitbtc ExchangeRateProvider and corresponding unit test. --- build.gradle | 1 + .../bisq/price/spot/providers/Hitbtc.java | 44 +++++++++++++++++++ .../bisq/price/spot/providers/HitbtcTest.java | 34 ++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 pricenode/src/main/java/bisq/price/spot/providers/Hitbtc.java create mode 100644 pricenode/src/test/java/bisq/price/spot/providers/HitbtcTest.java diff --git a/build.gradle b/build.gradle index 786822d4018..1a5e2ef66c8 100644 --- a/build.gradle +++ b/build.gradle @@ -480,6 +480,7 @@ configure(project(':pricenode')) { compile("org.knowm.xchange:xchange-coinmarketcap:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-coinone:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-exmo:$knowmXchangeVersion") + compile("org.knowm.xchange:xchange-hitbtc:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-huobi:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-independentreserve:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-kraken:$knowmXchangeVersion") diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Hitbtc.java b/pricenode/src/main/java/bisq/price/spot/providers/Hitbtc.java new file mode 100644 index 00000000000..68e584f0662 --- /dev/null +++ b/pricenode/src/main/java/bisq/price/spot/providers/Hitbtc.java @@ -0,0 +1,44 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.spot.ExchangeRate; +import bisq.price.spot.ExchangeRateProvider; + +import org.knowm.xchange.hitbtc.v2.HitbtcExchange; + +import org.springframework.stereotype.Component; + +import java.time.Duration; + +import java.util.Set; + +@Component +class Hitbtc extends ExchangeRateProvider { + + public Hitbtc() { super("HITBTC", "hitbtc", Duration.ofMinutes(1)); } + + @Override + public Set doGet() { + // Supported fiat: USD + // Supported alts: AEON, BTM, DASH, DCR, DOGE, EMC, ETC, ETH, GRIN, LTC, NAV, + // PART, XMR, XRC, XZC, ZEC, ZEN + return doGet(HitbtcExchange.class); + } + +} diff --git a/pricenode/src/test/java/bisq/price/spot/providers/HitbtcTest.java b/pricenode/src/test/java/bisq/price/spot/providers/HitbtcTest.java new file mode 100644 index 00000000000..0d70e427225 --- /dev/null +++ b/pricenode/src/test/java/bisq/price/spot/providers/HitbtcTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.price.spot.providers; + +import bisq.price.AbstractExchangeRateProviderTest; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.Test; + +@Slf4j +public class HitbtcTest extends AbstractExchangeRateProviderTest { + + @Test + public void doGet_successfulCall() { + doGet_successfulCall(new Hitbtc()); + } + +} From 8d335441c337400b32a226cfae94053a5ce66fbb Mon Sep 17 00:00:00 2001 From: cd2357 Date: Tue, 28 Jul 2020 20:45:32 +0200 Subject: [PATCH 19/26] Fix Bitpay and CoinGecko altcoin rates Correctly interpret the alt conversion rate reported by the API. For alts, Bisq needs the Alt/BTC rate, whereas the API returns the BTC/Alt one. Calculate the inverse of the reported values before storing them as Bisq internal datastructures (ExchangeRates). --- .../bisq/price/spot/providers/Bitpay.java | 22 ++++++++++++++++++- .../bisq/price/spot/providers/CoinGecko.java | 21 ++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java index 0cf92a78086..e76aeeb040e 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java @@ -22,6 +22,8 @@ import bisq.price.util.bitpay.BitpayMarketData; import bisq.price.util.bitpay.BitpayTicker; +import org.knowm.xchange.utils.BigDecimalUtils; + import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.RequestEntity; import org.springframework.stereotype.Component; @@ -30,6 +32,9 @@ import java.time.Duration; +import java.math.BigDecimal; +import java.math.RoundingMode; + import java.util.Arrays; import java.util.Date; import java.util.HashSet; @@ -57,9 +62,24 @@ public Set doGet() { getTickers() .filter(isDesiredFiatPair.or(isDesiredCryptoPair)) .forEach(ticker -> { + boolean useInverseRate = false; + if (SUPPORTED_CRYPTO_CURRENCIES.contains(ticker.getCode())) { + // 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; + } + + BigDecimal rate = ticker.getRate(); + // Find the inverse rate, while using enough decimals to reflect very + // small exchange rates + BigDecimal inverseRate = (rate.compareTo(BigDecimal.ZERO) > 0) ? + BigDecimal.ONE.divide(rate, 8, RoundingMode.HALF_UP) : + BigDecimal.ZERO; + result.add(new ExchangeRate( ticker.getCode(), - ticker.getRate(), + (useInverseRate ? inverseRate : rate), new Date(), this.getName() )); diff --git a/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java b/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java index 7b9412545c0..b6ff2dd5a52 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java @@ -29,6 +29,9 @@ import java.time.Duration; +import java.math.BigDecimal; +import java.math.RoundingMode; + import java.util.Date; import java.util.HashSet; import java.util.Map; @@ -61,13 +64,27 @@ public Set doGet() { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) .forEach((key, ticker) -> { + boolean useInverseRate = false; + if (SUPPORTED_CRYPTO_CURRENCIES.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; + } + + BigDecimal rate = ticker.getValue(); + // Find the inverse rate, while using enough decimals to reflect very + // small exchange rates + BigDecimal inverseRate = (rate.compareTo(BigDecimal.ZERO) > 0) ? + BigDecimal.ONE.divide(rate, 8, RoundingMode.HALF_UP) : + BigDecimal.ZERO; + result.add(new ExchangeRate( key, - ticker.getValue(), + (useInverseRate ? inverseRate : rate), new Date(), this.getName() )); - }); return result; From 4dc24e5606b4ccfd71af0be2266b87f227196457 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sun, 12 Jul 2020 16:31:43 +0200 Subject: [PATCH 20/26] Disable BitcoinAverage Disable BitcoinAverage provider. Keep it registered as a provider to ensure that the data structure returned by the pricenode to the Bisq clients contain the hardcoded "btcAverageTs" key. --- build.gradle | 1 - pricenode/README-HEROKU.md | 1 - pricenode/README.md | 16 -- pricenode/bisq-pricenode.env | 2 - pricenode/docker/loop.sh | 2 +- .../bisq/price/spot/ExchangeRateService.java | 6 - .../price/spot/providers/BitcoinAverage.java | 137 +++--------------- 7 files changed, 18 insertions(+), 147 deletions(-) diff --git a/build.gradle b/build.gradle index 1a5e2ef66c8..a2a1c3164bd 100644 --- a/build.gradle +++ b/build.gradle @@ -469,7 +469,6 @@ configure(project(':pricenode')) { exclude(module: 'commons-codec') } compile("org.knowm.xchange:xchange-bitbay:$knowmXchangeVersion") - compile("org.knowm.xchange:xchange-bitcoinaverage:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-btcmarkets:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-binance:$knowmXchangeVersion") compile("org.knowm.xchange:xchange-bitfinex:$knowmXchangeVersion") diff --git a/pricenode/README-HEROKU.md b/pricenode/README-HEROKU.md index 19365dc9ef9..ab6081cd96b 100644 --- a/pricenode/README-HEROKU.md +++ b/pricenode/README-HEROKU.md @@ -5,7 +5,6 @@ Run the following commands: heroku create heroku buildpacks:add heroku/gradle - heroku config:set BITCOIN_AVG_PUBKEY=[your pubkey] BITCOIN_AVG_PRIVKEY=[your privkey] git push heroku master curl https://your-app-123456.herokuapp.com/getAllMarketPrices diff --git a/pricenode/README.md b/pricenode/README.md index 55165678d3c..d71dcda6e60 100644 --- a/pricenode/README.md +++ b/pricenode/README.md @@ -24,7 +24,6 @@ Operating a production pricenode is a valuable service to the Bisq network, and To run a pricenode, you will need: - - [BitcoinAverage API keys](https://bitcoinaverage.com/en/plans). Free plans are fine for local development or personal nodes; paid plans should be used for well-known production nodes. - JDK 8 if you want to build and run a node locally. - The `tor` binary (e.g. `brew install tor`) if you want to run a hidden service locally. @@ -40,21 +39,6 @@ curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/ins At the end of the installer script, it should print your Tor onion hostname. -### Setting your BitcoinAverage API keys - -Open `/etc/default/bisq-pricenode.env` in a text editor and look for these lines: -```bash -BITCOIN_AVG_PUBKEY=foo -BITCOIN_AVG_PRIVKEY=bar -``` - -Add your pubkey and privkey and then reload/restart bisq-pricenode service: - -```bash -systemctl daemon-reload -systemctl restart bisq-pricenode -``` - ### Test To manually test endpoints, run each of the following: diff --git a/pricenode/bisq-pricenode.env b/pricenode/bisq-pricenode.env index 56a5fe34530..a70fa924c0a 100644 --- a/pricenode/bisq-pricenode.env +++ b/pricenode/bisq-pricenode.env @@ -1,3 +1 @@ -BITCOIN_AVG_PUBKEY=foo -BITCOIN_AVG_PRIVKEY=bar JAVA_OPTS="" diff --git a/pricenode/docker/loop.sh b/pricenode/docker/loop.sh index 1720f6eed90..1382fbb13c5 100644 --- a/pricenode/docker/loop.sh +++ b/pricenode/docker/loop.sh @@ -2,7 +2,7 @@ while true do echo `date` "(Re)-starting node" -BITCOIN_AVG_PUBKEY=$BTCAVERAGE_PUBKEY BITCOIN_AVG_PRIVKEY=$BTCAVERAGE_PRIVKEY java -jar ./build/libs/bisq-pricenode.jar 2 2 +java -jar ./build/libs/bisq-pricenode.jar 2 2 echo `date` "node terminated unexpectedly!!" sleep 3 done diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java index f7bc35f8912..81cefd266bf 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java @@ -17,8 +17,6 @@ package bisq.price.spot; -import bisq.price.spot.providers.BitcoinAverage; - import org.springframework.stereotype.Service; import java.math.BigDecimal; @@ -167,10 +165,6 @@ private Map getMetadata(ExchangeRateProvider provider, Set doGet() { - return getTickersKeyedByCurrency().entrySet().stream() - .filter(e -> supportedCurrency(e.getKey())) - .map(e -> - new ExchangeRate( - e.getKey(), - e.getValue().getLast(), - e.getValue().getTimestamp(), - this.getName() - ) - ) - .collect(Collectors.toSet()); - } - - private boolean supportedCurrency(String currencyCode) { - // ignore Venezuelan bolivars as the "official" exchange rate is just wishful thinking - // we should use this API with a custom provider instead: http://api.bitcoinvenezuela.com/1 - return !"VEF".equals(currencyCode); - } - - private Map getTickersKeyedByCurrency() { - // go from a map with keys like "BTCUSD", "BTCVEF" - return getTickersKeyedByCurrencyPair().entrySet().stream() - // to a map with keys like "USD", "VEF" - .collect(Collectors.toMap(e -> e.getKey().substring(3), Map.Entry::getValue)); - } - - private Map getTickersKeyedByCurrencyPair() { - return restTemplate.exchange( - RequestEntity - .get(UriComponentsBuilder - .fromUriString("https://apiv2.bitcoinaverage.com/indices/{symbol-set}/ticker/all?crypto=BTC") - .buildAndExpand(symbolSet) - .toUri()) - .header("X-signature", getAuthSignature()) - .build(), - BitcoinAverageTickers.class - ).getBody().getTickers(); - } - - protected String getAuthSignature() { - String payload = String.format("%s.%s", Instant.now().getEpochSecond(), pubKey); - return String.format("%s.%s", payload, Hex.encode(mac.doFinal(payload.getBytes(Charsets.UTF_8)))); - } - - private static Mac initMac(String privKey) { - String algorithm = "HmacSHA256"; - SecretKey secretKey = new SecretKeySpec(privKey.getBytes(Charsets.UTF_8), algorithm); - try { - Mac mac = Mac.getInstance(algorithm); - mac.init(secretKey); - return mac; - } catch (NoSuchAlgorithmException | InvalidKeyException ex) { - throw new RuntimeException(ex); - } - } - - private static Duration refreshIntervalFor(double pctMaxRequests) { - long requestsPerMonth = (long) (MAX_REQUESTS_PER_MONTH * pctMaxRequests); - return Duration.ofDays(31).dividedBy(requestsPerMonth); - } - - - @Component - @Order(1) - public static class Global extends BitcoinAverage { - public Global(Environment env) { - super("BTCA_G", "btcAverageG", 0.3, "global", env); - } - } - - - @Component - @Order(2) - public static class Local extends BitcoinAverage { - public Local(Environment env) { - super("BTCA_L", "btcAverageL", 0.7, "local", env); - } + HashSet exchangeRates = new HashSet<>(); + exchangeRates.add(new ExchangeRate("NON_EXISTING_SYMBOL_BA", 0, 0L, getName())); + return exchangeRates; } } From 82bbb2dafacc761d836f9b9496743647895261be Mon Sep 17 00:00:00 2001 From: cd2357 Date: Wed, 5 Aug 2020 23:20:14 +0200 Subject: [PATCH 21/26] Upgrade Tor to v3 Use tor v3 addresses during a new pricenode setup. --- pricenode/install_pricenode_debian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pricenode/install_pricenode_debian.sh b/pricenode/install_pricenode_debian.sh index e14033616d4..c90b7a26b7a 100755 --- a/pricenode/install_pricenode_debian.sh +++ b/pricenode/install_pricenode_debian.sh @@ -46,7 +46,7 @@ echo "[*] Adding Tor configuration" if ! grep "${BISQ_TORHS}" /etc/tor/torrc >/dev/null 2>&1;then sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${BISQ_TORHS}/ >> ${TOR_CONF}" sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServicePort 80 127.0.0.1:8080 >> ${TOR_CONF}" - sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceVersion 2 >> ${TOR_CONF}" + sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceVersion 3 >> ${TOR_CONF}" fi echo "[*] Creating Bisq user with Tor access" From 36dbb2e943b47216c7a520c145404878f65a68cf Mon Sep 17 00:00:00 2001 From: cd2357 Date: Wed, 5 Aug 2020 23:26:03 +0200 Subject: [PATCH 22/26] Upgrade Java to v11 Use Java 11 to run the pricenode service, since v11 includes by default some root certificates needed when establishing SSH connections to some of the new API endpoints. --- pricenode/install_pricenode_debian.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pricenode/install_pricenode_debian.sh b/pricenode/install_pricenode_debian.sh index c90b7a26b7a..dd9bc465416 100755 --- a/pricenode/install_pricenode_debian.sh +++ b/pricenode/install_pricenode_debian.sh @@ -60,8 +60,8 @@ echo "[*] Cloning Bisq repo" sudo -H -i -u "${BISQ_USER}" git config --global advice.detachedHead false sudo -H -i -u "${BISQ_USER}" git clone --branch "${BISQ_REPO_TAG}" "${BISQ_REPO_URL}" "${BISQ_HOME}/${BISQ_REPO_NAME}" -echo "[*] Installing OpenJDK 10.0.2 from Bisq repo" -sudo -H -i -u "${ROOT_USER}" "${BISQ_HOME}/${BISQ_REPO_NAME}/scripts/install_java.sh" +echo "[*] Installing OpenJDK 11" +sudo -H -i -u "${ROOT_USER}" apt-get install -y openjdk-11-jdk echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}" sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}" From 11076e773dccb95f39a08d4ae5e218a677ae91bc Mon Sep 17 00:00:00 2001 From: cd2357 <15956136+cd2357@users.noreply.github.com> Date: Thu, 6 Aug 2020 10:26:13 +0200 Subject: [PATCH 23/26] Set quiet flag for java install command Reduce verbosity of install command to only errors Co-authored-by: wiz --- pricenode/install_pricenode_debian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pricenode/install_pricenode_debian.sh b/pricenode/install_pricenode_debian.sh index dd9bc465416..06cd0f27d99 100755 --- a/pricenode/install_pricenode_debian.sh +++ b/pricenode/install_pricenode_debian.sh @@ -61,7 +61,7 @@ sudo -H -i -u "${BISQ_USER}" git config --global advice.detachedHead false sudo -H -i -u "${BISQ_USER}" git clone --branch "${BISQ_REPO_TAG}" "${BISQ_REPO_URL}" "${BISQ_HOME}/${BISQ_REPO_NAME}" echo "[*] Installing OpenJDK 11" -sudo -H -i -u "${ROOT_USER}" apt-get install -y openjdk-11-jdk +sudo -H -i -u "${ROOT_USER}" apt-get install -qq -y openjdk-11-jdk echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}" sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}" From 9fb5c0bb5350d13d0550b98471a1d0b7da506b05 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 8 Aug 2020 15:24:08 +0200 Subject: [PATCH 24/26] Remove unused imports Removed unused imports from pricenode classes. --- pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java | 2 -- .../bisq/price/util/coinpaprika/CoinpaprikaMarketData.java | 1 - .../test/java/bisq/price/AbstractExchangeRateProviderTest.java | 3 --- 3 files changed, 6 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java b/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java index e76aeeb040e..0c44cbc2db2 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Bitpay.java @@ -22,8 +22,6 @@ import bisq.price.util.bitpay.BitpayMarketData; import bisq.price.util.bitpay.BitpayTicker; -import org.knowm.xchange.utils.BigDecimalUtils; - import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.RequestEntity; import org.springframework.stereotype.Component; diff --git a/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaMarketData.java b/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaMarketData.java index 23c64cdc58f..b5e366ddbcb 100644 --- a/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaMarketData.java +++ b/pricenode/src/main/java/bisq/price/util/coinpaprika/CoinpaprikaMarketData.java @@ -2,7 +2,6 @@ import java.util.Map; -import java.util.stream.Collectors; import lombok.Getter; import lombok.Setter; diff --git a/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java b/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java index 8bf1f2d78d9..a459dc68b4a 100644 --- a/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java +++ b/pricenode/src/test/java/bisq/price/AbstractExchangeRateProviderTest.java @@ -3,9 +3,6 @@ import bisq.price.spot.ExchangeRate; import bisq.price.spot.ExchangeRateProvider; -import bisq.core.locale.CurrencyUtil; -import bisq.core.locale.TradeCurrency; - import com.google.common.collect.Sets; import java.util.Set; From 0c2703848c2f36bb3448c4ba8fc6d6ad00e66cc6 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 8 Aug 2020 16:05:15 +0200 Subject: [PATCH 25/26] Apply Codacy style changes Apply various changes in coding style, required by the Codacy check. --- .../src/main/java/bisq/price/spot/ExchangeRateProvider.java | 6 +++--- .../src/main/java/bisq/price/spot/ExchangeRateService.java | 6 +++--- .../src/main/java/bisq/price/spot/providers/CoinGecko.java | 2 +- .../main/java/bisq/price/spot/providers/Coinpaprika.java | 5 ++--- .../src/main/java/bisq/price/spot/providers/Hitbtc.java | 4 +++- .../src/main/java/bisq/price/spot/providers/Huobi.java | 4 +++- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index a53f7b49dc8..c8f87f1db56 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -159,10 +159,11 @@ protected Set doGet(Class exchangeClass) { public Collection getCurrencyPairs() { // If required by the exchange implementation, specify a filter // (list of pairs which should be retrieved) - if (requiresFilterDuringBulkTickerRetrieval()) + if (requiresFilterDuringBulkTickerRetrieval()) { return Stream.of(desiredFiatPairs, desiredCryptoPairs) .flatMap(Collection::stream) .collect(Collectors.toList()); + } // Otherwise, specify an empty list, indicating that the API should // simply return all available tickers @@ -178,8 +179,7 @@ public Collection getCurrencyPairs() { throw new IllegalArgumentException("No tickers retrieved, " + "exchange requires explicit filter argument during bulk retrieval?"); } - } - catch (NotYetImplementedForExchangeException e) { + } catch (NotYetImplementedForExchangeException e) { // Thrown when a provider has no marketDataService.getTickers() implementation // either because the exchange API does not provide it, or because it has not // been implemented yet in the knowm xchange library diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java index 81cefd266bf..881235d715e 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java @@ -99,18 +99,18 @@ private Map getAggregateExchangeRates() { // For each currency code, calculate aggregate rate currencyCodeToExchangeRates.forEach((currencyCode, exchangeRateList) -> { - if (exchangeRateList.isEmpty()) + if (exchangeRateList.isEmpty()) { // If the map was built incorrectly and this currency points to an empty // list of rates, skip it return; + } ExchangeRate aggregateExchangeRate; if (exchangeRateList.size() == 1) { // If a single provider has rates for this currency, then aggregate = rate // from that provider aggregateExchangeRate = exchangeRateList.get(0); - } - else { + } else { // If multiple providers have rates for this currency, then // aggregate = average of the rates OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average(); diff --git a/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java b/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java index b6ff2dd5a52..aaed9106963 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/CoinGecko.java @@ -85,7 +85,7 @@ public Set doGet() { new Date(), this.getName() )); - }); + }); return result; } diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Coinpaprika.java b/pricenode/src/main/java/bisq/price/spot/providers/Coinpaprika.java index 423436e400e..ca0c1e9a79a 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Coinpaprika.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Coinpaprika.java @@ -53,7 +53,7 @@ class Coinpaprika extends ExchangeRateProvider { ("USD, EUR, PLN, KRW, GBP, CAD, JPY, RUB, TRY, NZD, AUD, CHF, UAH, HKD, " + "SGD, NGN, PHP, MXN, BRL, THB, CLP, CNY, CZK, DKK, HUF, IDR, ILS," + "INR, MYR, NOK, PKR, SEK, TWD, ZAR, VND, BOB, COP, PEN, ARS, ISK") - .replace(" " , ""); // Strip any spaces + .replace(" ", ""); // Strip any spaces public Coinpaprika() { super("COINPAPRIKA", "coinpaprika", Duration.ofMinutes(1)); @@ -80,8 +80,7 @@ public Set doGet() { new Date(), this.getName() )); - - }); + }); return result; } diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Hitbtc.java b/pricenode/src/main/java/bisq/price/spot/providers/Hitbtc.java index 68e584f0662..cd98cff1732 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Hitbtc.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Hitbtc.java @@ -31,7 +31,9 @@ @Component class Hitbtc extends ExchangeRateProvider { - public Hitbtc() { super("HITBTC", "hitbtc", Duration.ofMinutes(1)); } + public Hitbtc() { + super("HITBTC", "hitbtc", Duration.ofMinutes(1)); + } @Override public Set doGet() { diff --git a/pricenode/src/main/java/bisq/price/spot/providers/Huobi.java b/pricenode/src/main/java/bisq/price/spot/providers/Huobi.java index 48be4f4930e..9514aa0cda5 100644 --- a/pricenode/src/main/java/bisq/price/spot/providers/Huobi.java +++ b/pricenode/src/main/java/bisq/price/spot/providers/Huobi.java @@ -31,7 +31,9 @@ @Component class Huobi extends ExchangeRateProvider { - public Huobi() { super("HUOBI", "huobi", Duration.ofMinutes(1)); } + public Huobi() { + super("HUOBI", "huobi", Duration.ofMinutes(1)); + } @Override public Set doGet() { From d972a7571a9a8fa3de7fa5c1f516f44ba47ba3a8 Mon Sep 17 00:00:00 2001 From: cd2357 Date: Sat, 8 Aug 2020 16:35:51 +0200 Subject: [PATCH 26/26] Improve exception handling to match Codacy rules Rewrite a few generic parts of the code to be more specific in what they handle, or how they handle the resulting data structure. --- .../bisq/price/spot/ExchangeRateProvider.java | 13 +++++++++---- .../bisq/price/spot/ExchangeRateService.java | 17 +++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java index c8f87f1db56..b15cd5faedb 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java @@ -27,6 +27,7 @@ import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.exceptions.ExchangeException; import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; import org.knowm.xchange.service.marketdata.MarketDataService; import org.knowm.xchange.service.marketdata.params.CurrencyPairsParam; @@ -210,8 +211,13 @@ public Collection getCurrencyPairs() { log.error("Could not query tickers for " + getName(), e); } }); - } - catch (Exception e) { + } catch (ExchangeException | // Errors reported by the exchange (rate limit, etc) + IOException | // Errors while trying to connect to the API (timeouts, etc) + // Potential error when integrating new exchange (hints that exchange + // provider implementation needs to overwrite + // requiresFilterDuringBulkTickerRetrieval() and have it return true ) + IllegalArgumentException e) { + // Catch and handle all other possible exceptions // If there was a problem with polling this exchange, return right away, // since there are no results to parse and process log.error("Could not query tickers for provider " + getName(), e); @@ -236,8 +242,7 @@ public Collection getCurrencyPairs() { String otherExchangeRateCurrency; if (t.getCurrencyPair().base.equals(Currency.BTC)) { otherExchangeRateCurrency = t.getCurrencyPair().counter.getCurrencyCode(); - } - else { + } else { otherExchangeRateCurrency = t.getCurrencyPair().base.getCurrencyCode(); } diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java index 881235d715e..6fcf4a6eb2b 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java @@ -70,14 +70,15 @@ public Map getAllMarketPrices() { metadata.putAll(getMetadata(p, exchangeRates)); }); - return new LinkedHashMap() {{ - putAll(metadata); - // Use a sorted list by currency code to make comparision of json data between - // different price nodes easier - List values = new ArrayList<>(aggregateExchangeRates.values()); - values.sort(Comparator.comparing(ExchangeRate::getCurrency)); - put("data", values); - }}; + LinkedHashMap result = new LinkedHashMap<>(); + result.putAll(metadata); + // Use a sorted list by currency code to make comparision of json data between + // different price nodes easier + List values = new ArrayList<>(aggregateExchangeRates.values()); + values.sort(Comparator.comparing(ExchangeRate::getCurrency)); + result.put("data", values); + + return result; } /**