Add check for deviation from market price at create offer

This commit is contained in:
Manfred Karrer 2016-03-22 19:59:37 +01:00
parent 007a71cc29
commit 32e1a5a3ac
6 changed files with 103 additions and 5 deletions

View File

@ -60,7 +60,7 @@ public class Log {
appender.start();
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);
// log errors in separate file

View File

@ -87,6 +87,13 @@ public class PriceFeed {
requestAllPrices(cryptoCurrenciesPriceProvider, this::applyPrice);
}
@Nullable
public MarketPrice getMarketPrice(String currencyCode) {
if (cache.containsKey(currencyCode))
return cache.get(currencyCode);
else
return null;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setter

View File

@ -106,6 +106,7 @@ public final class Preferences implements Persistable {
private Locale preferredLocale;
private TradeCurrency preferredTradeCurrency;
private long txFeePerKB = FeePolicy.getFeePerKb().value;
private double maxPriceDistanceInPercent;
// Observable wrappers
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
@ -155,6 +156,10 @@ public final class Preferences implements Persistable {
defaultTradeCurrency = preferredTradeCurrency;
useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
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 {
setTxFeePerKB(persisted.getTxFeePerKB());
@ -174,6 +179,7 @@ public final class Preferences implements Persistable {
dontShowAgainMap = new HashMap<>();
preferredLocale = getDefaultLocale();
preferredTradeCurrency = getDefaultTradeCurrency();
maxPriceDistanceInPercent = 0.2;
storage.queueUpForSave();
}
@ -459,4 +465,12 @@ public final class Preferences implements Persistable {
public void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook) {
this.showOwnOffersInOfferBook = showOwnOffersInOfferBook;
}
public double getMaxPriceDistanceInPercent() {
return maxPriceDistanceInPercent;
}
public void setMaxPriceDistanceInPercent(double maxPriceDistanceInPercent) {
this.maxPriceDistanceInPercent = maxPriceDistanceInPercent;
}
}

View File

@ -702,7 +702,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
cancelButton1.setId("cancel-button");
GridPane.setMargin(nextButton, new Insets(-35, 0, 0, 0));
nextButton.setOnAction(e -> onShowPayFundsScreen());
nextButton.setOnAction(e -> {
if (model.isPriceInRange())
onShowPayFundsScreen();
});
}
private void addFundingGroup() {

View File

@ -19,10 +19,17 @@ package io.bitsquare.gui.main.offer.createoffer;
import io.bitsquare.app.BitsquareApp;
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.UserThread;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
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.validation.BtcValidator;
import io.bitsquare.gui.util.validation.FiatValidator;
@ -32,6 +39,7 @@ import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
@ -41,11 +49,15 @@ import org.bitcoinj.utils.Fiat;
import javax.inject.Inject;
import static com.google.common.math.LongMath.checkedPow;
import static javafx.beans.binding.Bindings.createStringBinding;
class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel> implements ViewModel {
private final BtcValidator btcValidator;
private final P2PService p2PService;
private PriceFeed priceFeed;
private Preferences preferences;
private Navigation navigation;
final BSFormatter formatter;
private final FiatValidator fiatValidator;
@ -109,13 +121,16 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
@Inject
public CreateOfferViewModel(CreateOfferDataModel dataModel, FiatValidator fiatValidator, BtcValidator btcValidator,
P2PService p2PService,
P2PService p2PService, PriceFeed priceFeed, Preferences preferences, Navigation navigation,
BSFormatter formatter) {
super(dataModel);
this.fiatValidator = fiatValidator;
this.btcValidator = btcValidator;
this.p2PService = p2PService;
this.priceFeed = priceFeed;
this.preferences = preferences;
this.navigation = navigation;
this.formatter = formatter;
paymentLabel = BSResources.get("createOffer.fundsBox.paymentLabel", dataModel.getOfferId());
@ -378,6 +393,33 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
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() {
showPayFundsScreenDisplayed = true;
updateSpinnerInfo();

View File

@ -22,8 +22,10 @@ import io.bitsquare.common.util.Tuple2;
import io.bitsquare.gui.common.model.Activatable;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.components.TitledGroupBg;
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.Layout;
import io.bitsquare.locale.*;
@ -44,6 +46,7 @@ import javafx.util.Callback;
import javafx.util.StringConverter;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.gui.util.FormBuilder.*;
@ -61,6 +64,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
//private InputTextField transactionFeeInputTextField;
private ChangeListener<Boolean> transactionFeeFocusedListener;
private final Preferences preferences;
private BSFormatter formatter;
private ListView<FiatCurrency> fiatCurrenciesListView;
private ComboBox<FiatCurrency> fiatCurrenciesComboBox;
@ -77,6 +81,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
public final ObservableList<CryptoCurrency> cryptoCurrencies;
public final ObservableList<CryptoCurrency> allCryptoCurrencies;
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
public PreferencesView(Preferences preferences) {
public PreferencesView(Preferences preferences, BSFormatter formatter) {
super();
this.preferences = preferences;
this.formatter = formatter;
blockExplorers = FXCollections.observableArrayList(preferences.getBlockChainExplorers());
languageCodes = FXCollections.observableArrayList(LanguageUtil.getAllLanguageCodes());
@ -280,13 +288,31 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
}
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);
// userLanguageComboBox = addLabelComboBox(root, gridRow, "Language:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").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;
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
/*transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
@ -393,6 +419,10 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
});
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.focusedProperty().addListener(transactionFeeFocusedListener);
}
@ -422,6 +452,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
//btcDenominationComboBox.setOnAction(null);
// userLanguageComboBox.setOnAction(null);
blockChainExplorerComboBox.setOnAction(null);
deviationInputTextField.textProperty().removeListener(deviationListener);
deviationInputTextField.focusedProperty().removeListener(deviationFocusedListener);
// transactionFeeInputTextField.textProperty().unbind();
/// transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
}