Skip to content

Commit

Permalink
Integrate initial set of ExchangeRateProviders
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cd2357 committed Jun 16, 2020
1 parent ff732aa commit 09845a9
Show file tree
Hide file tree
Showing 11 changed files with 456 additions and 54 deletions.
11 changes: 10 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -458,20 +458,29 @@ 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"
implementation("org.apache.httpcomponents:httpclient:$httpclientVersion") {
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 {
Expand Down
93 changes: 93 additions & 0 deletions pricenode/src/main/java/bisq/price/spot/ExchangeRateProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ExchangeRate> doGet(Class<? extends Exchange> exchangeClass) {
Set<ExchangeRate> result = new HashSet<ExchangeRate>();

// Initialize XChange objects
Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeClass.getName());
MarketDataService marketDataService = exchange.getMarketDataService();

// Retrieve all currency pairs supported by the exchange
List<CurrencyPair> currencyPairs = exchange.getExchangeSymbols();

Set<String> supportedCryptoCurrencies = CurrencyUtil.getAllSortedCryptoCurrencies().stream()
.map(TradeCurrency::getCode)
.collect(Collectors.toSet());

Set<String> 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;
}
}
46 changes: 46 additions & 0 deletions pricenode/src/main/java/bisq/price/spot/providers/Binance.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<ExchangeRate> 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);
}
}
46 changes: 46 additions & 0 deletions pricenode/src/main/java/bisq/price/spot/providers/Bitfinex.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, JPY, USD
// Supported alts: DAI, ETC, ETH, LTC, XMR, ZEC
return doGet(BitfinexExchange.class);
}
}
46 changes: 46 additions & 0 deletions pricenode/src/main/java/bisq/price/spot/providers/Kraken.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<ExchangeRate> doGet() {
// Supported fiat: AUD, CAD, CHF, EUR, GBP, JPY, USD
// Supported alts: DASH, DOGE, ETC, ETH, LTC, XMR, ZEC
return doGet(KrakenExchange.class);
}
}
58 changes: 5 additions & 53 deletions pricenode/src/main/java/bisq/price/spot/providers/Poloniex.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,76 +19,28 @@

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));
}

@Override
public Set<ExchangeRate> 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<PoloniexTicker> 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<String, PoloniexMarketData> getTickersKeyedByCurrencyPair() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://poloniex.com/public?command=returnTicker").build()
.toUri())
.build(),
new ParameterizedTypeReference<Map<String, PoloniexMarketData>>() {
}
).getBody();
// Supported fiat: -
// Supported alts: DASH, DCR, DOGE, ETC, ETH, GRIN, LTC, XMR, ZEC
return doGet(PoloniexExchange.class);
}
}
Loading

0 comments on commit 09845a9

Please sign in to comment.