Add market price feed for altcoins

This commit is contained in:
Manfred Karrer 2016-02-10 13:59:22 +01:00
parent d2e8c6afd7
commit eefafab977
14 changed files with 177 additions and 63 deletions

View file

@ -13,12 +13,23 @@ public class MarketPrice {
private final double last;
public MarketPrice(String currencyCode, String ask, String bid, String last) {
this(currencyCode, ask, bid, last, false);
}
public MarketPrice(String currencyCode, String ask, String bid, String last, boolean invert) {
this.currencyCode = currencyCode;
if (invert) {
this.ask = 1d / parseDouble(ask);
this.bid = 1d / parseDouble(bid);
this.last = 1d / parseDouble(last);
} else {
this.ask = parseDouble(ask);
this.bid = parseDouble(bid);
this.last = parseDouble(last);
}
}
public double getPrice(MarketPriceFeed.Type type) {
switch (type) {
case ASK:

View file

@ -6,6 +6,7 @@ import com.google.common.util.concurrent.SettableFuture;
import com.google.inject.Inject;
import io.bitsquare.app.Log;
import io.bitsquare.btc.pricefeed.providers.BitcoinAveragePriceProvider;
import io.bitsquare.btc.pricefeed.providers.PoloniexPriceProvider;
import io.bitsquare.btc.pricefeed.providers.PriceProvider;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.FaultHandler;
@ -18,6 +19,7 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@ -43,15 +45,15 @@ public class MarketPriceFeed {
}
}
// TODO
// https://poloniex.com/public?command=returnTicker 33 kb
private static long PERIOD = 30;
private static long PERIOD_FIAT = 1; // We load only the selected currency on interval. Only the first request we load all
private static long PERIOD_CRYPTO = 10; // We load the full list with 33kb so we don't want to load too often
private final ScheduledThreadPoolExecutor executorService = Utilities.getScheduledThreadPoolExecutor("MarketPriceFeed", 5, 10, 120L);
private final ScheduledThreadPoolExecutor executorService = Utilities.getScheduledThreadPoolExecutor("MarketPriceFeed", 5, 10, 700L);
private final Map<String, MarketPrice> cache = new HashMap<>();
private final PriceProvider fiatPriceProvider = new BitcoinAveragePriceProvider();
private final PriceProvider cryptoCurrenciesPriceProvider = new PoloniexPriceProvider();
private Consumer<Double> priceConsumer;
private FaultHandler faultHandler;
private PriceProvider fiatPriceProvider = new BitcoinAveragePriceProvider();
private Type type;
private String currencyCode;
transient private final StringProperty currencyCodeProperty = new SimpleStringProperty();
@ -66,6 +68,7 @@ public class MarketPriceFeed {
public MarketPriceFeed() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@ -76,8 +79,18 @@ public class MarketPriceFeed {
requestAllPrices(fiatPriceProvider, () -> {
applyPrice();
executorService.scheduleAtFixedRate(() -> requestPrice(fiatPriceProvider), PERIOD, PERIOD, TimeUnit.SECONDS);
executorService.scheduleAtFixedRate(
() -> requestPrice(fiatPriceProvider),
PERIOD_FIAT, PERIOD_FIAT, TimeUnit.MINUTES);
});
requestAllPrices(cryptoCurrenciesPriceProvider, () -> {
applyPrice();
executorService.scheduleAtFixedRate(
() -> requestAllPrices(cryptoCurrenciesPriceProvider,
this::applyPrice),
PERIOD_CRYPTO, PERIOD_CRYPTO, TimeUnit.MINUTES);
});
requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice);
}
@ -156,7 +169,7 @@ public class MarketPriceFeed {
});
}
private void requestAllPrices(PriceProvider provider, Runnable resultHandler) {
private void requestAllPrices(PriceProvider provider, @Nullable Runnable resultHandler) {
Log.traceCall();
GetPriceRequest getPriceRequest = new GetPriceRequest();
SettableFuture<Map<String, MarketPrice>> future = getPriceRequest.requestAllPrices(provider);
@ -164,6 +177,7 @@ public class MarketPriceFeed {
public void onSuccess(Map<String, MarketPrice> marketPriceMap) {
UserThread.execute(() -> {
cache.putAll(marketPriceMap);
if (resultHandler != null)
resultHandler.run();
});
}

View file

@ -0,0 +1,81 @@
package io.bitsquare.btc.pricefeed.providers;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.LinkedTreeMap;
import io.bitsquare.app.Log;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class PoloniexPriceProvider implements PriceProvider {
private static final Logger log = LoggerFactory.getLogger(PoloniexPriceProvider.class);
//https://poloniex.com/public?command=returnTicker
private final HttpClient httpClient = new HttpClient("https://poloniex.com/public");
public PoloniexPriceProvider() {
}
@Override
public Map<String, MarketPrice> getAllPrices() throws IOException, HttpException {
Map<String, MarketPrice> marketPriceMap = new HashMap<>();
String response = httpClient.requestWithGET("?command=returnTicker");
LinkedTreeMap<String, Object> treeMap = new Gson().fromJson(response, LinkedTreeMap.class);
Map<String, String> temp = new HashMap<>();
Set<String> supported = CurrencyUtil.getSortedCryptoCurrencies().stream()
.map(TradeCurrency::getCode)
.collect(Collectors.toSet());
treeMap.entrySet().stream().forEach(e -> {
Object value = e.getValue();
String currencyPair = e.getKey();
String otherCurrency = null;
if (currencyPair.startsWith("BTC")) {
String[] tokens = currencyPair.split("_");
if (tokens.length > 1) {
otherCurrency = tokens[1];
if (supported.contains(otherCurrency)) {
if (value instanceof LinkedTreeMap) {
LinkedTreeMap<String, Object> treeMap2 = (LinkedTreeMap) value;
temp.clear();
treeMap2.entrySet().stream().forEach(e2 -> temp.put(e2.getKey(), e2.getValue().toString()));
marketPriceMap.put(otherCurrency,
new MarketPrice(otherCurrency, temp.get("lowestAsk"), temp.get("highestBid"), temp.get("last"), true));
}
}
}
}
});
return marketPriceMap;
}
@Override
public MarketPrice getPrice(String currencyCode) throws IOException, HttpException {
Log.traceCall("currencyCode=" + currencyCode);
JsonObject jsonObject = new JsonParser()
.parse(httpClient.requestWithGET(currencyCode))
.getAsJsonObject();
return new MarketPrice(currencyCode,
jsonObject.get("ask").getAsString(),
jsonObject.get("bid").getAsString(),
jsonObject.get("last").getAsString());
}
@Override
public String toString() {
return "BitcoinAveragePriceProvider{" +
'}';
}
}

View file

@ -178,20 +178,20 @@ public class CurrencyUtil {
// result.add(new CryptoCurrency("XMR", "Monero"));
// result.add(new CryptoCurrency("BCN", "Bytecoin"));
result.add(new CryptoCurrency("DASH", "Dash"));
result.add(new CryptoCurrency("ANC", "Anoncoin"));
result.add(new CryptoCurrency("NBT", "NuBits"));
result.add(new CryptoCurrency("NSR", "NuShares"));
result.add(new CryptoCurrency("FAIR", "FairCoin"));
result.add(new CryptoCurrency("PPC", "Peercoin"));
result.add(new CryptoCurrency("XPM", "Primecoin"));
result.add(new CryptoCurrency("SC", "Siacoin"));
result.add(new CryptoCurrency("SJCX", "StorjcoinX"));
result.add(new CryptoCurrency("GEMZ", "Gemz"));
result.add(new CryptoCurrency("DOGE", "Dogecoin"));
result.add(new CryptoCurrency("BLK", "Blackcoin"));
result.add(new CryptoCurrency("FCT", "Factom"));
result.add(new CryptoCurrency("NXT", "Nxt"));
result.add(new CryptoCurrency("BTS", "BitShares"));
result.add(new CryptoCurrency("XCP", "Counterparty"));
result.add(new CryptoCurrency("XRP", "Ripple"));
// Stellar (XLM Lumen) uses an additional memo field. We dont support that for now
//result.add(new CryptoCurrency("STR", "Stellar"));
return result;
}

View file

@ -247,7 +247,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
private Tuple3<TextField, Label, VBox> getMarketPriceBox(String text) {
TextField textField = new TextField();
textField.setEditable(false);
textField.setPrefWidth(140);
textField.setPrefWidth(150);
textField.setMouseTransparent(true);
textField.setFocusTraversable(false);
textField.setStyle("-fx-alignment: center; -fx-background-color: -bs-bg-grey;");

View file

@ -83,11 +83,20 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
UserThread.execute(InputTextField::hideErrorMessageDisplay);
if (newValue != null) {
if (newValue.equals(createOfferTab) && createOfferView != null) {
createOfferView.onTabSelected();
createOfferView.onTabSelected(true);
} else if (newValue.equals(takeOfferTab) && takeOfferView != null) {
takeOfferView.onTabSelected();
takeOfferView.onTabSelected(true);
} else if (newValue.equals(offerBookTab) && offerBookView != null) {
offerBookView.onTabSelected();
offerBookView.onTabSelected(true);
}
}
if (oldValue != null) {
if (oldValue.equals(createOfferTab) && createOfferView != null) {
createOfferView.onTabSelected(false);
} else if (oldValue.equals(takeOfferTab) && takeOfferView != null) {
takeOfferView.onTabSelected(false);
} else if (oldValue.equals(offerBookTab) && offerBookView != null) {
offerBookView.onTabSelected(false);
}
}
});
@ -130,6 +139,7 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
offerBookTab.setContent(view.getRoot());
tabPane.getTabs().add(offerBookTab);
offerBookView = (OfferBookView) view;
offerBookView.onTabSelected(true);
OfferActionHandler offerActionHandler = new OfferActionHandler() {
@Override

View file

@ -103,6 +103,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
private PaymentAccount paymentAccount;
private WalletEventListener walletEventListener;
private boolean isTabSelected;
///////////////////////////////////////////////////////////////////////////////////////////
@ -181,6 +182,9 @@ class CreateOfferDataModel extends ActivatableDataModel {
if (direction == Offer.Direction.BUY)
calculateTotalToPay();
if (isTabSelected)
marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get());
}
@Override
@ -227,7 +231,9 @@ class CreateOfferDataModel extends ActivatableDataModel {
marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get());
}
void onTabSelected() {
void onTabSelected(boolean isSelected) {
this.isTabSelected = isSelected;
if (isTabSelected)
marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get());
}

View file

@ -201,8 +201,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
this.closeHandler = closeHandler;
}
public void onTabSelected() {
model.dataModel.onTabSelected();
public void onTabSelected(boolean isSelected) {
model.dataModel.onTabSelected(isSelected);
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -197,8 +197,8 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
this.offerActionHandler = offerActionHandler;
}
public void onTabSelected() {
model.onTabSelected();
public void onTabSelected(boolean isSelected) {
model.onTabSelected(isSelected);
}

View file

@ -67,6 +67,7 @@ class OfferBookViewModel extends ActivatableViewModel {
private PaymentMethod paymentMethod = new AllPaymentMethodsEntry();
private final ObservableList<OfferBookListItem> offerBookListItems;
private final ListChangeListener<OfferBookListItem> listChangeListener;
private boolean isTabSelected;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
@ -94,7 +95,6 @@ class OfferBookViewModel extends ActivatableViewModel {
tradeCurrency = CurrencyUtil.getDefaultTradeCurrency();
tradeCurrencyCode.set(tradeCurrency.getCode());
marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get());
}
@Override
@ -103,6 +103,8 @@ class OfferBookViewModel extends ActivatableViewModel {
offerBookListItems.addListener(listChangeListener);
offerBook.fillOfferBookListItems();
filterList();
if (isTabSelected)
marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get());
}
@Override
@ -119,7 +121,9 @@ class OfferBookViewModel extends ActivatableViewModel {
this.direction = direction;
}
void onTabSelected() {
void onTabSelected(boolean isSelected) {
this.isTabSelected = isSelected;
if (isTabSelected)
marketPriceFeed.setCurrencyCode(tradeCurrencyCode.get());
}

View file

@ -82,6 +82,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
private BalanceListener balanceListener;
private PaymentAccount paymentAccount;
private boolean isTabSelected;
///////////////////////////////////////////////////////////////////////////////////////////
@ -114,6 +115,9 @@ class TakeOfferDataModel extends ActivatableDataModel {
addBindings();
addListeners();
updateBalance(walletService.getBalanceForAddress(addressEntry.getAddress()));
if (isTabSelected)
marketPriceFeed.setCurrencyCode(offer.getCurrencyCode());
}
@Override
@ -162,7 +166,9 @@ class TakeOfferDataModel extends ActivatableDataModel {
tradeManager.checkOfferAvailability(offer, resultHandler);
}
void onTabSelected() {
void onTabSelected(boolean isSelected) {
this.isTabSelected = isSelected;
if (isTabSelected)
marketPriceFeed.setCurrencyCode(offer.getCurrencyCode());
}

View file

@ -310,8 +310,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
new Popup().warning("You have already funds paid in.\nIn the <Funds/Open for withdrawal> section you can withdraw those funds.").show();*/
}
public void onTabSelected() {
model.dataModel.onTabSelected();
public void onTabSelected(boolean isSelected) {
model.dataModel.onTabSelected(isSelected);
}

View file

@ -23,6 +23,7 @@ import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.popups.OpenEmergencyTicketPopup;
import io.bitsquare.gui.popups.TradeDetailsPopup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.trade.Trade;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
@ -50,6 +51,7 @@ import javax.inject.Inject;
public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTradesViewModel> {
private final TradeDetailsPopup tradeDetailsPopup;
private BSFormatter formatter;
@FXML
TableView<PendingTradesListItem> table;
@FXML
@ -74,9 +76,10 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PendingTradesView(PendingTradesViewModel model, TradeDetailsPopup tradeDetailsPopup) {
public PendingTradesView(PendingTradesViewModel model, TradeDetailsPopup tradeDetailsPopup, BSFormatter formatter) {
super(model);
this.tradeDetailsPopup = tradeDetailsPopup;
this.formatter = formatter;
}
@Override
@ -232,7 +235,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
super.updateItem(item, empty);
if (item != null && !empty) {
field = new HyperlinkWithIcon(model.formatTradeId(item.getId()), true);
field = new HyperlinkWithIcon(item.getId(), true);
field.setOnAction(event -> tradeDetailsPopup.show(item.getTrade()));
field.setTooltip(new Tooltip("Open popup for details"));
setGraphic(field);
@ -267,7 +270,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
} else {
setId("-fx-text-fill: black");
}
setText(model.getDate(item));
setText(formatter.formatDateTime(item.getTrade().getDate()));
} else {
setText(null);
}
@ -282,7 +285,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
new StringConverter<Coin>() {
@Override
public String toString(Coin value) {
return model.formatTradeAmount(value);
return formatter.formatCoinWithCode(value);
}
@Override
@ -297,7 +300,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
new StringConverter<Fiat>() {
@Override
public String toString(Fiat value) {
return model.formatPrice(value);
return formatter.formatPriceWithCode(value);
}
@Override
@ -313,7 +316,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
new StringConverter<Fiat>() {
@Override
public String toString(Fiat value) {
return model.formatTradeVolume(value);
return formatter.formatFiatWithCode(value);
}
@Override

View file

@ -34,8 +34,6 @@ import io.bitsquare.trade.offer.Offer;
import javafx.beans.property.*;
import javafx.collections.ObservableList;
import org.bitcoinj.core.BlockChainListener;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
@ -225,21 +223,6 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
}
// columns
String formatTradeId(String value) {
return value;
}
String formatTradeAmount(Coin value) {
return formatter.formatCoinWithCode(value);
}
String formatPrice(Fiat value) {
return formatter.formatFiat(value);
}
String formatTradeVolume(Fiat value) {
return formatter.formatFiatWithCode(value);
}
public String getRemainingTime() {
return formatter.getPeriodBetweenBlockHeights(getBestChainHeight(),
@ -255,10 +238,6 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
return 0;
}
String getDate(PendingTradesListItem item) {
return formatter.formatDateTime(item.getTrade().getDate());
}
public boolean showWarning(Trade trade) {
return getBestChainHeight() >= trade.getCheckPaymentTimeAsBlockHeight();
}