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 289d505f7d..fd284dd968 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java @@ -107,15 +107,18 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload private final String id; private final long date; private final long protocolVersion; - // Price if fixed price is used (usePercentageBasedPrice = false) + + // We use 2 type of prices: fixed price or price based on distance from market price + private final boolean useMarketBasedPrice; + // fiatPrice if fixed price is used (usePercentageBasedPrice = false), otherwise 0 private final long fiatPrice; - // Distance form market price if percentage based price is used (usePercentageBasedPrice = true). + // Distance form market price if percentage based price is used (usePercentageBasedPrice = true), otherwise 0. // E.g. 0.1 -> 10%. Can be negative as well. Depending on direction the marketPriceMargin is above or below the market price. // Positive values is always the usual case where you want a better price as the market. // E.g. Buy offer with market price 400.- leads to a 360.- price. // Sell offer with market price 400.- leads to a 440.- price. - private final double marketPriceMargin; - private final boolean usePercentageBasedPrice; + private final double marketPriceMargin; + private final long amount; private final long minAmount; private final NodeAddress offererNodeAddress; @@ -150,7 +153,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload Direction direction, long fiatPrice, double marketPriceMargin, - boolean usePercentageBasedPrice, + boolean useMarketBasedPrice, long amount, long minAmount, String currencyCode, @@ -168,7 +171,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload this.direction = direction; this.fiatPrice = fiatPrice; this.marketPriceMargin = marketPriceMargin; - this.usePercentageBasedPrice = usePercentageBasedPrice; + this.useMarketBasedPrice = useMarketBasedPrice; this.amount = amount; this.minAmount = minAmount; this.currencyCode = currencyCode; @@ -234,10 +237,12 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload } public Fiat getVolumeByAmount(Coin amount) { - if (fiatPrice != 0 && amount != null && !amount.isZero()) + try { return new ExchangeRate(getPrice()).coinToFiat(amount); - else - return null; + } catch (Throwable t) { + log.error("getVolumeByAmount failed. Error=" + t.getMessage()); + return Fiat.valueOf(currencyCode, 0); + } } public Fiat getOfferVolume() { @@ -333,8 +338,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload } public Fiat getPrice() { - Fiat priceAsFiat = Fiat.valueOf(currencyCode, fiatPrice); - if (usePercentageBasedPrice && priceFeed != null) { + if (useMarketBasedPrice) { + checkNotNull(priceFeed, "priceFeed must not be null"); MarketPrice marketPrice = priceFeed.getMarketPrice(currencyCode); if (marketPrice != null) { PriceFeed.Type priceFeedType = direction == Direction.BUY ? PriceFeed.Type.ASK : PriceFeed.Type.BID; @@ -347,22 +352,22 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload targetPrice = targetPrice * factor1; long tmp = Math.round(targetPrice); targetPrice = (double) tmp / factor1; - + 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; + log.error("Exception at getPrice / parseToFiat: " + e.toString() + "\n" + + "We use an inaccessible price to avoid null pointers.\n" + + "That case should never happen."); + return Fiat.valueOf(currencyCode, direction == Direction.BUY ? Long.MIN_VALUE : Long.MAX_VALUE); } } else { - log.warn("We don't have a market price. We use the static price instead."); - return priceAsFiat; + log.warn("We don't have a market price. We use an inaccessible price to avoid null pointers.\n" + + "That case could only happen if you don't get a price feed."); + return Fiat.valueOf(currencyCode, direction == Direction.BUY ? Long.MIN_VALUE : Long.MAX_VALUE); } } else { - if (priceFeed == null) - log.warn("priceFeed must not be null"); - return priceAsFiat; + return Fiat.valueOf(currencyCode, fiatPrice); } } @@ -370,8 +375,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload return marketPriceMargin; } - public boolean getUsePercentageBasedPrice() { - return usePercentageBasedPrice; + public boolean getUseMarketBasedPrice() { + return useMarketBasedPrice; } public Coin getAmount() { @@ -454,7 +459,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload if (date != offer.date) return false; if (fiatPrice != offer.fiatPrice) return false; if (Double.compare(offer.marketPriceMargin, marketPriceMargin) != 0) return false; - if (usePercentageBasedPrice != offer.usePercentageBasedPrice) return false; + if (useMarketBasedPrice != offer.useMarketBasedPrice) return false; if (amount != offer.amount) return false; if (minAmount != offer.minAmount) return false; if (id != null ? !id.equals(offer.id) : offer.id != null) return false; @@ -488,7 +493,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload result = 31 * result + (int) (fiatPrice ^ (fiatPrice >>> 32)); long temp = Double.doubleToLongBits(marketPriceMargin); result = 31 * result + (int) (temp ^ (temp >>> 32)); - result = 31 * result + (usePercentageBasedPrice ? 1 : 0); + result = 31 * result + (useMarketBasedPrice ? 1 : 0); result = 31 * result + (int) (amount ^ (amount >>> 32)); result = 31 * result + (int) (minAmount ^ (minAmount >>> 32)); result = 31 * result + (offererNodeAddress != null ? offererNodeAddress.hashCode() : 0); @@ -513,7 +518,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload "\n\tdate=" + date + "\n\tfiatPrice=" + fiatPrice + "\n\tmarketPriceMargin=" + marketPriceMargin + - "\n\tusePercentageBasedPrice=" + usePercentageBasedPrice + + "\n\tuseMarketBasedPrice=" + useMarketBasedPrice + "\n\tamount=" + amount + "\n\tminAmount=" + minAmount + "\n\toffererAddress=" + offererNodeAddress + 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 e56e1f68ae..97cb8f8e44 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 @@ -89,7 +89,7 @@ class CreateOfferDataModel extends ActivatableDataModel { final StringProperty btcCode = new SimpleStringProperty(); final BooleanProperty isWalletFunded = new SimpleBooleanProperty(); - final BooleanProperty usePercentageBasedPrice = new SimpleBooleanProperty(); + final BooleanProperty useMarketBasedPrice = new SimpleBooleanProperty(); //final BooleanProperty isMainNet = new SimpleBooleanProperty(); //final BooleanProperty isFeeFromFundingTxSufficient = new SimpleBooleanProperty(); @@ -109,7 +109,7 @@ class CreateOfferDataModel extends ActivatableDataModel { private Notification walletFundedNotification; boolean useSavingsWallet; Coin totalAvailableBalance; - private double percentageBasedPrice = 0; + private double marketPriceMargin = 0; /////////////////////////////////////////////////////////////////////////////////////////// @@ -140,7 +140,7 @@ class CreateOfferDataModel extends ActivatableDataModel { networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades(); securityDepositAsCoin = FeePolicy.getSecurityDeposit(); - usePercentageBasedPrice.set(preferences.getUsePercentageBasedPrice()); + useMarketBasedPrice.set(preferences.getUsePercentageBasedPrice()); balanceListener = new BalanceListener(getAddressEntry().getAddress()) { @Override @@ -256,8 +256,8 @@ class CreateOfferDataModel extends ActivatableDataModel { /////////////////////////////////////////////////////////////////////////////////////////// Offer createAndGetOffer() { - long fiatPrice = priceAsFiat.get() != null ? priceAsFiat.get().getValue() : 0L; - + long fiatPrice = priceAsFiat.get() != null && !useMarketBasedPrice.get() ? priceAsFiat.get().getValue() : 0L; + double marketPriceMarginParam = useMarketBasedPrice.get() ? marketPriceMargin : 0; long amount = amountAsCoin.get() != null ? amountAsCoin.get().getValue() : 0L; long minAmount = minAmountAsCoin.get() != null ? minAmountAsCoin.get().getValue() : 0L; @@ -289,8 +289,8 @@ class CreateOfferDataModel extends ActivatableDataModel { keyRing.getPubKeyRing(), direction, fiatPrice, - percentageBasedPrice, - usePercentageBasedPrice.get(), + marketPriceMarginParam, + useMarketBasedPrice.get(), amount, minAmount, tradeCurrencyCode.get(), @@ -386,9 +386,9 @@ class CreateOfferDataModel extends ActivatableDataModel { return user.getAcceptedArbitrators().size() > 0; } - public void setUsePercentageBasedPrice(boolean usePercentageBasedPrice) { - this.usePercentageBasedPrice.set(usePercentageBasedPrice); - preferences.setUsePercentageBasedPrice(usePercentageBasedPrice); + public void setUseMarketBasedPrice(boolean useMarketBasedPrice) { + this.useMarketBasedPrice.set(useMarketBasedPrice); + preferences.setUsePercentageBasedPrice(useMarketBasedPrice); } /*boolean isFeeFromFundingTxSufficient() { @@ -498,11 +498,11 @@ class CreateOfferDataModel extends ActivatableDataModel { walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE); } - double getPercentageBasedPrice() { - return percentageBasedPrice; + double getMarketPriceMargin() { + return marketPriceMargin; } - void setPercentageBasedPrice(double percentageBasedPrice) { - this.percentageBasedPrice = percentageBasedPrice; + void setMarketPriceMargin(double marketPriceMargin) { + this.marketPriceMargin = marketPriceMargin; } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java index 26ce8eb611..3d71c7f602 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java @@ -91,17 +91,17 @@ public class CreateOfferView extends ActivatableViewAndModel paymentAccountsComboBox; private ComboBox currencyComboBox; private PopOver totalToPayInfoPopover; - private ToggleButton fixedPriceButton, percentagePriceButton; + private ToggleButton fixedPriceButton, useMarketBasedPriceButton; private OfferView.CloseHandler closeHandler; @@ -180,8 +180,8 @@ public class CreateOfferView extends ActivatableViewAndModel model.tradeCurrencyCode.get() + "/" + model.btcCode.get(), model.btcCode, model.tradeCurrencyCode)); - priceTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice); - priceCurrencyLabel.disableProperty().bind(model.dataModel.usePercentageBasedPrice); - priceAsPercentageTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not()); - priceAsPercentageLabel.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not()); - priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty()); + fixedPriceTextField.disableProperty().bind(model.dataModel.useMarketBasedPrice); + priceCurrencyLabel.disableProperty().bind(model.dataModel.useMarketBasedPrice); + marketBasedPriceTextField.disableProperty().bind(model.dataModel.useMarketBasedPrice.not()); + marketBasedPriceLabel.disableProperty().bind(model.dataModel.useMarketBasedPrice.not()); + marketBasedPriceLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty()); volumeCurrencyLabel.textProperty().bind(model.tradeCurrencyCode); minAmountBtcLabel.textProperty().bind(model.btcCode); priceDescriptionLabel.textProperty().bind(createStringBinding(() -> BSResources.get("createOffer.amountPriceBox.priceDescription", model.tradeCurrencyCode.get()), model.tradeCurrencyCode)); volumeDescriptionLabel.textProperty().bind(createStringBinding(model.volumeDescriptionLabel::get, model.tradeCurrencyCode, model.volumeDescriptionLabel)); amountTextField.textProperty().bindBidirectional(model.amount); minAmountTextField.textProperty().bindBidirectional(model.minAmount); - priceTextField.textProperty().bindBidirectional(model.price); - priceAsPercentageTextField.textProperty().bindBidirectional(model.priceAsPercentage); + fixedPriceTextField.textProperty().bindBidirectional(model.price); + marketBasedPriceTextField.textProperty().bindBidirectional(model.priceAsPercentage); volumeTextField.textProperty().bindBidirectional(model.volume); volumeTextField.promptTextProperty().bind(model.volumePromptLabel); totalToPayTextField.textProperty().bind(model.totalToPay); @@ -434,7 +434,7 @@ public class CreateOfferView extends ActivatableViewAndModel { - model.onFocusOutPriceTextField(oldValue, newValue, priceTextField.getText()); - priceTextField.setText(model.price.get()); + model.onFocusOutPriceTextField(oldValue, newValue, fixedPriceTextField.getText()); + fixedPriceTextField.setText(model.price.get()); }; priceAsPercentageFocusedListener = (o, oldValue, newValue) -> { - model.onFocusOutPriceAsPercentageTextField(oldValue, newValue, priceAsPercentageTextField.getText()); - priceAsPercentageTextField.setText(model.priceAsPercentage.get()); + model.onFocusOutPriceAsPercentageTextField(oldValue, newValue, marketBasedPriceTextField.getText()); + marketBasedPriceTextField.setText(model.priceAsPercentage.get()); }; volumeFocusedListener = (o, oldValue, newValue) -> { model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText()); @@ -604,8 +604,8 @@ public class CreateOfferView extends ActivatableViewAndModel onCurrencyComboBoxSelected(); tradeCurrencyCodeListener = (observable, oldValue, newValue) -> { - priceTextField.clear(); - priceAsPercentageTextField.clear(); + fixedPriceTextField.clear(); + marketBasedPriceTextField.clear(); volumeTextField.clear(); }; @@ -643,8 +643,8 @@ public class CreateOfferView extends ActivatableViewAndModel priceValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.price.prompt")); HBox priceValueCurrencyBox = priceValueCurrencyBoxTuple.first; - priceTextField = priceValueCurrencyBoxTuple.second; - editOfferElements.add(priceTextField); + fixedPriceTextField = priceValueCurrencyBoxTuple.second; + editOfferElements.add(fixedPriceTextField); priceCurrencyLabel = priceValueCurrencyBoxTuple.third; editOfferElements.add(priceCurrencyLabel); Tuple2 priceInputBoxTuple = getTradeInputBox(priceValueCurrencyBox, BSResources.get("createOffer.amountPriceBox.priceDescription")); @@ -950,22 +950,22 @@ public class CreateOfferView extends ActivatableViewAndModel { - model.dataModel.setUsePercentageBasedPrice(!newValue); - percentagePriceButton.setSelected(!newValue); + model.dataModel.setUseMarketBasedPrice(!newValue); + useMarketBasedPriceButton.setSelected(!newValue); }); - percentagePriceButton = new ToggleButton("Percentage"); - editOfferElements.add(percentagePriceButton); - percentagePriceButton.setId("toggle-price-right"); - percentagePriceButton.setToggleGroup(toggleGroup); - percentagePriceButton.selectedProperty().addListener((ov, oldValue, newValue) -> { - model.dataModel.setUsePercentageBasedPrice(newValue); + useMarketBasedPriceButton = new ToggleButton("Percentage"); + editOfferElements.add(useMarketBasedPriceButton); + useMarketBasedPriceButton.setId("toggle-price-right"); + useMarketBasedPriceButton.setToggleGroup(toggleGroup); + useMarketBasedPriceButton.selectedProperty().addListener((ov, oldValue, newValue) -> { + model.dataModel.setUseMarketBasedPrice(newValue); fixedPriceButton.setSelected(!newValue); }); HBox toggleButtons = new HBox(); toggleButtons.setPadding(new Insets(18, 0, 0, 0)); - toggleButtons.getChildren().addAll(fixedPriceButton, percentagePriceButton); + toggleButtons.getChildren().addAll(fixedPriceButton, useMarketBasedPriceButton); // = Label resultLabel = new Label("="); @@ -998,18 +998,18 @@ public class CreateOfferView extends ActivatableViewAndModel priceAsPercentageTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.price.prompt")); HBox priceAsPercentageValueCurrencyBox = priceAsPercentageTuple.first; - priceAsPercentageTextField = priceAsPercentageTuple.second; - editOfferElements.add(priceAsPercentageTextField); - priceAsPercentageLabel = priceAsPercentageTuple.third; - editOfferElements.add(priceAsPercentageLabel); + marketBasedPriceTextField = priceAsPercentageTuple.second; + editOfferElements.add(marketBasedPriceTextField); + marketBasedPriceLabel = priceAsPercentageTuple.third; + editOfferElements.add(marketBasedPriceLabel); Tuple2 priceAsPercentageInputBoxTuple = getTradeInputBox(priceAsPercentageValueCurrencyBox, "Distance in % from market price"); priceAsPercentageInputBoxTuple.first.setPrefWidth(200); VBox priceAsPercentageInputBox = priceAsPercentageInputBoxTuple.second; - priceAsPercentageTextField.setPromptText("Enter % value"); - priceAsPercentageLabel.setText("%"); - priceAsPercentageLabel.setStyle("-fx-alignment: center;"); + marketBasedPriceTextField.setPromptText("Enter % value"); + marketBasedPriceLabel.setText("%"); + marketBasedPriceLabel.setStyle("-fx-alignment: center;"); Tuple3 amountValueCurrencyBoxTuple = getValueCurrencyBox(BSResources.get("createOffer.amount.prompt")); HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first; 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 1d3ea2dce2..85a2f13e21 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 @@ -104,7 +104,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel amountListener; private ChangeListener minAmountListener; - private ChangeListener priceListener, priceAsPercentageListener; + private ChangeListener priceListener, marketPriceMarginListener; private ChangeListener volumeListener; private ChangeListener amountAsCoinListener; private ChangeListener minAmountAsCoinListener; @@ -116,8 +116,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel usePercentageBasedPriceListener; + private boolean inputIsMarketBasedPrice; + private ChangeListener useMarketBasedPriceListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -240,7 +240,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel { - if (priceAsPercentageIsInput) { + marketPriceMarginListener = (ov, oldValue, newValue) -> { + if (inputIsMarketBasedPrice) { try { if (!newValue.isEmpty() && !newValue.equals("-")) { double marketPriceMargin = formatter.parsePercentStringToDouble(newValue); if (marketPriceMargin >= 1 || marketPriceMargin <= -1) { - dataModel.setPercentageBasedPrice(0); + dataModel.setMarketPriceMargin(0); UserThread.execute(() -> priceAsPercentage.set("0")); new Popup().warning("You cannot set a percentage of 100% or larger. Please enter a percentage number like \"5.4\" for 5.4%") .show(); @@ -276,7 +276,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel priceAsPercentage.set("0")); new Popup().warning("Your input is not a valid number. Please enter a percentage number like \"5.4\" for 5.4%") .show(); } } }; - usePercentageBasedPriceListener = (observable, oldValue, newValue) -> { + useMarketBasedPriceListener = (observable, oldValue, newValue) -> { if (newValue) priceValidationResult.set(new InputValidator.ValidationResult(true)); }; @@ -335,8 +335,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel