mirror of
https://github.com/bisq-network/bisq.git
synced 2025-03-03 18:56:59 +01:00
Merge pull request #4347 from sqrrm/deposit-improvements
Deposit improvements
This commit is contained in:
commit
cfc3252f7b
19 changed files with 299 additions and 34 deletions
|
@ -22,6 +22,10 @@ import com.google.common.math.DoubleMath;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -107,4 +111,60 @@ public class MathUtils {
|
||||||
}
|
}
|
||||||
return median;
|
return median;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class MovingAverage {
|
||||||
|
Deque<Long> window;
|
||||||
|
private int size;
|
||||||
|
private long sum;
|
||||||
|
private double outlier;
|
||||||
|
|
||||||
|
// Outlier as ratio
|
||||||
|
public MovingAverage(int size, double outlier) {
|
||||||
|
this.size = size;
|
||||||
|
window = new ArrayDeque<>(size);
|
||||||
|
this.outlier = outlier;
|
||||||
|
sum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Double> 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;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Double> current() {
|
||||||
|
return window.size() == 0 ? Optional.empty() : Optional.of((double) sum / window.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
57
common/src/test/java/bisq/common/util/MathUtilsTest.java
Normal file
57
common/src/test/java/bisq/common/util/MathUtilsTest.java
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
MIN_REFUND_AT_MEDIATED_DISPUTE = Coin.parseCoin("0.003"); // 0.003 BTC about 21 USD @ 7000 USD/BTC
|
||||||
return MIN_REFUND_AT_MEDIATED_DISPUTE;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,7 @@ public class CreateOfferService {
|
||||||
List<String> acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount);
|
List<String> acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount);
|
||||||
String bankId = PaymentAccountUtil.getBankId(paymentAccount);
|
String bankId = PaymentAccountUtil.getBankId(paymentAccount);
|
||||||
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
|
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
|
||||||
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble();
|
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(buyerSecurityDepositAsDouble);
|
||||||
Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
|
Coin txFeeFromFeeService = getEstimatedFeeAndTxSize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
|
||||||
Coin txFeeToUse = txFee.isPositive() ? txFee : txFeeFromFeeService;
|
Coin txFeeToUse = txFee.isPositive() ? txFee : txFeeFromFeeService;
|
||||||
Coin makerFeeAsCoin = getMakerFee(amount);
|
Coin makerFeeAsCoin = getMakerFee(amount);
|
||||||
|
@ -287,8 +287,9 @@ public class CreateOfferService {
|
||||||
getSellerSecurityDeposit(amount, sellerSecurityDeposit);
|
getSellerSecurityDeposit(amount, sellerSecurityDeposit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getSellerSecurityDepositAsDouble() {
|
public double getSellerSecurityDepositAsDouble(double buyerSecurityDeposit) {
|
||||||
return Restrictions.getSellerSecurityDepositAsPercent();
|
return Preferences.USE_SYMMETRIC_SECURITY_DEPOSIT ? buyerSecurityDeposit :
|
||||||
|
Restrictions.getSellerSecurityDepositAsPercent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getMakerFee(Coin amount) {
|
public Coin getMakerFee(Coin amount) {
|
||||||
|
|
|
@ -22,8 +22,8 @@ import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
import bisq.core.dao.DaoFacade;
|
import bisq.core.dao.DaoFacade;
|
||||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||||
import bisq.core.locale.Res;
|
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||||
|
@ -353,7 +353,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(),
|
Coin reservedFundsForOffer = createOfferService.getReservedFundsForOffer(offer.getDirection(),
|
||||||
offer.getAmount(),
|
offer.getAmount(),
|
||||||
buyerSecurityDeposit,
|
buyerSecurityDeposit,
|
||||||
createOfferService.getSellerSecurityDepositAsDouble());
|
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit));
|
||||||
|
|
||||||
PlaceOfferModel model = new PlaceOfferModel(offer,
|
PlaceOfferModel model = new PlaceOfferModel(offer,
|
||||||
reservedFundsForOffer,
|
reservedFundsForOffer,
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks.maker;
|
package bisq.core.trade.protocol.tasks.maker;
|
||||||
|
|
||||||
|
import bisq.core.btc.wallet.Restrictions;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ public class MakerSetsLockTime extends TradeTask {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
// 10 days for altcoins, 20 days for other payment methods
|
// 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()) {
|
if (Config.baseCurrencyNetwork().isRegtest()) {
|
||||||
delay = 5;
|
delay = 5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,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=")
|
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.
|
// payload is initialized so the default values are available for Property initialization.
|
||||||
@Setter
|
@Setter
|
||||||
@Delegate(excludes = ExcludesDelegateMethods.class)
|
@Delegate(excludes = ExcludesDelegateMethods.class)
|
||||||
|
|
|
@ -211,6 +211,10 @@ public class FormattingUtils {
|
||||||
return formatToPercent(value) + "%";
|
return formatToPercent(value) + "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatToRoundedPercentWithSymbol(double value) {
|
||||||
|
return formatToPercent(value, new DecimalFormat("#")) + "%";
|
||||||
|
}
|
||||||
|
|
||||||
public static String formatPercentagePrice(double value) {
|
public static String formatPercentagePrice(double value) {
|
||||||
return formatToPercentWithSymbol(value);
|
return formatToPercentWithSymbol(value);
|
||||||
}
|
}
|
||||||
|
@ -219,6 +223,11 @@ public class FormattingUtils {
|
||||||
DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
||||||
decimalFormat.setMinimumFractionDigits(2);
|
decimalFormat.setMinimumFractionDigits(2);
|
||||||
decimalFormat.setMaximumFractionDigits(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(",", ".");
|
return decimalFormat.format(MathUtils.roundDouble(value * 100.0, 2)).replace(",", ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -357,6 +357,8 @@ shared.notSigned.noNeed=This account type doesn't use signing
|
||||||
|
|
||||||
offerbook.nrOffers=No. of offers: {0}
|
offerbook.nrOffers=No. of offers: {0}
|
||||||
offerbook.volume={0} (min - max)
|
offerbook.volume={0} (min - max)
|
||||||
|
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}
|
offerbook.createOfferToBuy=Create new offer to buy {0}
|
||||||
offerbook.createOfferToSell=Create new offer to sell {0}
|
offerbook.createOfferToSell=Create new offer to sell {0}
|
||||||
|
@ -480,6 +482,7 @@ createOffer.tac=With publishing this offer I agree to trade with any trader who
|
||||||
createOffer.currencyForFee=Trade fee
|
createOffer.currencyForFee=Trade fee
|
||||||
createOffer.setDeposit=Set buyer's security deposit (%)
|
createOffer.setDeposit=Set buyer's security deposit (%)
|
||||||
createOffer.setDepositAsBuyer=Set my security deposit as buyer (%)
|
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.securityDepositInfo=Your buyer''s security deposit will be {0}
|
||||||
createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0}
|
createOffer.securityDepositInfoAsBuyer=Your security deposit as buyer will be {0}
|
||||||
createOffer.minSecurityDepositUsed=Min. buyer security deposit is used
|
createOffer.minSecurityDepositUsed=Min. buyer security deposit is used
|
||||||
|
|
|
@ -43,6 +43,8 @@ import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
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.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
|
@ -79,8 +81,11 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.collections.SetChangeListener;
|
import javafx.collections.SetChangeListener;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -126,6 +131,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||||
private boolean marketPriceAvailable;
|
private boolean marketPriceAvailable;
|
||||||
private int feeTxSize = TxFeeEstimationService.TYPICAL_TX_WITH_1_INPUT_SIZE;
|
private int feeTxSize = TxFeeEstimationService.TYPICAL_TX_WITH_1_INPUT_SIZE;
|
||||||
protected boolean allowAmountUpdate = true;
|
protected boolean allowAmountUpdate = true;
|
||||||
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -145,6 +151,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||||
FeeService feeService,
|
FeeService feeService,
|
||||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||||
MakerFeeProvider makerFeeProvider,
|
MakerFeeProvider makerFeeProvider,
|
||||||
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
Navigation navigation) {
|
Navigation navigation) {
|
||||||
super(btcWalletService);
|
super(btcWalletService);
|
||||||
|
|
||||||
|
@ -160,6 +167,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||||
this.btcFormatter = btcFormatter;
|
this.btcFormatter = btcFormatter;
|
||||||
this.makerFeeProvider = makerFeeProvider;
|
this.makerFeeProvider = makerFeeProvider;
|
||||||
this.navigation = navigation;
|
this.navigation = navigation;
|
||||||
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
|
|
||||||
offerId = createOfferService.getRandomOfferId();
|
offerId = createOfferService.getRandomOfferId();
|
||||||
shortOfferId = Utilities.getShortId(offerId);
|
shortOfferId = Utilities.getShortId(offerId);
|
||||||
|
@ -257,6 +265,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||||
calculateVolume();
|
calculateVolume();
|
||||||
calculateTotalToPay();
|
calculateTotalToPay();
|
||||||
updateBalance();
|
updateBalance();
|
||||||
|
setSuggestedSecurityDeposit(getPaymentAccount());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -294,7 +303,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||||
Tuple2<Coin, Integer> estimatedFeeAndTxSize = createOfferService.getEstimatedFeeAndTxSize(amount.get(),
|
Tuple2<Coin, Integer> estimatedFeeAndTxSize = createOfferService.getEstimatedFeeAndTxSize(amount.get(),
|
||||||
direction,
|
direction,
|
||||||
buyerSecurityDeposit.get(),
|
buyerSecurityDeposit.get(),
|
||||||
createOfferService.getSellerSecurityDepositAsDouble());
|
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit.get()));
|
||||||
txFeeFromFeeService = estimatedFeeAndTxSize.first;
|
txFeeFromFeeService = estimatedFeeAndTxSize.first;
|
||||||
feeTxSize = estimatedFeeAndTxSize.second;
|
feeTxSize = estimatedFeeAndTxSize.second;
|
||||||
}
|
}
|
||||||
|
@ -317,14 +326,49 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||||
this.paymentAccount = paymentAccount;
|
this.paymentAccount = paymentAccount;
|
||||||
|
|
||||||
setTradeCurrencyFromPaymentAccount(paymentAccount);
|
setTradeCurrencyFromPaymentAccount(paymentAccount);
|
||||||
|
setSuggestedSecurityDeposit(getPaymentAccount());
|
||||||
buyerSecurityDeposit.set(preferences.getBuyerSecurityDepositAsPercent(getPaymentAccount()));
|
|
||||||
|
|
||||||
if (amount.get() != null)
|
if (amount.get() != null)
|
||||||
this.amount.set(Coin.valueOf(Math.min(amount.get().value, getMaxTradeLimit())));
|
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, 0.2);
|
||||||
|
double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE};
|
||||||
|
sortedRangeData.forEach(e -> {
|
||||||
|
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 = extremes[0];
|
||||||
|
var max = extremes[1];
|
||||||
|
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) {
|
private void setTradeCurrencyFromPaymentAccount(PaymentAccount paymentAccount) {
|
||||||
if (!paymentAccount.getTradeCurrencies().contains(tradeCurrency)) {
|
if (!paymentAccount.getTradeCurrencies().contains(tradeCurrency)) {
|
||||||
if (paymentAccount.getSelectedTradeCurrency() != null)
|
if (paymentAccount.getSelectedTradeCurrency() != null)
|
||||||
|
@ -591,9 +635,12 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||||
this.volume.set(volume);
|
this.volume.set(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBuyerSecurityDeposit(double value) {
|
void setBuyerSecurityDeposit(double value, boolean persist) {
|
||||||
this.buyerSecurityDeposit.set(value);
|
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() {
|
protected boolean isUseMarketBasedPriceValue() {
|
||||||
|
@ -650,7 +697,8 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
||||||
if (amountAsCoin == null)
|
if (amountAsCoin == null)
|
||||||
amountAsCoin = Coin.ZERO;
|
amountAsCoin = Coin.ZERO;
|
||||||
|
|
||||||
Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(createOfferService.getSellerSecurityDepositAsDouble(), amountAsCoin);
|
Coin percentOfAmountAsCoin = CoinUtil.getPercentOfAmountAsCoin(
|
||||||
|
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit.get()), amountAsCoin);
|
||||||
return getBoundedSellerSecurityDepositAsCoin(percentOfAmountAsCoin);
|
return getBoundedSellerSecurityDepositAsCoin(percentOfAmountAsCoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -421,7 +421,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||||
securityDepositStringListener = (ov, oldValue, newValue) -> {
|
securityDepositStringListener = (ov, oldValue, newValue) -> {
|
||||||
if (!ignoreSecurityDepositStringListener) {
|
if (!ignoreSecurityDepositStringListener) {
|
||||||
if (securityDepositValidator.validate(newValue).isValid) {
|
if (securityDepositValidator.validate(newValue).isValid) {
|
||||||
setBuyerSecurityDepositToModel();
|
setBuyerSecurityDepositToModel(false);
|
||||||
dataModel.calculateTotalToPay();
|
dataModel.calculateTotalToPay();
|
||||||
}
|
}
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
|
@ -898,7 +898,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||||
.width(800)
|
.width(800)
|
||||||
.actionButtonText(Res.get("createOffer.resetToDefault"))
|
.actionButtonText(Res.get("createOffer.resetToDefault"))
|
||||||
.onAction(() -> {
|
.onAction(() -> {
|
||||||
dataModel.setBuyerSecurityDeposit(defaultSecurityDeposit);
|
dataModel.setBuyerSecurityDeposit(defaultSecurityDeposit, false);
|
||||||
ignoreSecurityDepositStringListener = true;
|
ignoreSecurityDepositStringListener = true;
|
||||||
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get()));
|
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get()));
|
||||||
ignoreSecurityDepositStringListener = false;
|
ignoreSecurityDepositStringListener = false;
|
||||||
|
@ -915,7 +915,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyBuyerSecurityDepositOnFocusOut() {
|
private void applyBuyerSecurityDepositOnFocusOut() {
|
||||||
setBuyerSecurityDepositToModel();
|
setBuyerSecurityDepositToModel(true);
|
||||||
ignoreSecurityDepositStringListener = true;
|
ignoreSecurityDepositStringListener = true;
|
||||||
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get()));
|
buyerSecurityDeposit.set(FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get()));
|
||||||
ignoreSecurityDepositStringListener = false;
|
ignoreSecurityDepositStringListener = false;
|
||||||
|
@ -966,7 +966,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSecurityDepositLabel() {
|
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) {
|
public String getSecurityDepositPopOverLabel(String depositInBTC) {
|
||||||
|
@ -1145,11 +1146,13 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBuyerSecurityDepositToModel() {
|
private void setBuyerSecurityDepositToModel(boolean persistPreference) {
|
||||||
if (buyerSecurityDeposit.get() != null && !buyerSecurityDeposit.get().isEmpty()) {
|
if (buyerSecurityDeposit.get() != null && !buyerSecurityDeposit.get().isEmpty()) {
|
||||||
dataModel.setBuyerSecurityDeposit(ParsingUtils.parsePercentStringToDouble(buyerSecurityDeposit.get()));
|
dataModel.setBuyerSecurityDeposit(ParsingUtils.parsePercentStringToDouble(buyerSecurityDeposit.get()),
|
||||||
|
persistPreference);
|
||||||
} else {
|
} else {
|
||||||
dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent());
|
dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent(),
|
||||||
|
persistPreference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1157,7 +1160,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||||
// If the security deposit in the model is not valid percent
|
// If the security deposit in the model is not valid percent
|
||||||
String value = FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get());
|
String value = FormattingUtils.formatToPercent(dataModel.getBuyerSecurityDeposit().get());
|
||||||
if (!securityDepositValidator.validate(value).isValid) {
|
if (!securityDepositValidator.validate(value).isValid) {
|
||||||
dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent());
|
dataModel.setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import bisq.core.offer.CreateOfferService;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
|
@ -63,6 +64,7 @@ class CreateOfferDataModel extends MutableOfferDataModel {
|
||||||
FeeService feeService,
|
FeeService feeService,
|
||||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||||
MakerFeeProvider makerFeeProvider,
|
MakerFeeProvider makerFeeProvider,
|
||||||
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
Navigation navigation) {
|
Navigation navigation) {
|
||||||
super(createOfferService,
|
super(createOfferService,
|
||||||
openOfferManager,
|
openOfferManager,
|
||||||
|
@ -76,6 +78,7 @@ class CreateOfferDataModel extends MutableOfferDataModel {
|
||||||
feeService,
|
feeService,
|
||||||
btcFormatter,
|
btcFormatter,
|
||||||
makerFeeProvider,
|
makerFeeProvider,
|
||||||
|
tradeStatisticsManager,
|
||||||
navigation);
|
navigation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,6 @@ import bisq.desktop.main.offer.OfferView;
|
||||||
import bisq.desktop.main.overlays.popups.Popup;
|
import bisq.desktop.main.overlays.popups.Popup;
|
||||||
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
||||||
import bisq.desktop.util.CssTheme;
|
import bisq.desktop.util.CssTheme;
|
||||||
import bisq.desktop.util.DisplayUtils;
|
|
||||||
import bisq.desktop.util.FormBuilder;
|
import bisq.desktop.util.FormBuilder;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
import bisq.desktop.util.Layout;
|
import bisq.desktop.util.Layout;
|
||||||
|
@ -130,7 +129,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||||
private AutocompleteComboBox<PaymentMethod> paymentMethodComboBox;
|
private AutocompleteComboBox<PaymentMethod> paymentMethodComboBox;
|
||||||
private AutoTooltipButton createOfferButton;
|
private AutoTooltipButton createOfferButton;
|
||||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> amountColumn, volumeColumn, marketColumn,
|
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> amountColumn, volumeColumn, marketColumn,
|
||||||
priceColumn, paymentMethodColumn, signingStateColumn, avatarColumn;
|
priceColumn, paymentMethodColumn, depositColumn, signingStateColumn, avatarColumn;
|
||||||
private TableView<OfferBookListItem> tableView;
|
private TableView<OfferBookListItem> tableView;
|
||||||
|
|
||||||
private OfferView.OfferActionHandler offerActionHandler;
|
private OfferView.OfferActionHandler offerActionHandler;
|
||||||
|
@ -224,6 +223,8 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||||
tableView.getColumns().add(volumeColumn);
|
tableView.getColumns().add(volumeColumn);
|
||||||
paymentMethodColumn = getPaymentMethodColumn();
|
paymentMethodColumn = getPaymentMethodColumn();
|
||||||
tableView.getColumns().add(paymentMethodColumn);
|
tableView.getColumns().add(paymentMethodColumn);
|
||||||
|
depositColumn = getDepositColumn();
|
||||||
|
tableView.getColumns().add(depositColumn);
|
||||||
signingStateColumn = getSigningStateColumn();
|
signingStateColumn = getSigningStateColumn();
|
||||||
tableView.getColumns().add(signingStateColumn);
|
tableView.getColumns().add(signingStateColumn);
|
||||||
avatarColumn = getAvatarColumn();
|
avatarColumn = getAvatarColumn();
|
||||||
|
@ -245,6 +246,14 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||||
volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getMinVolume(), Comparator.nullsFirst(Comparator.naturalOrder())));
|
volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getMinVolume(), Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||||
paymentMethodColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPaymentMethod()));
|
paymentMethodColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPaymentMethod()));
|
||||||
avatarColumn.setComparator(Comparator.comparing(o -> o.getOffer().getOwnerNodeAddress().getFullAddress()));
|
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 = new AutoTooltipLabel("");
|
||||||
nrOfOffersLabel.setId("num-offers");
|
nrOfOffersLabel.setId("num-offers");
|
||||||
|
@ -927,10 +936,56 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||||
return column;
|
return column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> getDepositColumn() {
|
||||||
|
AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> 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<OfferBookListItem, OfferBookListItem> call(
|
||||||
|
TableColumn<OfferBookListItem, OfferBookListItem> 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<OfferBookListItem, OfferBookListItem> getActionColumn() {
|
private TableColumn<OfferBookListItem, OfferBookListItem> getActionColumn() {
|
||||||
TableColumn<OfferBookListItem, OfferBookListItem> column = new AutoTooltipTableColumn<>(Res.get("shared.actions")) {
|
TableColumn<OfferBookListItem, OfferBookListItem> column = new AutoTooltipTableColumn<>(Res.get("shared.actions")) {
|
||||||
{
|
{
|
||||||
setMinWidth(200);
|
setMinWidth(180);
|
||||||
setSortable(false);
|
setSortable(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1147,8 +1202,8 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> getAvatarColumn() {
|
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> getAvatarColumn() {
|
||||||
AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> column = new AutoTooltipTableColumn<>(Res.get("offerbook.trader")) {
|
AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> column = new AutoTooltipTableColumn<>(Res.get("offerbook.trader")) {
|
||||||
{
|
{
|
||||||
setMinWidth(80);
|
setMinWidth(60);
|
||||||
setMaxWidth(80);
|
setMaxWidth(60);
|
||||||
setSortable(true);
|
setSortable(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,8 +47,8 @@ import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.closed.ClosedTradableManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.coin.BsqFormatter;
|
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
|
import bisq.core.util.coin.BsqFormatter;
|
||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
@ -168,9 +168,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||||
this.filteredItems = new FilteredList<>(offerBook.getOfferBookListItems());
|
this.filteredItems = new FilteredList<>(offerBook.getOfferBookListItems());
|
||||||
this.sortedItems = new SortedList<>(filteredItems);
|
this.sortedItems = new SortedList<>(filteredItems);
|
||||||
|
|
||||||
tradeCurrencyListChangeListener = c -> {
|
tradeCurrencyListChangeListener = c -> fillAllTradeCurrencies();
|
||||||
fillAllTradeCurrencies();
|
|
||||||
};
|
|
||||||
|
|
||||||
filterItemsListener = c -> {
|
filterItemsListener = c -> {
|
||||||
final Optional<OfferBookListItem> highestAmountOffer = filteredItems.stream()
|
final Optional<OfferBookListItem> highestAmountOffer = filteredItems.stream()
|
||||||
|
@ -645,4 +643,9 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||||
else
|
else
|
||||||
return (direction == OfferPayload.Direction.SELL) ? Res.get("shared.buyingCurrency", currencyCode) : Res.get("shared.sellingCurrency", currencyCode);
|
return (direction == OfferPayload.Direction.SELL) ? Res.get("shared.buyingCurrency", currencyCode) : Res.get("shared.sellingCurrency", currencyCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String formatDepositString(Coin deposit, long amount) {
|
||||||
|
var percentage = FormattingUtils.formatToRoundedPercentWithSymbol(deposit.getValue() / (double) amount);
|
||||||
|
return btcFormatter.formatCoin(deposit) + " (" + percentage + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
|
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
|
@ -74,6 +75,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
||||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||||
CorePersistenceProtoResolver corePersistenceProtoResolver,
|
CorePersistenceProtoResolver corePersistenceProtoResolver,
|
||||||
MakerFeeProvider makerFeeProvider,
|
MakerFeeProvider makerFeeProvider,
|
||||||
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
Navigation navigation) {
|
Navigation navigation) {
|
||||||
super(createOfferService,
|
super(createOfferService,
|
||||||
openOfferManager,
|
openOfferManager,
|
||||||
|
@ -87,6 +89,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
||||||
feeService,
|
feeService,
|
||||||
btcFormatter,
|
btcFormatter,
|
||||||
makerFeeProvider,
|
makerFeeProvider,
|
||||||
|
tradeStatisticsManager,
|
||||||
navigation);
|
navigation);
|
||||||
this.corePersistenceProtoResolver = corePersistenceProtoResolver;
|
this.corePersistenceProtoResolver = corePersistenceProtoResolver;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,14 @@ import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.RevolutAccount;
|
import bisq.core.payment.RevolutAccount;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -52,17 +55,19 @@ public class CreateOfferDataModelTest {
|
||||||
CreateOfferService createOfferService = mock(CreateOfferService.class);
|
CreateOfferService createOfferService = mock(CreateOfferService.class);
|
||||||
preferences = mock(Preferences.class);
|
preferences = mock(Preferences.class);
|
||||||
user = mock(User.class);
|
user = mock(User.class);
|
||||||
|
var tradeStats = mock(TradeStatisticsManager.class);
|
||||||
|
|
||||||
when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry);
|
when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry);
|
||||||
when(preferences.isUsePercentageBasedPrice()).thenReturn(true);
|
when(preferences.isUsePercentageBasedPrice()).thenReturn(true);
|
||||||
when(preferences.getBuyerSecurityDepositAsPercent(null)).thenReturn(0.01);
|
when(preferences.getBuyerSecurityDepositAsPercent(null)).thenReturn(0.01);
|
||||||
when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString());
|
when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString());
|
||||||
|
when(tradeStats.getObservableTradeStatisticsSet()).thenReturn(FXCollections.observableSet());
|
||||||
|
|
||||||
makerFeeProvider = mock(MakerFeeProvider.class);
|
makerFeeProvider = mock(MakerFeeProvider.class);
|
||||||
model = new CreateOfferDataModel(createOfferService, null, btcWalletService,
|
model = new CreateOfferDataModel(createOfferService, null, btcWalletService,
|
||||||
null, preferences, user, null,
|
null, preferences, user, null,
|
||||||
priceFeedService, null,
|
priceFeedService, null,
|
||||||
feeService, null, makerFeeProvider, null);
|
feeService, null, makerFeeProvider, tradeStats, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -34,14 +34,16 @@ import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.CreateOfferService;
|
import bisq.core.offer.CreateOfferService;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferPayload;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.MarketPrice;
|
import bisq.core.provider.price.MarketPrice;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.coin.ImmutableCoinFormatter;
|
|
||||||
import bisq.core.util.coin.BsqFormatter;
|
import bisq.core.util.coin.BsqFormatter;
|
||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
|
import bisq.core.util.coin.ImmutableCoinFormatter;
|
||||||
import bisq.core.util.validation.InputValidator;
|
import bisq.core.util.validation.InputValidator;
|
||||||
|
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
|
@ -95,6 +97,7 @@ public class CreateOfferViewModelTest {
|
||||||
SecurityDepositValidator securityDepositValidator = mock(SecurityDepositValidator.class);
|
SecurityDepositValidator securityDepositValidator = mock(SecurityDepositValidator.class);
|
||||||
AccountAgeWitnessService accountAgeWitnessService = mock(AccountAgeWitnessService.class);
|
AccountAgeWitnessService accountAgeWitnessService = mock(AccountAgeWitnessService.class);
|
||||||
CreateOfferService createOfferService = mock(CreateOfferService.class);
|
CreateOfferService createOfferService = mock(CreateOfferService.class);
|
||||||
|
var tradeStats = mock(TradeStatisticsManager.class);
|
||||||
|
|
||||||
when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry);
|
when(btcWalletService.getOrCreateAddressEntry(anyString(), any())).thenReturn(addressEntry);
|
||||||
when(btcWalletService.getBalanceForAddress(any())).thenReturn(Coin.valueOf(1000L));
|
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(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true));
|
||||||
when(feeService.getTxFee(anyInt())).thenReturn(Coin.valueOf(1000L));
|
when(feeService.getTxFee(anyInt())).thenReturn(Coin.valueOf(1000L));
|
||||||
when(user.findFirstPaymentAccountWithCurrency(any())).thenReturn(paymentAccount);
|
when(user.findFirstPaymentAccountWithCurrency(any())).thenReturn(paymentAccount);
|
||||||
|
when(paymentAccount.getPaymentMethod()).thenReturn(mock(PaymentMethod.class));
|
||||||
when(user.getPaymentAccountsAsObservable()).thenReturn(FXCollections.observableSet());
|
when(user.getPaymentAccountsAsObservable()).thenReturn(FXCollections.observableSet());
|
||||||
when(securityDepositValidator.validate(any())).thenReturn(new InputValidator.ValidationResult(false));
|
when(securityDepositValidator.validate(any())).thenReturn(new InputValidator.ValidationResult(false));
|
||||||
when(accountAgeWitnessService.getMyTradeLimit(any(), any(), any())).thenReturn(100000000L);
|
when(accountAgeWitnessService.getMyTradeLimit(any(), any(), any())).thenReturn(100000000L);
|
||||||
|
@ -109,11 +113,12 @@ public class CreateOfferViewModelTest {
|
||||||
when(bsqFormatter.formatCoin(any())).thenReturn("0");
|
when(bsqFormatter.formatCoin(any())).thenReturn("0");
|
||||||
when(bsqWalletService.getAvailableConfirmedBalance()).thenReturn(Coin.ZERO);
|
when(bsqWalletService.getAvailableConfirmedBalance()).thenReturn(Coin.ZERO);
|
||||||
when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString());
|
when(createOfferService.getRandomOfferId()).thenReturn(UUID.randomUUID().toString());
|
||||||
|
when(tradeStats.getObservableTradeStatisticsSet()).thenReturn(FXCollections.observableSet());
|
||||||
|
|
||||||
CreateOfferDataModel dataModel = new CreateOfferDataModel(createOfferService, null, btcWalletService,
|
CreateOfferDataModel dataModel = new CreateOfferDataModel(createOfferService, null, btcWalletService,
|
||||||
bsqWalletService, empty, user, null, priceFeedService,
|
bsqWalletService, empty, user, null, priceFeedService,
|
||||||
accountAgeWitnessService, feeService,
|
accountAgeWitnessService, feeService,
|
||||||
coinFormatter, mock(MakerFeeProvider.class), null);
|
coinFormatter, mock(MakerFeeProvider.class), tradeStats, null);
|
||||||
dataModel.initWithData(OfferPayload.Direction.BUY, new CryptoCurrency("BTC", "bitcoin"));
|
dataModel.initWithData(OfferPayload.Direction.BUY, new CryptoCurrency("BTC", "bitcoin"));
|
||||||
dataModel.activate();
|
dataModel.activate();
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.MarketPrice;
|
import bisq.core.provider.price.MarketPrice;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.coin.BsqFormatter;
|
import bisq.core.util.coin.BsqFormatter;
|
||||||
|
@ -95,7 +96,7 @@ public class EditOfferDataModelTest {
|
||||||
btcWalletService, bsqWalletService, empty, user,
|
btcWalletService, bsqWalletService, empty, user,
|
||||||
null, priceFeedService,
|
null, priceFeedService,
|
||||||
accountAgeWitnessService, feeService, null, null,
|
accountAgeWitnessService, feeService, null, null,
|
||||||
mock(MakerFeeProvider.class), null);
|
mock(MakerFeeProvider.class), mock(TradeStatisticsManager.class), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -345,7 +345,7 @@ public abstract class NetworkNode implements MessageListener {
|
||||||
allConnections.forEach(c -> c.shutDown(CloseConnectionReason.APP_SHUT_DOWN,
|
allConnections.forEach(c -> c.shutDown(CloseConnectionReason.APP_SHUT_DOWN,
|
||||||
() -> {
|
() -> {
|
||||||
shutdownCompleted.getAndIncrement();
|
shutdownCompleted.getAndIncrement();
|
||||||
log.info("Shutdown o fnode {} completed", c.getPeersNodeAddressOptional());
|
log.info("Shutdown of node {} completed", c.getPeersNodeAddressOptional());
|
||||||
if (shutdownCompleted.get() == numConnections) {
|
if (shutdownCompleted.get() == numConnections) {
|
||||||
log.info("Shutdown completed with all connections closed");
|
log.info("Shutdown completed with all connections closed");
|
||||||
timeoutHandler.stop();
|
timeoutHandler.stop();
|
||||||
|
|
Loading…
Add table
Reference in a new issue