From 13a62b1342d319e7260fca4cbe5e677106db835b Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Fri, 15 Apr 2016 00:35:31 +0200 Subject: [PATCH] Add pricefeed to offer, display offerprice with perc. (WIP) --- .../java/io/bitsquare/trade/TradeManager.java | 7 ++- .../trade/closed/ClosedTradableManager.java | 4 +- .../trade/failed/FailedTradesManager.java | 4 +- .../java/io/bitsquare/trade/offer/Offer.java | 52 +++++++++++++++---- .../trade/offer/OfferBookService.java | 18 +++++-- .../trade/offer/OpenOfferManager.java | 5 +- .../createoffer/CreateOfferDataModel.java | 3 +- .../createoffer/CreateOfferViewModel.java | 39 +++++++++----- .../offer/offerbook/OfferBookViewModel.java | 13 ++++- .../offer/takeoffer/TakeOfferViewModel.java | 5 +- .../offerbook/OfferBookViewModelTest.java | 3 +- 11 files changed, 115 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/io/bitsquare/trade/TradeManager.java b/core/src/main/java/io/bitsquare/trade/TradeManager.java index 53b6aec5f9..f2f321b843 100644 --- a/core/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/core/src/main/java/io/bitsquare/trade/TradeManager.java @@ -24,6 +24,7 @@ import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.AddressEntryException; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.handlers.FaultHandler; import io.bitsquare.common.handlers.ResultHandler; @@ -97,6 +98,7 @@ public class TradeManager { FailedTradesManager failedTradesManager, ArbitratorManager arbitratorManager, P2PService p2PService, + PriceFeed priceFeed, @Named("storage.dir") File storageDir) { this.user = user; this.keyRing = keyRing; @@ -109,8 +111,9 @@ public class TradeManager { this.p2PService = p2PService; tradableListStorage = new Storage<>(storageDir); - this.trades = new TradableList<>(tradableListStorage, "PendingTrades"); - + trades = new TradableList<>(tradableListStorage, "PendingTrades"); + trades.forEach(e -> e.getOffer().setPriceFeed(priceFeed)); + p2PService.addDecryptedDirectMessageListener(new DecryptedDirectMessageListener() { @Override public void onDirectMessage(DecryptedMsgWithPubKey decryptedMsgWithPubKey, NodeAddress peerNodeAddress) { diff --git a/core/src/main/java/io/bitsquare/trade/closed/ClosedTradableManager.java b/core/src/main/java/io/bitsquare/trade/closed/ClosedTradableManager.java index c738c798b5..cd5dbb6eb7 100644 --- a/core/src/main/java/io/bitsquare/trade/closed/ClosedTradableManager.java +++ b/core/src/main/java/io/bitsquare/trade/closed/ClosedTradableManager.java @@ -18,6 +18,7 @@ package io.bitsquare.trade.closed; import com.google.inject.Inject; +import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.storage.Storage; import io.bitsquare.trade.Tradable; @@ -37,9 +38,10 @@ public class ClosedTradableManager { private final KeyRing keyRing; @Inject - public ClosedTradableManager(KeyRing keyRing, @Named("storage.dir") File storageDir) { + public ClosedTradableManager(KeyRing keyRing, PriceFeed priceFeed, @Named("storage.dir") File storageDir) { this.keyRing = keyRing; this.closedTrades = new TradableList<>(new Storage<>(storageDir), "ClosedTrades"); + closedTrades.forEach(e -> e.getOffer().setPriceFeed(priceFeed)); } public void add(Tradable tradable) { diff --git a/core/src/main/java/io/bitsquare/trade/failed/FailedTradesManager.java b/core/src/main/java/io/bitsquare/trade/failed/FailedTradesManager.java index 200eb98b0b..e0b3d931ff 100644 --- a/core/src/main/java/io/bitsquare/trade/failed/FailedTradesManager.java +++ b/core/src/main/java/io/bitsquare/trade/failed/FailedTradesManager.java @@ -18,6 +18,7 @@ package io.bitsquare.trade.failed; import com.google.inject.Inject; +import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.storage.Storage; import io.bitsquare.trade.TradableList; @@ -37,9 +38,10 @@ public class FailedTradesManager { private final KeyRing keyRing; @Inject - public FailedTradesManager(KeyRing keyRing, @Named("storage.dir") File storageDir) { + public FailedTradesManager(KeyRing keyRing, PriceFeed priceFeed, @Named("storage.dir") File storageDir) { this.keyRing = keyRing; this.failedTrades = new TradableList<>(new Storage<>(storageDir), "FailedTrades"); + failedTrades.forEach(e -> e.getOffer().setPriceFeed(priceFeed)); } public void add(Trade trade) { diff --git a/core/src/main/java/io/bitsquare/trade/offer/Offer.java b/core/src/main/java/io/bitsquare/trade/offer/Offer.java index 6fc326b289..ae5caeeff7 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java @@ -19,6 +19,8 @@ package io.bitsquare.trade.offer; import io.bitsquare.app.Version; import io.bitsquare.btc.Restrictions; +import io.bitsquare.btc.pricefeed.MarketPrice; +import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.crypto.PubKeyRing; import io.bitsquare.common.handlers.ResultHandler; @@ -106,7 +108,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload private final long date; private final long protocolVersion; private final long fiatPrice; - private final double percentageBasedPrice; + private final double marketPriceMargin; private final boolean usePercentageBasedPrice; private final long amount; private final long minAmount; @@ -129,6 +131,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload transient private OfferAvailabilityProtocol availabilityProtocol; @JsonExclude transient private StringProperty errorMessageProperty = new SimpleStringProperty(); + transient private PriceFeed priceFeed; /////////////////////////////////////////////////////////////////////////////////////////// @@ -140,7 +143,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload PubKeyRing pubKeyRing, Direction direction, long fiatPrice, - double percentageBasedPrice, + double marketPriceMargin, boolean usePercentageBasedPrice, long amount, long minAmount, @@ -151,13 +154,14 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload @Nullable String countryCode, @Nullable ArrayList acceptedCountryCodes, @Nullable String bankId, - @Nullable ArrayList acceptedBankIds) { + @Nullable ArrayList acceptedBankIds, + PriceFeed priceFeed) { this.id = id; this.offererNodeAddress = offererNodeAddress; this.pubKeyRing = pubKeyRing; this.direction = direction; this.fiatPrice = fiatPrice; - this.percentageBasedPrice = percentageBasedPrice; + this.marketPriceMargin = marketPriceMargin; this.usePercentageBasedPrice = usePercentageBasedPrice; this.amount = amount; this.minAmount = minAmount; @@ -169,6 +173,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload this.acceptedCountryCodes = acceptedCountryCodes; this.bankId = bankId; this.acceptedBankIds = acceptedBankIds; + this.priceFeed = priceFeed; protocolVersion = Version.TRADE_PROTOCOL_VERSION; @@ -269,6 +274,10 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload // Setters /////////////////////////////////////////////////////////////////////////////////////////// + public void setPriceFeed(PriceFeed priceFeed) { + this.priceFeed = priceFeed; + } + public void setState(State state) { this.state = state; stateProperty().set(state); @@ -318,11 +327,34 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload } public Fiat getPrice() { - return Fiat.valueOf(currencyCode, fiatPrice); + Fiat priceAsFiat = Fiat.valueOf(currencyCode, fiatPrice); + if (usePercentageBasedPrice && priceFeed != null) { + MarketPrice marketPrice = priceFeed.getMarketPrice(currencyCode); + if (marketPrice != null) { + PriceFeed.Type priceFeedType = direction == Direction.SELL ? PriceFeed.Type.ASK : PriceFeed.Type.BID; + double marketPriceAsDouble = marketPrice.getPrice(priceFeedType); + double factor = direction == Offer.Direction.BUY ? 1 - marketPriceMargin : 1 + marketPriceMargin; + double targetPrice = marketPriceAsDouble * factor; + try { + return Fiat.parseFiat(currencyCode, String.valueOf(targetPrice)); + } catch (Exception e) { + log.warn("Exception at parseToFiat: " + e.toString()); + log.warn("We use the static price."); + return priceAsFiat; + } + } else { + log.warn("We don't have a market price. We use the static price instead."); + return priceAsFiat; + } + } else { + if (priceFeed == null) + log.warn("priceFeed must not be null"); + return priceAsFiat; + } } - public double getPercentageBasedPrice() { - return percentageBasedPrice; + public double getMarketPriceMargin() { + return marketPriceMargin; } public boolean isUsePercentageBasedPrice() { @@ -408,7 +440,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload Offer offer = (Offer) o; if (date != offer.date) return false; if (fiatPrice != offer.fiatPrice) return false; - if (Double.compare(offer.percentageBasedPrice, percentageBasedPrice) != 0) return false; + if (Double.compare(offer.marketPriceMargin, marketPriceMargin) != 0) return false; if (usePercentageBasedPrice != offer.usePercentageBasedPrice) return false; if (amount != offer.amount) return false; if (minAmount != offer.minAmount) return false; @@ -441,7 +473,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload result = 31 * result + (currencyCode != null ? currencyCode.hashCode() : 0); result = 31 * result + (int) (date ^ (date >>> 32)); result = 31 * result + (int) (fiatPrice ^ (fiatPrice >>> 32)); - long temp = Double.doubleToLongBits(percentageBasedPrice); + long temp = Double.doubleToLongBits(marketPriceMargin); result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (usePercentageBasedPrice ? 1 : 0); result = 31 * result + (int) (amount ^ (amount >>> 32)); @@ -467,7 +499,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload "\n\tcurrencyCode='" + currencyCode + '\'' + "\n\tdate=" + date + "\n\tfiatPrice=" + fiatPrice + - "\n\tpercentagePrice=" + percentageBasedPrice + + "\n\tmarketPriceMargin=" + marketPriceMargin + "\n\tusePercentageBasedPrice=" + usePercentageBasedPrice + "\n\tamount=" + amount + "\n\tminAmount=" + minAmount + diff --git a/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java b/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java index 810447fe79..c3650bee08 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.offer; +import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.common.handlers.ErrorMessageHandler; import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.p2p.P2PService; @@ -45,6 +46,7 @@ public class OfferBookService { } private final P2PService p2PService; + private PriceFeed priceFeed; private final List offerBookChangedListeners = new LinkedList<>(); @@ -53,15 +55,19 @@ public class OfferBookService { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public OfferBookService(P2PService p2PService) { + public OfferBookService(P2PService p2PService, PriceFeed priceFeed) { this.p2PService = p2PService; + this.priceFeed = priceFeed; p2PService.addHashSetChangedListener(new HashMapChangedListener() { @Override public void onAdded(ProtectedStorageEntry data) { offerBookChangedListeners.stream().forEach(listener -> { - if (data.getStoragePayload() instanceof Offer) - listener.onAdded((Offer) data.getStoragePayload()); + if (data.getStoragePayload() instanceof Offer) { + Offer offer = (Offer) data.getStoragePayload(); + offer.setPriceFeed(priceFeed); + listener.onAdded(offer); + } }); } @@ -118,7 +124,11 @@ public class OfferBookService { public List getOffers() { return p2PService.getDataMap().values().stream() .filter(data -> data.getStoragePayload() instanceof Offer) - .map(data -> (Offer) data.getStoragePayload()) + .map(data -> { + Offer offer = (Offer) data.getStoragePayload(); + offer.setPriceFeed(priceFeed); + return offer; + }) .collect(Collectors.toList()); } diff --git a/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java b/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java index e5fca72a65..7b8f89fce7 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java @@ -22,6 +22,7 @@ import io.bitsquare.app.Log; import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.common.Timer; import io.bitsquare.common.UserThread; import io.bitsquare.common.crypto.KeyRing; @@ -95,6 +96,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe TradeWalletService tradeWalletService, OfferBookService offerBookService, ClosedTradableManager closedTradableManager, + PriceFeed priceFeed, @Named("storage.dir") File storageDir) { this.keyRing = keyRing; this.user = user; @@ -105,7 +107,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe this.closedTradableManager = closedTradableManager; openOffersStorage = new Storage<>(storageDir); - this.openOffers = new TradableList<>(openOffersStorage, "OpenOffers"); + openOffers = new TradableList<>(openOffersStorage, "OpenOffers"); + openOffers.forEach(e -> e.getOffer().setPriceFeed(priceFeed)); // In case the app did get killed the shutDown from the modules is not called, so we use a shutdown hook Runtime.getRuntime().addShutdownHook(new Thread(OpenOfferManager.this::shutDown, diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index b7b464fc14..23c73acf0b 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -301,7 +301,8 @@ class CreateOfferDataModel extends ActivatableDataModel { countryCode, acceptedCountryCodes, bankId, - acceptedBanks); + acceptedBanks, + priceFeed); } void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java index 14d1a009d6..be6cbf61aa 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java @@ -117,6 +117,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel usePercentageBasedPriceListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -239,18 +240,20 @@ class CreateOfferViewModel extends ActivatableWithDataModel { + if (newValue) + priceValidationResult.set(new InputValidator.ValidationResult(true)); + }; + volumeListener = (ov, oldValue, newValue) -> { if (isFiatInputValid(newValue).isValid) { setVolumeToModel(); @@ -324,6 +332,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel preferences.getMaxPriceDistanceInPercent()) { + double percentage = Math.abs(1 - (offerPrice / marketPriceAsDouble)); + if (marketPriceAsDouble != 0 && percentage > preferences.getMaxPriceDistanceInPercent()) { displayPriceOutofRangePopup(); return false; } else { diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java index 62b52e990e..b908e49af5 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java @@ -257,10 +257,19 @@ class OfferBookViewModel extends ActivatableViewModel { } String getPrice(OfferBookListItem item) { + if ((item == null)) + return ""; + + Offer offer = item.getOffer(); + Fiat price = offer.getPrice(); + String postFix = ""; + if (offer.isUsePercentageBasedPrice()) { + postFix = " (" + formatter.formatToPercentWithSymbol(offer.getMarketPriceMargin()) + ")"; + } if (showAllTradeCurrenciesProperty.get()) - return (item != null) ? formatter.formatFiatWithCode(item.getOffer().getPrice()) : ""; + return formatter.formatPriceWithCode(price) + postFix; else - return (item != null) ? formatter.formatFiat(item.getOffer().getPrice()) : ""; + return formatter.formatFiat(price) + postFix; } String getVolume(OfferBookListItem item) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferViewModel.java index ed850c58d8..11d2f17c48 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferViewModel.java @@ -18,6 +18,7 @@ package io.bitsquare.gui.main.offer.takeoffer; import io.bitsquare.arbitration.Arbitrator; +import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.model.ActivatableWithDataModel; import io.bitsquare.gui.common.model.ViewModel; @@ -52,6 +53,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel im final TakeOfferDataModel dataModel; private final BtcValidator btcValidator; private final P2PService p2PService; + private PriceFeed priceFeed; private final Navigation navigation; final BSFormatter formatter; @@ -101,13 +103,14 @@ class TakeOfferViewModel extends ActivatableWithDataModel im /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, P2PService p2PService, + public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, P2PService p2PService, PriceFeed priceFeed, Navigation navigation, BSFormatter formatter) { super(dataModel); this.dataModel = dataModel; this.btcValidator = btcValidator; this.p2PService = p2PService; + this.priceFeed = priceFeed; this.navigation = navigation; this.formatter = formatter; diff --git a/gui/src/test/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModelTest.java b/gui/src/test/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModelTest.java index 40c259c712..21e92daeb3 100644 --- a/gui/src/test/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModelTest.java +++ b/gui/src/test/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModelTest.java @@ -277,6 +277,7 @@ public class OfferBookViewModelTest { countryCode, acceptedCountryCodes, bankId, - acceptedBanks); + acceptedBanks, + null); } }