From 3121f6f2fe9c64cf2a0e7605bac735edc4ea7607 Mon Sep 17 00:00:00 2001 From: sqrrm Date: Sun, 28 Jun 2020 19:19:09 +0200 Subject: [PATCH 01/11] Add deposit column to OfferBookView --- .../resources/i18n/displayStrings.properties | 2 + .../main/offer/offerbook/OfferBookView.java | 51 ++++++++++++++++++- .../offer/offerbook/OfferBookViewModel.java | 11 ++-- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 7767f3644d..0860b75039 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -357,6 +357,8 @@ shared.notSigned.noNeed=This account type doesn't use signing offerbook.nrOffers=No. of offers: {0} offerbook.volume={0} (min - max) +offerbook.deposit=Deposit +offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed. offerbook.createOfferToBuy=Create new offer to buy {0} offerbook.createOfferToSell=Create new offer to sell {0} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index f91c593dd0..af810592cc 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -38,7 +38,6 @@ import bisq.desktop.main.offer.OfferView; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.util.CssTheme; -import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.FormBuilder; import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; @@ -130,7 +129,7 @@ public class OfferBookView extends ActivatableViewAndModel paymentMethodComboBox; private AutoTooltipButton createOfferButton; private AutoTooltipTableColumn amountColumn, volumeColumn, marketColumn, - priceColumn, paymentMethodColumn, signingStateColumn, avatarColumn; + priceColumn, paymentMethodColumn, depositColumn, signingStateColumn, avatarColumn; private TableView tableView; private OfferView.OfferActionHandler offerActionHandler; @@ -224,6 +223,8 @@ public class OfferBookView extends ActivatableViewAndModel getDepositColumn() { + AutoTooltipTableColumn column = new AutoTooltipTableColumn<>( + Res.get("offerbook.deposit"), + Res.get("offerbook.deposit.help")) { + { + setMinWidth(70); + setSortable(true); + } + }; + + column.getStyleClass().add("number-column"); + column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + column.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(final OfferBookListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + var isSellOffer = item.getOffer().getDirection() == OfferPayload.Direction.SELL; + var deposit = isSellOffer ? item.getOffer().getBuyerSecurityDeposit() : + item.getOffer().getSellerSecurityDeposit(); + if (deposit == null) { + setText(Res.get("shared.na")); + setGraphic(null); + } else { + setText(""); + setGraphic(new ColoredDecimalPlacesWithZerosText(model.formatDepositString( + deposit, item.getOffer().getAmount().getValue()), + GUIUtil.AMOUNT_DECIMALS_WITH_ZEROS)); + } + } else { + setText(""); + setGraphic(null); + } + } + }; + } + }); + return column; + } + private TableColumn getActionColumn() { TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.actions")) { { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 77203fb0dc..0e42d0ed62 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -47,8 +47,8 @@ import bisq.core.trade.Trade; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.user.Preferences; import bisq.core.user.User; -import bisq.core.util.coin.BsqFormatter; import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; import bisq.network.p2p.NodeAddress; @@ -168,9 +168,7 @@ class OfferBookViewModel extends ActivatableViewModel { this.filteredItems = new FilteredList<>(offerBook.getOfferBookListItems()); this.sortedItems = new SortedList<>(filteredItems); - tradeCurrencyListChangeListener = c -> { - fillAllTradeCurrencies(); - }; + tradeCurrencyListChangeListener = c -> fillAllTradeCurrencies(); filterItemsListener = c -> { final Optional highestAmountOffer = filteredItems.stream() @@ -645,4 +643,9 @@ class OfferBookViewModel extends ActivatableViewModel { else return (direction == OfferPayload.Direction.SELL) ? Res.get("shared.buyingCurrency", currencyCode) : Res.get("shared.sellingCurrency", currencyCode); } + + public String formatDepositString(Coin deposit, long amount) { + return btcFormatter.formatCoinWithCode(deposit) + " (" + deposit.getValue() / (double) amount + "%)"; + } + } From 5b94a895aa5f631a0027912daaa69accf0477357 Mon Sep 17 00:00:00 2001 From: sqrrm Date: Mon, 29 Jun 2020 20:01:47 +0200 Subject: [PATCH 02/11] Move locktime definition to Restrictions --- core/src/main/java/bisq/core/btc/wallet/Restrictions.java | 5 +++++ .../core/trade/protocol/tasks/maker/MakerSetsLockTime.java | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/Restrictions.java b/core/src/main/java/bisq/core/btc/wallet/Restrictions.java index c74ff7519c..8b0ff7f45b 100644 --- a/core/src/main/java/bisq/core/btc/wallet/Restrictions.java +++ b/core/src/main/java/bisq/core/btc/wallet/Restrictions.java @@ -90,4 +90,9 @@ public class Restrictions { MIN_REFUND_AT_MEDIATED_DISPUTE = Coin.parseCoin("0.003"); // 0.003 BTC about 21 USD @ 7000 USD/BTC return MIN_REFUND_AT_MEDIATED_DISPUTE; } + + public static int getLockTime(boolean isAsset) { + // 10 days for altcoins, 20 days for other payment methods + return isAsset ? 144 * 10 : 144 * 20; + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java index 642d49c934..94e817a340 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSetsLockTime.java @@ -17,6 +17,7 @@ package bisq.core.trade.protocol.tasks.maker; +import bisq.core.btc.wallet.Restrictions; import bisq.core.trade.Trade; import bisq.core.trade.protocol.tasks.TradeTask; @@ -38,7 +39,7 @@ public class MakerSetsLockTime extends TradeTask { runInterceptHook(); // 10 days for altcoins, 20 days for other payment methods - int delay = processModel.getOffer().getPaymentMethod().isAsset() ? 144 * 10 : 144 * 20; + int delay = Restrictions.getLockTime(processModel.getOffer().getPaymentMethod().isAsset()); if (Config.baseCurrencyNetwork().isRegtest()) { delay = 5; } From 190867e383d893a025cbe82069178be2972a4570 Mon Sep 17 00:00:00 2001 From: sqrrm Date: Tue, 30 Jun 2020 14:45:51 +0200 Subject: [PATCH 03/11] Suggest security deposit depending on previous volatility --- .../main/java/bisq/common/util/MathUtils.java | 25 ++++++++ .../main/offer/MutableOfferDataModel.java | 60 +++++++++++++++++-- .../main/offer/MutableOfferViewModel.java | 16 ++--- .../createoffer/CreateOfferDataModel.java | 3 + .../editoffer/EditOfferDataModel.java | 3 + 5 files changed, 96 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/bisq/common/util/MathUtils.java b/common/src/main/java/bisq/common/util/MathUtils.java index cb3a32a9c7..016a4db815 100644 --- a/common/src/main/java/bisq/common/util/MathUtils.java +++ b/common/src/main/java/bisq/common/util/MathUtils.java @@ -107,4 +107,29 @@ public class MathUtils { } return median; } + + public static class MovingAverage { + private long[] window; + private int n, insert; + private long sum; + + public MovingAverage(int size) { + window = new long[size]; + insert = 0; + sum = 0; + } + + public double next(long val) { + if (n < window.length) n++; + sum -= window[insert]; + sum += val; + window[insert] = val; + insert = (insert + 1) % window.length; + return (double) sum / n; + } + + public boolean fullWindow() { + return n == window.length; + } + } } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 5cbd272bea..fc098ee9a3 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -43,6 +43,8 @@ import bisq.core.payment.PaymentAccount; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.handlers.TransactionResultHandler; +import bisq.core.trade.statistics.TradeStatistics2; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.FormattingUtils; @@ -79,8 +81,12 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.SetChangeListener; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; import java.util.HashSet; import java.util.Optional; +import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -126,6 +132,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs private boolean marketPriceAvailable; private int feeTxSize = TxFeeEstimationService.TYPICAL_TX_WITH_1_INPUT_SIZE; protected boolean allowAmountUpdate = true; + private final TradeStatisticsManager tradeStatisticsManager; /////////////////////////////////////////////////////////////////////////////////////////// @@ -145,6 +152,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs FeeService feeService, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, MakerFeeProvider makerFeeProvider, + TradeStatisticsManager tradeStatisticsManager, Navigation navigation) { super(btcWalletService); @@ -160,6 +168,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs this.btcFormatter = btcFormatter; this.makerFeeProvider = makerFeeProvider; this.navigation = navigation; + this.tradeStatisticsManager = tradeStatisticsManager; offerId = createOfferService.getRandomOfferId(); shortOfferId = Utilities.getShortId(offerId); @@ -257,6 +266,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs calculateVolume(); calculateTotalToPay(); updateBalance(); + setSuggestedSecurityDeposit(getPaymentAccount()); return true; } @@ -316,14 +326,53 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs this.paymentAccount = paymentAccount; setTradeCurrencyFromPaymentAccount(paymentAccount); - - buyerSecurityDeposit.set(preferences.getBuyerSecurityDepositAsPercent(getPaymentAccount())); + setSuggestedSecurityDeposit(getPaymentAccount()); if (amount.get() != null) this.amount.set(Coin.valueOf(Math.min(amount.get().value, getMaxTradeLimit()))); } } + private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) { + var minSecurityDeposit = preferences.getBuyerSecurityDepositAsPercent(getPaymentAccount()); + if (getTradeCurrency() == null) { + setBuyerSecurityDeposit(minSecurityDeposit, false); + return; + } + // Get average historic prices over for the prior trade period equaling the lock time + var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset()); + var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000); + var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() + .filter(e -> e.getCurrencyCode().equals(getTradeCurrency().getCode())) + .filter(e -> e.getTradeDate().compareTo(startDate) >= 0) + .sorted(Comparator.comparing(TradeStatistics2::getTradeDate)) + .collect(Collectors.toList()); + var movingAverage = new MathUtils.MovingAverage(10); + var rangedMovingAverage = new ArrayList(); + sortedRangeData.forEach(e -> { + var nextVal = movingAverage.next(e.getTradePrice().getValue()); + if (movingAverage.fullWindow()) { + rangedMovingAverage.add(nextVal); + } + }); + + var min = rangedMovingAverage.stream() + .min(Double::compareTo) + .orElse(0d); + var max = rangedMovingAverage.stream() + .max(Double::compareTo) + .orElse(0d); + if (min == 0d || max == 0d) { + setBuyerSecurityDeposit(minSecurityDeposit, false); + return; + } + // Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit + var suggestedSecurityDeposit = + Math.min(2 * (max - min) / max, Restrictions.getMaxBuyerSecurityDepositAsPercent()); + buyerSecurityDeposit.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit)); + } + + private void setTradeCurrencyFromPaymentAccount(PaymentAccount paymentAccount) { if (!paymentAccount.getTradeCurrencies().contains(tradeCurrency)) { if (paymentAccount.getSelectedTradeCurrency() != null) @@ -590,9 +639,12 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs this.volume.set(volume); } - void setBuyerSecurityDeposit(double value) { + void setBuyerSecurityDeposit(double value, boolean persist) { this.buyerSecurityDeposit.set(value); - preferences.setBuyerSecurityDepositAsPercent(value, getPaymentAccount()); + if (persist) { + // Only expected to persist for manually changed deposit values + preferences.setBuyerSecurityDepositAsPercent(value, getPaymentAccount()); + } } protected boolean isUseMarketBasedPriceValue() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index 3fb6731d5f..ed250a3ca8 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -421,7 +421,7 @@ public abstract class MutableOfferViewModel ext securityDepositStringListener = (ov, oldValue, newValue) -> { if (!ignoreSecurityDepositStringListener) { if (securityDepositValidator.validate(newValue).isValid) { - setBuyerSecurityDepositToModel(); + setBuyerSecurityDepositToModel(false); dataModel.calculateTotalToPay(); } updateButtonDisableState(); @@ -898,7 +898,7 @@ public abstract class MutableOfferViewModel ext .width(800) .actionButtonText(Res.get("createOffer.resetToDefault")) .onAction(() -> { - dataModel.setBuyerSecurityDeposit(defaultSecurityDeposit); + dataModel.setBuyerSecurityDeposit(defaultSecurityDeposit, false); ignoreSecurityDepositStringListener = true; buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get())); ignoreSecurityDepositStringListener = false; @@ -915,7 +915,7 @@ public abstract class MutableOfferViewModel ext } private void applyBuyerSecurityDepositOnFocusOut() { - setBuyerSecurityDepositToModel(); + setBuyerSecurityDepositToModel(true); ignoreSecurityDepositStringListener = true; buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get())); ignoreSecurityDepositStringListener = false; @@ -1145,11 +1145,13 @@ public abstract class MutableOfferViewModel ext } } - private void setBuyerSecurityDepositToModel() { + private void setBuyerSecurityDepositToModel(boolean persistPreference) { if (buyerSecurityDeposit.get() != null && !buyerSecurityDeposit.get().isEmpty()) { - dataModel.setBuyerSecurityDeposit(ParsingUtils.parsePercentStringToDouble(buyerSecurityDeposit.get())); + dataModel.setBuyerSecurityDeposit(ParsingUtils.parsePercentStringToDouble(buyerSecurityDeposit.get()), + persistPreference); } else { - dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()); + dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent(), + persistPreference); } } @@ -1157,7 +1159,7 @@ public abstract class MutableOfferViewModel ext // If the security deposit in the model is not valid percent String value = FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get()); if (!securityDepositValidator.validate(value).isValid) { - dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()); + dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent(), false); } } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java index 05b1e859d6..c0f5e822f2 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModel.java @@ -32,6 +32,7 @@ import bisq.core.offer.CreateOfferService; import bisq.core.offer.OpenOfferManager; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.FormattingUtils; @@ -63,6 +64,7 @@ class CreateOfferDataModel extends MutableOfferDataModel { FeeService feeService, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, MakerFeeProvider makerFeeProvider, + TradeStatisticsManager tradeStatisticsManager, Navigation navigation) { super(createOfferService, openOfferManager, @@ -76,6 +78,7 @@ class CreateOfferDataModel extends MutableOfferDataModel { feeService, btcFormatter, makerFeeProvider, + tradeStatisticsManager, navigation); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java index c1bce03da5..f3ba118a13 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java @@ -37,6 +37,7 @@ import bisq.core.payment.PaymentAccount; import bisq.core.proto.persistable.CorePersistenceProtoResolver; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.FormattingUtils; @@ -74,6 +75,7 @@ class EditOfferDataModel extends MutableOfferDataModel { @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter, CorePersistenceProtoResolver corePersistenceProtoResolver, MakerFeeProvider makerFeeProvider, + TradeStatisticsManager tradeStatisticsManager, Navigation navigation) { super(createOfferService, openOfferManager, @@ -87,6 +89,7 @@ class EditOfferDataModel extends MutableOfferDataModel { feeService, btcFormatter, makerFeeProvider, + tradeStatisticsManager, navigation); this.corePersistenceProtoResolver = corePersistenceProtoResolver; } From 106c656993fe9dbd640d5e6203ee3cef4580745c Mon Sep 17 00:00:00 2001 From: sqrrm Date: Tue, 30 Jun 2020 17:52:57 +0200 Subject: [PATCH 04/11] Use same security deposit for buyer and seller --- core/src/main/java/bisq/core/offer/CreateOfferService.java | 7 ++++--- core/src/main/java/bisq/core/offer/OpenOfferManager.java | 4 ++-- core/src/main/java/bisq/core/user/Preferences.java | 3 +++ .../bisq/desktop/main/offer/MutableOfferDataModel.java | 5 +++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/CreateOfferService.java b/core/src/main/java/bisq/core/offer/CreateOfferService.java index f3791aae35..d27221c2c4 100644 --- a/core/src/main/java/bisq/core/offer/CreateOfferService.java +++ b/core/src/main/java/bisq/core/offer/CreateOfferService.java @@ -181,7 +181,7 @@ public class CreateOfferService { List acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount); String bankId = PaymentAccountUtil.getBankId(paymentAccount); List acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount); - double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(); + double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(buyerSecurityDepositAsDouble); Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first; Coin makerFeeAsCoin = getMakerFee(amount); boolean isCurrencyForMakerFeeBtc = OfferUtil.isCurrencyForMakerFeeBtc(preferences, bsqWalletService, amount); @@ -285,8 +285,9 @@ public class CreateOfferService { getSellerSecurityDeposit(amount, sellerSecurityDeposit); } - public double getSellerSecurityDepositAsDouble() { - return Restrictions.getSellerSecurityDepositAsPercent(); + public double getSellerSecurityDepositAsDouble(double buyerSecurityDeposit) { + return Preferences.USE_SYMMETRIC_SECURITY_DEPOSIT ? buyerSecurityDeposit : + Restrictions.getSellerSecurityDepositAsPercent(); } public Coin getMakerFee(Coin amount) { diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index cfc9bd1b3c..5b74864790 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -22,8 +22,8 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; import bisq.core.dao.DaoFacade; import bisq.core.exceptions.TradePriceOutOfToleranceException; -import bisq.core.locale.Res; import bisq.core.filter.FilterManager; +import bisq.core.locale.Res; import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.messages.OfferAvailabilityRequest; import bisq.core.offer.messages.OfferAvailabilityResponse; @@ -353,7 +353,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(), offer.getAmount(), buyerSecurityDeposit, - createOfferService.getSellerSecurityDepositAsDouble()); + createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit)); PlaceOfferModel model = new PlaceOfferModel(offer, reservedFundsForOffer, diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 699f939b58..4e75bcb4f0 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -118,6 +118,9 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid new BlockChainExplorer("bsq.bisq.cc (@m52go)", "https://bsq.bisq.cc/tx.html?tx=", "https://bsq.bisq.cc/Address.html?addr=") )); + public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true; + + // payload is initialized so the default values are available for Property initialization. @Setter @Delegate(excludes = ExcludesDelegateMethods.class) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index fc098ee9a3..44e4b1ffdf 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -303,7 +303,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs Tuple2 estimatedFeeAndTxSize = createOfferService.getEstimatedFeeAndTxSize(amount.get(), direction, buyerSecurityDeposit.get(), - createOfferService.getSellerSecurityDepositAsDouble()); + createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit.get())); txFeeFromFeeService = estimatedFeeAndTxSize.first; feeTxSize = estimatedFeeAndTxSize.second; } @@ -701,7 +701,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs if (amountAsCoin == null) amountAsCoin = Coin.ZERO; - Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(createOfferService.getSellerSecurityDepositAsDouble(), amountAsCoin); + Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin( + createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit.get()), amountAsCoin); return getBoundedSellerSecurityDepositAsCoin(percentOfAmountAsCoin); } From 97f76069f32abfad67d3622626a9c7730dda3cf8 Mon Sep 17 00:00:00 2001 From: sqrrm Date: Tue, 30 Jun 2020 18:09:17 +0200 Subject: [PATCH 05/11] Update security deposit info text at offer creation --- core/src/main/resources/i18n/displayStrings.properties | 1 + .../java/bisq/desktop/main/offer/MutableOfferViewModel.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 0860b75039..638f8a70f3 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -482,6 +482,7 @@ createOffer.tac=With publishing this offer I agree to trade with any trader who createOffer.currencyForFee=Trade fee createOffer.setDeposit=Set buyer's security deposit (%) createOffer.setDepositAsBuyer=Set my security deposit as buyer (%) +createOffer.setDepositForBothTraders=Set both traders' security deposit (%) createOffer.securityDepositInfo=Your buyer''s security deposit will be {0} createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0} createOffer.minSecurityDepositUsed=Min. buyer security deposit is used diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index ed250a3ca8..55b9e75d5d 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -966,7 +966,8 @@ public abstract class MutableOfferViewModel ext } public String getSecurityDepositLabel() { - return dataModel.isBuyOffer() ? Res.get("createOffer.setDepositAsBuyer") : Res.get("createOffer.setDeposit"); + return Preferences.USE_SYMMETRIC_SECURITY_DEPOSIT ? Res.get("createOffer.setDepositForBothTraders") : + dataModel.isBuyOffer() ? Res.get("createOffer.setDepositAsBuyer") : Res.get("createOffer.setDeposit"); } public String getSecurityDepositPopOverLabel(String depositInBTC) { From 2a8c25e33ac4fd51a8d69479759523e665490432 Mon Sep 17 00:00:00 2001 From: sqrrm Date: Tue, 30 Jun 2020 19:04:26 +0200 Subject: [PATCH 06/11] Fix deposit column layout --- core/src/main/resources/i18n/displayStrings.properties | 2 +- .../java/bisq/desktop/main/offer/offerbook/OfferBookView.java | 2 +- .../bisq/desktop/main/offer/offerbook/OfferBookViewModel.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 638f8a70f3..99f681d16d 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -357,7 +357,7 @@ shared.notSigned.noNeed=This account type doesn't use signing offerbook.nrOffers=No. of offers: {0} offerbook.volume={0} (min - max) -offerbook.deposit=Deposit +offerbook.deposit=Deposit BTC (%) offerbook.deposit.help=Deposit paid by each trader to guarantee the trade. Will be returned when the trade is completed. offerbook.createOfferToBuy=Create new offer to buy {0} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index af810592cc..d93b6869f9 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -934,7 +934,7 @@ public class OfferBookView extends ActivatableViewAndModel Date: Wed, 1 Jul 2020 00:01:17 +0200 Subject: [PATCH 07/11] Fix broken tests --- .../main/offer/createoffer/CreateOfferDataModelTest.java | 7 ++++++- .../main/offer/createoffer/CreateOfferViewModelTest.java | 9 +++++++-- .../main/portfolio/editoffer/EditOfferDataModelTest.java | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java index 4d58655dcc..e955089589 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferDataModelTest.java @@ -15,11 +15,14 @@ import bisq.core.payment.PaymentAccount; import bisq.core.payment.RevolutAccount; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; import org.bitcoinj.core.Coin; +import javafx.collections.FXCollections; + import java.util.HashSet; import java.util.UUID; @@ -52,17 +55,19 @@ public class CreateOfferDataModelTest { CreateOfferService createOfferService = mock(CreateOfferService.class); preferences = mock(Preferences.class); user = mock(User.class); + var tradeStats = mock(TradeStatisticsManager.class); when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); when(preferences.isUsePercentageBasedPrice()).thenReturn(true); when(preferences.getBuyerSecurityDepositAsPercent(null)).thenReturn(0.01); when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); + when(tradeStats.getObservableTradeStatisticsSet()).thenReturn(FXCollections.observableSet()); makerFeeProvider = mock(MakerFeeProvider.class); model = new CreateOfferDataModel(createOfferService, null, btcWalletService, null, preferences, user, null, priceFeedService, null, - feeService, null, makerFeeProvider, null); + feeService, null, makerFeeProvider, tradeStats, null); } @Test diff --git a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java index 5ade9bdb79..17d8ab802e 100644 --- a/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/offer/createoffer/CreateOfferViewModelTest.java @@ -34,14 +34,16 @@ import bisq.core.locale.Res; import bisq.core.offer.CreateOfferService; import bisq.core.offer.OfferPayload; import bisq.core.payment.PaymentAccount; +import bisq.core.payment.payload.PaymentMethod; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; -import bisq.core.util.coin.ImmutableCoinFormatter; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; +import bisq.core.util.coin.ImmutableCoinFormatter; import bisq.core.util.validation.InputValidator; import bisq.common.config.Config; @@ -95,6 +97,7 @@ public class CreateOfferViewModelTest { SecurityDepositValidator securityDepositValidator = mock(SecurityDepositValidator.class); AccountAgeWitnessService accountAgeWitnessService = mock(AccountAgeWitnessService.class); CreateOfferService createOfferService = mock(CreateOfferService.class); + var tradeStats = mock(TradeStatisticsManager.class); when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry); when(btcWalletService.getBalanceForAddress(any())).thenReturn(Coin.valueOf(1000L)); @@ -102,6 +105,7 @@ public class CreateOfferViewModelTest { when(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true)); when(feeService.getTxFee(anyInt())).thenReturn(Coin.valueOf(1000L)); when(user.findFirstPaymentAccountWithCurrency(any())).thenReturn(paymentAccount); + when(paymentAccount.getPaymentMethod()).thenReturn(mock(PaymentMethod.class)); when(user.getPaymentAccountsAsObservable()).thenReturn(FXCollections.observableSet()); when(securityDepositValidator.validate(any())).thenReturn(new InputValidator.ValidationResult(false)); when(accountAgeWitnessService.getMyTradeLimit(any(), any(), any())).thenReturn(100000000L); @@ -109,11 +113,12 @@ public class CreateOfferViewModelTest { when(bsqFormatter.formatCoin(any())).thenReturn("0"); when(bsqWalletService.getAvailableConfirmedBalance()).thenReturn(Coin.ZERO); when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString()); + when(tradeStats.getObservableTradeStatisticsSet()).thenReturn(FXCollections.observableSet()); CreateOfferDataModel dataModel = new CreateOfferDataModel(createOfferService, null, btcWalletService, bsqWalletService, empty, user, null, priceFeedService, accountAgeWitnessService, feeService, - coinFormatter, mock(MakerFeeProvider.class), null); + coinFormatter, mock(MakerFeeProvider.class), tradeStats, null); dataModel.initWithData(OfferPayload.Direction.BUY, new CryptoCurrency("BTC", "bitcoin")); dataModel.activate(); diff --git a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java index b362adf18a..9865f12fe1 100644 --- a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java @@ -19,6 +19,7 @@ import bisq.core.payment.PaymentAccount; import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.coin.BsqFormatter; @@ -95,7 +96,7 @@ public class EditOfferDataModelTest { btcWalletService, bsqWalletService, empty, user, null, priceFeedService, accountAgeWitnessService, feeService, null, null, - mock(MakerFeeProvider.class), null); + mock(MakerFeeProvider.class), mock(TradeStatisticsManager.class), null); } @Test From 11ff27b8924d91f30eb9c6ccb99bf253d0c2ce23 Mon Sep 17 00:00:00 2001 From: sqrrm Date: Wed, 1 Jul 2020 22:52:46 +0200 Subject: [PATCH 08/11] Fix annoying log typo --- p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java b/p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java index 58cd7a210b..8e8e48b168 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java +++ b/p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java @@ -345,7 +345,7 @@ public abstract class NetworkNode implements MessageListener { allConnections.forEach(c -> c.shutDown(CloseConnectionReason.APP_SHUT_DOWN, () -> { shutdownCompleted.getAndIncrement(); - log.info("Shutdown o fnode {} completed", c.getPeersNodeAddressOptional()); + log.info("Shutdown of node {} completed", c.getPeersNodeAddressOptional()); if (shutdownCompleted.get() == numConnections) { log.info("Shutdown completed with all connections closed"); timeoutHandler.stop(); From 3630abdeb8d1c938327da9690470f519cc7e96b0 Mon Sep 17 00:00:00 2001 From: sqrrm Date: Thu, 2 Jul 2020 23:03:26 +0200 Subject: [PATCH 09/11] Improve MovingAverage Don't include outliers (20% deviation from moving average) in moving average calculation. It's quite likely that low liquidity markets or markets with large spreads can't calculate deposit suggestion and will then suggest deposit from preferences. Added test for moving average class --- .../main/java/bisq/common/util/MathUtils.java | 61 +++++++++++++++---- .../java/bisq/common/util/MathUtilsTest.java | 57 +++++++++++++++++ .../main/offer/MutableOfferDataModel.java | 23 +++---- 3 files changed, 114 insertions(+), 27 deletions(-) create mode 100644 common/src/test/java/bisq/common/util/MathUtilsTest.java diff --git a/common/src/main/java/bisq/common/util/MathUtils.java b/common/src/main/java/bisq/common/util/MathUtils.java index 016a4db815..6de3011a42 100644 --- a/common/src/main/java/bisq/common/util/MathUtils.java +++ b/common/src/main/java/bisq/common/util/MathUtils.java @@ -22,6 +22,10 @@ import com.google.common.math.DoubleMath; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Optional; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -109,27 +113,58 @@ public class MathUtils { } public static class MovingAverage { - private long[] window; - private int n, insert; + Deque window; + private int size; private long sum; + private double outlier; - public MovingAverage(int size) { - window = new long[size]; - insert = 0; + // Outlier as ratio + public MovingAverage(int size, double outlier) { + this.size = size; + window = new ArrayDeque<>(size); + this.outlier = outlier; sum = 0; } - public double next(long val) { - if (n < window.length) n++; - sum -= window[insert]; + public Optional next(long val) { + var fullAtStart = isFull(); + if (fullAtStart) { + if (outlier > 0) { + // Return early if it's an outlier + var avg = (double) sum / size; + if (Math.abs(avg - val) / avg > outlier) { + return Optional.empty(); + } + } + sum -= window.remove(); + } + window.add(val); sum += val; - window[insert] = val; - insert = (insert + 1) % window.length; - return (double) sum / n; + if (!fullAtStart && isFull() && outlier != 0) { + removeInitialOutlier(); + } + // When discarding outliers, the first n non discarded elements return Optional.empty() + return outlier > 0 && !isFull() ? Optional.empty() : current(); } - public boolean fullWindow() { - return n == window.length; + boolean isFull() { + return window.size() == size; + } + + private void removeInitialOutlier() { + var element = window.iterator(); + while (element.hasNext()) { + var val = element.next(); + var avgExVal = (double) (sum - val) / (size - 1); + if (Math.abs(avgExVal - val) / avgExVal > outlier) { + element.remove(); + break; + } + } + } + + public Optional current() { + return window.size() == 0 ? Optional.empty() : Optional.of((double) sum / window.size()); } } } diff --git a/common/src/test/java/bisq/common/util/MathUtilsTest.java b/common/src/test/java/bisq/common/util/MathUtilsTest.java new file mode 100644 index 0000000000..1d2f56ce34 --- /dev/null +++ b/common/src/test/java/bisq/common/util/MathUtilsTest.java @@ -0,0 +1,57 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.common.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class MathUtilsTest { + + + @SuppressWarnings("OptionalGetWithoutIsPresent") + @Test + public void testMovingAverageWithoutOutlierExclusion() { + var values = new int[]{4, 5, 3, 1, 2, 4}; + // Moving average = 4, 4.5, 4, 3, 2, 7/3 + var movingAverage = new MathUtils.MovingAverage(3, 0); + int i = 0; + assertEquals(4, movingAverage.next(values[i++]).get(),0.001); + assertEquals(4.5, movingAverage.next(values[i++]).get(),0.001); + assertEquals(4, movingAverage.next(values[i++]).get(),0.001); + assertEquals(3, movingAverage.next(values[i++]).get(),0.001); + assertEquals(2, movingAverage.next(values[i++]).get(),0.001); + assertEquals((double) 7 / 3, movingAverage.next(values[i]).get(),0.001); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + @Test + public void testMovingAverageWithOutlierExclusion() { + var values = new int[]{100, 102, 95, 101, 120, 115}; + // Moving average = N/A, N/A, 99, 99.333..., N/A, 103.666... + var movingAverage = new MathUtils.MovingAverage(3, 0.2); + int i = 0; + assertFalse(movingAverage.next(values[i++]).isPresent()); + assertFalse(movingAverage.next(values[i++]).isPresent()); + assertEquals(99, movingAverage.next(values[i++]).get(),0.001); + assertEquals(99.333, movingAverage.next(values[i++]).get(),0.001); + assertFalse(movingAverage.next(values[i++]).isPresent()); + assertEquals(103.666, movingAverage.next(values[i]).get(),0.001); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index 44e4b1ffdf..599697466c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -81,7 +81,6 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.SetChangeListener; -import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.HashSet; @@ -347,21 +346,17 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs .filter(e -> e.getTradeDate().compareTo(startDate) >= 0) .sorted(Comparator.comparing(TradeStatistics2::getTradeDate)) .collect(Collectors.toList()); - var movingAverage = new MathUtils.MovingAverage(10); - var rangedMovingAverage = new ArrayList(); + var movingAverage = new MathUtils.MovingAverage(10, 0.2); + double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE}; sortedRangeData.forEach(e -> { - var nextVal = movingAverage.next(e.getTradePrice().getValue()); - if (movingAverage.fullWindow()) { - rangedMovingAverage.add(nextVal); - } + var price = e.getTradePrice().getValue(); + movingAverage.next(price).ifPresent(val -> { + if (val < extremes[0]) extremes[0] = val; + if (val > extremes[1]) extremes[1] = val; + }); }); - - var min = rangedMovingAverage.stream() - .min(Double::compareTo) - .orElse(0d); - var max = rangedMovingAverage.stream() - .max(Double::compareTo) - .orElse(0d); + var min = extremes[0]; + var max = extremes[1]; if (min == 0d || max == 0d) { setBuyerSecurityDeposit(minSecurityDeposit, false); return; From ad374bd6ec28c104ae904adbbb7f7791ab1251b3 Mon Sep 17 00:00:00 2001 From: Christoph Atteneder Date: Fri, 3 Jul 2020 19:08:00 +0200 Subject: [PATCH 10/11] Improve layout to prevent truncation in most common use-cases --- core/src/main/java/bisq/core/util/FormattingUtils.java | 9 +++++++++ .../bisq/desktop/main/offer/offerbook/OfferBookView.java | 8 ++++---- .../desktop/main/offer/offerbook/OfferBookViewModel.java | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/util/FormattingUtils.java b/core/src/main/java/bisq/core/util/FormattingUtils.java index ac1512148f..c567bf1cef 100644 --- a/core/src/main/java/bisq/core/util/FormattingUtils.java +++ b/core/src/main/java/bisq/core/util/FormattingUtils.java @@ -211,6 +211,10 @@ public class FormattingUtils { return formatToPercent(value) + "%"; } + public static String formatToRoundedPercentWithSymbol(double value) { + return formatToPercent(value, new DecimalFormat("#")) + "%"; + } + public static String formatPercentagePrice(double value) { return formatToPercentWithSymbol(value); } @@ -219,6 +223,11 @@ public class FormattingUtils { DecimalFormat decimalFormat = new DecimalFormat("#.##"); decimalFormat.setMinimumFractionDigits(2); decimalFormat.setMaximumFractionDigits(2); + + return formatToPercent(value, decimalFormat); + } + + public static String formatToPercent(double value, DecimalFormat decimalFormat) { return decimalFormat.format(MathUtils.roundDouble(value * 100.0, 2)).replace(",", "."); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index d93b6869f9..c607f2ff6c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -934,7 +934,7 @@ public class OfferBookView extends ActivatableViewAndModel getActionColumn() { TableColumn column = new AutoTooltipTableColumn<>(Res.get("shared.actions")) { { - setMinWidth(200); + setMinWidth(180); setSortable(false); } }; @@ -1194,8 +1194,8 @@ public class OfferBookView extends ActivatableViewAndModel getAvatarColumn() { AutoTooltipTableColumn column = new AutoTooltipTableColumn<>(Res.get("offerbook.trader")) { { - setMinWidth(80); - setMaxWidth(80); + setMinWidth(60); + setMaxWidth(60); setSortable(true); } }; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 6be8f9c8ec..64a6eaf9c0 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -645,7 +645,7 @@ class OfferBookViewModel extends ActivatableViewModel { } public String formatDepositString(Coin deposit, long amount) { - var percentage = FormattingUtils.formatToPercentWithSymbol(deposit.getValue() / (double) amount); + var percentage = FormattingUtils.formatToRoundedPercentWithSymbol(deposit.getValue() / (double) amount); return btcFormatter.formatCoin(deposit) + " (" + percentage + ")"; } } From 17fbc8d22a9e42b2014645c8ab87301ee4a08d97 Mon Sep 17 00:00:00 2001 From: Christoph Atteneder Date: Fri, 3 Jul 2020 21:10:42 +0200 Subject: [PATCH 11/11] Add comparator for sorting deposits --- .../bisq/desktop/main/offer/offerbook/OfferBookView.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java index c607f2ff6c..fa6d666311 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookView.java @@ -246,6 +246,14 @@ public class OfferBookView extends ActivatableViewAndModel o.getOffer().getMinVolume(), Comparator.nullsFirst(Comparator.naturalOrder()))); paymentMethodColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPaymentMethod())); avatarColumn.setComparator(Comparator.comparing(o -> o.getOffer().getOwnerNodeAddress().getFullAddress())); + depositColumn.setComparator(Comparator.comparing(o -> { + var isSellOffer = o.getOffer().getDirection() == OfferPayload.Direction.SELL; + var deposit = isSellOffer ? o.getOffer().getBuyerSecurityDeposit() : + o.getOffer().getSellerSecurityDeposit(); + + return (deposit == null) ? 0.0 : deposit.getValue() / (double) o.getOffer().getAmount().getValue(); + + }, Comparator.nullsFirst(Comparator.naturalOrder()))); nrOfOffersLabel = new AutoTooltipLabel(""); nrOfOffersLabel.setId("num-offers");