mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-06-22 13:54:19 -04:00
Add check for deviation from market price at create offer
This commit is contained in:
parent
007a71cc29
commit
32e1a5a3ac
6 changed files with 103 additions and 5 deletions
|
@ -60,7 +60,7 @@ public class Log {
|
||||||
appender.start();
|
appender.start();
|
||||||
|
|
||||||
logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
||||||
logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.INFO);
|
logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.WARN);
|
||||||
logbackLogger.addAppender(appender);
|
logbackLogger.addAppender(appender);
|
||||||
|
|
||||||
// log errors in separate file
|
// log errors in separate file
|
||||||
|
|
|
@ -87,6 +87,13 @@ public class PriceFeed {
|
||||||
requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice);
|
requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public MarketPrice getMarketPrice(String currencyCode) {
|
||||||
|
if (cache.containsKey(currencyCode))
|
||||||
|
return cache.get(currencyCode);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Setter
|
// Setter
|
||||||
|
|
|
@ -106,6 +106,7 @@ public final class Preferences implements Persistable {
|
||||||
private Locale preferredLocale;
|
private Locale preferredLocale;
|
||||||
private TradeCurrency preferredTradeCurrency;
|
private TradeCurrency preferredTradeCurrency;
|
||||||
private long txFeePerKB = FeePolicy.getFeePerKb().value;
|
private long txFeePerKB = FeePolicy.getFeePerKb().value;
|
||||||
|
private double maxPriceDistanceInPercent;
|
||||||
|
|
||||||
// Observable wrappers
|
// Observable wrappers
|
||||||
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
|
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
|
||||||
|
@ -155,6 +156,10 @@ public final class Preferences implements Persistable {
|
||||||
defaultTradeCurrency = preferredTradeCurrency;
|
defaultTradeCurrency = preferredTradeCurrency;
|
||||||
useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
|
useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
|
||||||
showOwnOffersInOfferBook = persisted.getShowOwnOffersInOfferBook();
|
showOwnOffersInOfferBook = persisted.getShowOwnOffersInOfferBook();
|
||||||
|
maxPriceDistanceInPercent = persisted.getMaxPriceDistanceInPercent();
|
||||||
|
// Backward compatible to version 0.3.6. Can be removed after a while
|
||||||
|
if (maxPriceDistanceInPercent == 0d)
|
||||||
|
maxPriceDistanceInPercent = 0.2;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setTxFeePerKB(persisted.getTxFeePerKB());
|
setTxFeePerKB(persisted.getTxFeePerKB());
|
||||||
|
@ -174,6 +179,7 @@ public final class Preferences implements Persistable {
|
||||||
dontShowAgainMap = new HashMap<>();
|
dontShowAgainMap = new HashMap<>();
|
||||||
preferredLocale = getDefaultLocale();
|
preferredLocale = getDefaultLocale();
|
||||||
preferredTradeCurrency = getDefaultTradeCurrency();
|
preferredTradeCurrency = getDefaultTradeCurrency();
|
||||||
|
maxPriceDistanceInPercent = 0.2;
|
||||||
|
|
||||||
storage.queueUpForSave();
|
storage.queueUpForSave();
|
||||||
}
|
}
|
||||||
|
@ -459,4 +465,12 @@ public final class Preferences implements Persistable {
|
||||||
public void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook) {
|
public void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook) {
|
||||||
this.showOwnOffersInOfferBook = showOwnOffersInOfferBook;
|
this.showOwnOffersInOfferBook = showOwnOffersInOfferBook;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getMaxPriceDistanceInPercent() {
|
||||||
|
return maxPriceDistanceInPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxPriceDistanceInPercent(double maxPriceDistanceInPercent) {
|
||||||
|
this.maxPriceDistanceInPercent = maxPriceDistanceInPercent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -702,7 +702,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
cancelButton1.setId("cancel-button");
|
cancelButton1.setId("cancel-button");
|
||||||
|
|
||||||
GridPane.setMargin(nextButton, new Insets(-35, 0, 0, 0));
|
GridPane.setMargin(nextButton, new Insets(-35, 0, 0, 0));
|
||||||
nextButton.setOnAction(e -> onShowPayFundsScreen());
|
nextButton.setOnAction(e -> {
|
||||||
|
if (model.isPriceInRange())
|
||||||
|
onShowPayFundsScreen();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFundingGroup() {
|
private void addFundingGroup() {
|
||||||
|
|
|
@ -19,10 +19,17 @@ package io.bitsquare.gui.main.offer.createoffer;
|
||||||
|
|
||||||
import io.bitsquare.app.BitsquareApp;
|
import io.bitsquare.app.BitsquareApp;
|
||||||
import io.bitsquare.btc.FeePolicy;
|
import io.bitsquare.btc.FeePolicy;
|
||||||
|
import io.bitsquare.btc.pricefeed.MarketPrice;
|
||||||
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.common.Timer;
|
import io.bitsquare.common.Timer;
|
||||||
import io.bitsquare.common.UserThread;
|
import io.bitsquare.common.UserThread;
|
||||||
|
import io.bitsquare.gui.Navigation;
|
||||||
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
||||||
import io.bitsquare.gui.common.model.ViewModel;
|
import io.bitsquare.gui.common.model.ViewModel;
|
||||||
|
import io.bitsquare.gui.main.MainView;
|
||||||
|
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||||
|
import io.bitsquare.gui.main.settings.SettingsView;
|
||||||
|
import io.bitsquare.gui.main.settings.preferences.PreferencesView;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
import io.bitsquare.gui.util.validation.BtcValidator;
|
import io.bitsquare.gui.util.validation.BtcValidator;
|
||||||
import io.bitsquare.gui.util.validation.FiatValidator;
|
import io.bitsquare.gui.util.validation.FiatValidator;
|
||||||
|
@ -32,6 +39,7 @@ import io.bitsquare.locale.TradeCurrency;
|
||||||
import io.bitsquare.p2p.P2PService;
|
import io.bitsquare.p2p.P2PService;
|
||||||
import io.bitsquare.payment.PaymentAccount;
|
import io.bitsquare.payment.PaymentAccount;
|
||||||
import io.bitsquare.trade.offer.Offer;
|
import io.bitsquare.trade.offer.Offer;
|
||||||
|
import io.bitsquare.user.Preferences;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
@ -41,11 +49,15 @@ import org.bitcoinj.utils.Fiat;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static com.google.common.math.LongMath.checkedPow;
|
||||||
import static javafx.beans.binding.Bindings.createStringBinding;
|
import static javafx.beans.binding.Bindings.createStringBinding;
|
||||||
|
|
||||||
class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel> implements ViewModel {
|
class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel> implements ViewModel {
|
||||||
private final BtcValidator btcValidator;
|
private final BtcValidator btcValidator;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
private PriceFeed priceFeed;
|
||||||
|
private Preferences preferences;
|
||||||
|
private Navigation navigation;
|
||||||
final BSFormatter formatter;
|
final BSFormatter formatter;
|
||||||
private final FiatValidator fiatValidator;
|
private final FiatValidator fiatValidator;
|
||||||
|
|
||||||
|
@ -109,13 +121,16 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CreateOfferViewModel(CreateOfferDataModel dataModel, FiatValidator fiatValidator, BtcValidator btcValidator,
|
public CreateOfferViewModel(CreateOfferDataModel dataModel, FiatValidator fiatValidator, BtcValidator btcValidator,
|
||||||
P2PService p2PService,
|
P2PService p2PService, PriceFeed priceFeed, Preferences preferences, Navigation navigation,
|
||||||
BSFormatter formatter) {
|
BSFormatter formatter) {
|
||||||
super(dataModel);
|
super(dataModel);
|
||||||
|
|
||||||
this.fiatValidator = fiatValidator;
|
this.fiatValidator = fiatValidator;
|
||||||
this.btcValidator = btcValidator;
|
this.btcValidator = btcValidator;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.priceFeed = priceFeed;
|
||||||
|
this.preferences = preferences;
|
||||||
|
this.navigation = navigation;
|
||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
|
|
||||||
paymentLabel = BSResources.get("createOffer.fundsBox.paymentLabel", dataModel.getOfferId());
|
paymentLabel = BSResources.get("createOffer.fundsBox.paymentLabel", dataModel.getOfferId());
|
||||||
|
@ -378,6 +393,33 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
dataModel.onCurrencySelected(tradeCurrency);
|
dataModel.onCurrencySelected(tradeCurrency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPriceInRange() {
|
||||||
|
MarketPrice marketPrice = priceFeed.getMarketPrice(getTradeCurrency().getCode());
|
||||||
|
if (marketPrice != null) {
|
||||||
|
double marketPriceAsDouble = marketPrice.getPrice(PriceFeed.Type.LAST);
|
||||||
|
Fiat priceAsFiat = dataModel.priceAsFiat.get();
|
||||||
|
long shiftDivisor = checkedPow(10, priceAsFiat.smallestUnitExponent());
|
||||||
|
double offerPrice = ((double) priceAsFiat.longValue()) / ((double) shiftDivisor);
|
||||||
|
if (marketPriceAsDouble != 0 && Math.abs(1 - (offerPrice / marketPriceAsDouble)) > preferences.getMaxPriceDistanceInPercent()) {
|
||||||
|
Popup popup = new Popup();
|
||||||
|
popup.warning("The price you have entered is outside the max. allowed deviation from the market price.\n" +
|
||||||
|
"The max. allowed deviation is " +
|
||||||
|
formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent()) +
|
||||||
|
" and can be adjusted in the preferences.")
|
||||||
|
.actionButtonText("Change price")
|
||||||
|
.onAction(() -> popup.hide())
|
||||||
|
.closeButtonText("Go to \"Preferences\"")
|
||||||
|
.onClose(() -> navigation.navigateTo(MainView.class, SettingsView.class, PreferencesView.class))
|
||||||
|
.show();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onShowPayFundsScreen() {
|
public void onShowPayFundsScreen() {
|
||||||
showPayFundsScreenDisplayed = true;
|
showPayFundsScreenDisplayed = true;
|
||||||
updateSpinnerInfo();
|
updateSpinnerInfo();
|
||||||
|
|
|
@ -22,8 +22,10 @@ import io.bitsquare.common.util.Tuple2;
|
||||||
import io.bitsquare.gui.common.model.Activatable;
|
import io.bitsquare.gui.common.model.Activatable;
|
||||||
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
|
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
|
||||||
import io.bitsquare.gui.common.view.FxmlView;
|
import io.bitsquare.gui.common.view.FxmlView;
|
||||||
|
import io.bitsquare.gui.components.InputTextField;
|
||||||
import io.bitsquare.gui.components.TitledGroupBg;
|
import io.bitsquare.gui.components.TitledGroupBg;
|
||||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||||
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
import io.bitsquare.gui.util.ImageUtil;
|
import io.bitsquare.gui.util.ImageUtil;
|
||||||
import io.bitsquare.gui.util.Layout;
|
import io.bitsquare.gui.util.Layout;
|
||||||
import io.bitsquare.locale.*;
|
import io.bitsquare.locale.*;
|
||||||
|
@ -44,6 +46,7 @@ import javafx.util.Callback;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static io.bitsquare.gui.util.FormBuilder.*;
|
import static io.bitsquare.gui.util.FormBuilder.*;
|
||||||
|
|
||||||
|
@ -61,6 +64,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
//private InputTextField transactionFeeInputTextField;
|
//private InputTextField transactionFeeInputTextField;
|
||||||
private ChangeListener<Boolean> transactionFeeFocusedListener;
|
private ChangeListener<Boolean> transactionFeeFocusedListener;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
|
private BSFormatter formatter;
|
||||||
|
|
||||||
private ListView<FiatCurrency> fiatCurrenciesListView;
|
private ListView<FiatCurrency> fiatCurrenciesListView;
|
||||||
private ComboBox<FiatCurrency> fiatCurrenciesComboBox;
|
private ComboBox<FiatCurrency> fiatCurrenciesComboBox;
|
||||||
|
@ -77,6 +81,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
public final ObservableList<CryptoCurrency> cryptoCurrencies;
|
public final ObservableList<CryptoCurrency> cryptoCurrencies;
|
||||||
public final ObservableList<CryptoCurrency> allCryptoCurrencies;
|
public final ObservableList<CryptoCurrency> allCryptoCurrencies;
|
||||||
public final ObservableList<TradeCurrency> tradeCurrencies;
|
public final ObservableList<TradeCurrency> tradeCurrencies;
|
||||||
|
private InputTextField deviationInputTextField;
|
||||||
|
private ChangeListener<String> deviationListener;
|
||||||
|
private ChangeListener<Boolean> deviationFocusedListener;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -84,9 +91,10 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PreferencesView(Preferences preferences) {
|
public PreferencesView(Preferences preferences, BSFormatter formatter) {
|
||||||
super();
|
super();
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
|
this.formatter = formatter;
|
||||||
|
|
||||||
blockExplorers = FXCollections.observableArrayList(preferences.getBlockChainExplorers());
|
blockExplorers = FXCollections.observableArrayList(preferences.getBlockChainExplorers());
|
||||||
languageCodes = FXCollections.observableArrayList(LanguageUtil.getAllLanguageCodes());
|
languageCodes = FXCollections.observableArrayList(LanguageUtil.getAllLanguageCodes());
|
||||||
|
@ -280,13 +288,31 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeOtherOptions() {
|
private void initializeOtherOptions() {
|
||||||
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 2, "General preferences", Layout.GROUP_DISTANCE);
|
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 3, "General preferences", Layout.GROUP_DISTANCE);
|
||||||
GridPane.setColumnSpan(titledGroupBg, 4);
|
GridPane.setColumnSpan(titledGroupBg, 4);
|
||||||
// userLanguageComboBox = addLabelComboBox(root, gridRow, "Language:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
// userLanguageComboBox = addLabelComboBox(root, gridRow, "Language:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
||||||
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second;
|
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second;
|
||||||
blockChainExplorerComboBox = addLabelComboBox(root, gridRow, "Bitcoin block explorer:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
blockChainExplorerComboBox = addLabelComboBox(root, gridRow, "Bitcoin block explorer:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
||||||
|
deviationInputTextField = addLabelInputTextField(root, ++gridRow, "Max. deviation from market price:").second;
|
||||||
autoSelectArbitratorsCheckBox = addLabelCheckBox(root, ++gridRow, "Auto select arbitrators:", "").second;
|
autoSelectArbitratorsCheckBox = addLabelCheckBox(root, ++gridRow, "Auto select arbitrators:", "").second;
|
||||||
|
|
||||||
|
deviationListener = (observable, oldValue, newValue) -> {
|
||||||
|
try {
|
||||||
|
String input = newValue.replace("%", "");
|
||||||
|
input = input.replace(",", ".");
|
||||||
|
input = input.replace(" ", "");
|
||||||
|
double value = Double.parseDouble(input);
|
||||||
|
preferences.setMaxPriceDistanceInPercent(value / 100);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.error("Exception at parseDouble deviation: " + t.toString());
|
||||||
|
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
deviationFocusedListener = (observable1, oldValue1, newValue1) -> {
|
||||||
|
if (oldValue1 && !newValue1)
|
||||||
|
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
|
||||||
|
};
|
||||||
|
|
||||||
// TODO need a bit extra work to separate trade and non trade tx fees before it can be used
|
// TODO need a bit extra work to separate trade and non trade tx fees before it can be used
|
||||||
/*transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
|
/*transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
|
||||||
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
|
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
|
||||||
|
@ -393,6 +419,10 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
});
|
});
|
||||||
blockChainExplorerComboBox.setOnAction(e -> preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem()));
|
blockChainExplorerComboBox.setOnAction(e -> preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem()));
|
||||||
|
|
||||||
|
deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent()));
|
||||||
|
deviationInputTextField.textProperty().addListener(deviationListener);
|
||||||
|
deviationInputTextField.focusedProperty().addListener(deviationFocusedListener);
|
||||||
|
|
||||||
// transactionFeeInputTextField.textProperty().bindBidirectional(transactionFeePerByte);
|
// transactionFeeInputTextField.textProperty().bindBidirectional(transactionFeePerByte);
|
||||||
// transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener);
|
// transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener);
|
||||||
}
|
}
|
||||||
|
@ -422,6 +452,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
//btcDenominationComboBox.setOnAction(null);
|
//btcDenominationComboBox.setOnAction(null);
|
||||||
// userLanguageComboBox.setOnAction(null);
|
// userLanguageComboBox.setOnAction(null);
|
||||||
blockChainExplorerComboBox.setOnAction(null);
|
blockChainExplorerComboBox.setOnAction(null);
|
||||||
|
deviationInputTextField.textProperty().removeListener(deviationListener);
|
||||||
|
deviationInputTextField.focusedProperty().removeListener(deviationFocusedListener);
|
||||||
// transactionFeeInputTextField.textProperty().unbind();
|
// transactionFeeInputTextField.textProperty().unbind();
|
||||||
/// transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
|
/// transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue