diff --git a/core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java b/core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java
new file mode 100644
index 00000000000..50fab443dfa
--- /dev/null
+++ b/core/src/main/java/bisq/core/trade/closed/ClosedTradeUtil.java
@@ -0,0 +1,390 @@
+/*
+ * 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.core.trade.closed;
+
+import bisq.core.btc.wallet.BsqWalletService;
+import bisq.core.locale.CurrencyUtil;
+import bisq.core.locale.Res;
+import bisq.core.monetary.Altcoin;
+import bisq.core.monetary.Price;
+import bisq.core.monetary.Volume;
+import bisq.core.offer.Offer;
+import bisq.core.offer.OpenOffer;
+import bisq.core.trade.Tradable;
+import bisq.core.trade.Trade;
+import bisq.core.trade.statistics.TradeStatisticsManager;
+import bisq.core.user.Preferences;
+import bisq.core.util.coin.BsqFormatter;
+import bisq.core.util.coin.CoinFormatter;
+
+import bisq.network.p2p.NodeAddress;
+
+import bisq.common.util.Tuple2;
+
+import org.bitcoinj.core.Coin;
+import org.bitcoinj.core.Monetary;
+import org.bitcoinj.utils.Fiat;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import lombok.extern.slf4j.Slf4j;
+
+import static bisq.core.trade.Trade.DisputeState.DISPUTE_CLOSED;
+import static bisq.core.trade.Trade.DisputeState.MEDIATION_CLOSED;
+import static bisq.core.trade.Trade.DisputeState.REFUND_REQUEST_CLOSED;
+import static bisq.core.util.AveragePriceUtil.getAveragePriceTuple;
+import static bisq.core.util.FormattingUtils.BTC_FORMATTER_KEY;
+import static bisq.core.util.FormattingUtils.formatPercentagePrice;
+import static bisq.core.util.FormattingUtils.formatPrice;
+import static bisq.core.util.FormattingUtils.formatToPercentWithSymbol;
+import static bisq.core.util.VolumeUtil.formatVolume;
+import static bisq.core.util.VolumeUtil.formatVolumeWithCode;
+
+@Slf4j
+public class ClosedTradeUtil {
+
+ // Resource bundle i18n keys with Desktop UI specific property names,
+ // having "generic-enough" property values to be referenced in the core layer.
+ private static final String I18N_KEY_TOTAL_AMOUNT = "closedTradesSummaryWindow.totalAmount.value";
+ private static final String I18N_KEY_TOTAL_TX_FEE = "closedTradesSummaryWindow.totalMinerFee.value";
+ private static final String I18N_KEY_TOTAL_TRADE_FEE_BTC = "closedTradesSummaryWindow.totalTradeFeeInBtc.value";
+ private static final String I18N_KEY_TOTAL_TRADE_FEE_BSQ = "closedTradesSummaryWindow.totalTradeFeeInBsq.value";
+
+ private final ClosedTradableManager closedTradableManager;
+ private final BsqWalletService bsqWalletService;
+ private final BsqFormatter bsqFormatter;
+ private final CoinFormatter btcFormatter;
+ private final Preferences preferences;
+ private final TradeStatisticsManager tradeStatisticsManager;
+
+ @Inject
+ public ClosedTradeUtil(ClosedTradableManager closedTradableManager,
+ BsqWalletService bsqWalletService,
+ BsqFormatter bsqFormatter,
+ @Named(BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
+ Preferences preferences,
+ TradeStatisticsManager tradeStatisticsManager) {
+ this.closedTradableManager = closedTradableManager;
+ this.bsqWalletService = bsqWalletService;
+ this.bsqFormatter = bsqFormatter;
+ this.btcFormatter = btcFormatter;
+ this.preferences = preferences;
+ this.tradeStatisticsManager = tradeStatisticsManager;
+ }
+
+ public boolean wasMyOffer(Tradable tradable) {
+ return closedTradableManager.wasMyOffer(tradable.getOffer());
+ }
+
+ public Coin getTotalAmount(List tradableList) {
+ return Coin.valueOf(tradableList.stream()
+ .filter(e -> e instanceof Trade)
+ .map(e -> (Trade) e)
+ .mapToLong(Trade::getTradeAmountAsLong)
+ .sum());
+ }
+
+ public String getAmountAsString(Tradable tradable) {
+ if (tradable instanceof Trade)
+ return btcFormatter.formatCoin(((Trade) tradable).getTradeAmount());
+ else
+ return "";
+ }
+
+ public String getTotalAmountWithVolumeAsString(Coin totalTradeAmount, Volume volume) {
+ return Res.get(I18N_KEY_TOTAL_AMOUNT,
+ btcFormatter.formatCoin(totalTradeAmount, true),
+ formatVolumeWithCode(volume));
+ }
+
+ public String getPriceAsString(Tradable tradable) {
+ if (tradable instanceof Trade)
+ return formatPrice(((Trade) tradable).getTradePrice());
+ else
+ return formatPrice(tradable.getOffer().getPrice());
+ }
+
+ public String getPriceDeviationAsString(Tradable tradable) {
+ if (tradable.getOffer().isUseMarketBasedPrice()) {
+ return formatPercentagePrice(tradable.getOffer().getMarketPriceMargin());
+ } else {
+ return Res.get("shared.na");
+ }
+ }
+
+ public String getVolumeAsString(Tradable tradable, boolean appendCode) {
+ if (tradable instanceof OpenOffer) {
+ return "";
+ }
+
+ Trade trade = (Trade) tradable;
+ return formatVolume(trade.getTradeVolume(), appendCode);
+ }
+
+ public String getVolumeCurrencyAsString(Tradable tradable) {
+ Volume volume;
+ if (tradable instanceof OpenOffer) {
+ OpenOffer openOffer = (OpenOffer) tradable;
+ volume = openOffer.getOffer().getVolume();
+ } else {
+ Trade trade = (Trade) tradable;
+ volume = trade.getTradeVolume();
+ }
+ return volume != null ? volume.getCurrencyCode() : "";
+ }
+
+ public Map getTotalVolumeByCurrency(List tradableList) {
+ Map map = new HashMap<>();
+ tradableList.stream()
+ .filter(e -> e instanceof Trade)
+ .map(e -> (Trade) e)
+ .map(Trade::getTradeVolume)
+ .filter(Objects::nonNull)
+ .forEach(volume -> {
+ String currencyCode = volume.getCurrencyCode();
+ map.putIfAbsent(currencyCode, 0L);
+ map.put(currencyCode, volume.getValue() + map.get(currencyCode));
+ });
+ return map;
+ }
+
+ public Map getTotalVolumeByCurrencyAsString(List tradableList) {
+ return getTotalVolumeByCurrency(tradableList).entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey,
+ entry -> {
+ String currencyCode = entry.getKey();
+ Monetary monetary;
+ if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
+ monetary = Altcoin.valueOf(currencyCode, entry.getValue());
+ } else {
+ monetary = Fiat.valueOf(currencyCode, entry.getValue());
+ }
+ return formatVolumeWithCode(new Volume(monetary));
+ }
+ ));
+ }
+
+ public Volume getBsqVolumeInUsdWithAveragePrice(Coin amount) {
+ Tuple2 tuple = getAveragePriceTuple(preferences, tradeStatisticsManager, 30);
+ Price usdPrice = tuple.first;
+ long value = Math.round(amount.value * usdPrice.getValue() / 100d);
+ return new Volume(Fiat.valueOf("USD", value));
+ }
+
+ public Coin getTotalTxFee(List tradableList) {
+ return Coin.valueOf(tradableList.stream()
+ .mapToLong(tradable -> {
+ if (wasMyOffer(tradable) || tradable instanceof OpenOffer) {
+ return tradable.getOffer().getTxFee().value;
+ } else {
+ // taker pays for 3 transactions
+ return ((Trade) tradable).getTxFee().multiply(3).value;
+ }
+ })
+ .sum());
+ }
+
+ public String getTxFeeAsString(Tradable tradable) {
+ if (!wasMyOffer(tradable) && (tradable instanceof Trade)) {
+ // taker pays for 3 transactions
+ return btcFormatter.formatCoin(((Trade) tradable).getTxFee().multiply(3));
+ } else {
+ return btcFormatter.formatCoin(tradable.getOffer().getTxFee());
+ }
+ }
+
+ public String getTotalTxFeeAsString(Coin totalTradeAmount, Coin totalTxFee) {
+ double percentage = ((double) totalTxFee.value) / totalTradeAmount.value;
+ return Res.get(I18N_KEY_TOTAL_TX_FEE,
+ btcFormatter.formatCoin(totalTxFee, true),
+ formatToPercentWithSymbol(percentage));
+ }
+
+ public boolean isCurrencyForTradeFeeBtc(Tradable tradable) {
+ Offer offer = tradable.getOffer();
+ if (wasMyOffer(tradable) || tradable instanceof OpenOffer) {
+ // I was maker so we use offer
+ return offer.isCurrencyForMakerFeeBtc();
+ } else {
+ Trade trade = (Trade) tradable;
+ String takerFeeTxId = trade.getTakerFeeTxId();
+ // If we find our tx in the bsq wallet it's a BSQ trade fee tx.
+ return bsqWalletService.getTransaction(takerFeeTxId) == null;
+ }
+ }
+
+ public Coin getTotalTradeFee(List tradableList, boolean expectBtcFee) {
+ return Coin.valueOf(tradableList.stream()
+ .mapToLong(tradable -> getTradeFee(tradable, expectBtcFee))
+ .sum());
+ }
+
+ public String getTradeFeeAsString(Tradable tradable, boolean appendCode) {
+ Offer offer = tradable.getOffer();
+ if (wasMyOffer(tradable) || tradable instanceof OpenOffer) {
+ CoinFormatter formatter = offer.isCurrencyForMakerFeeBtc() ? btcFormatter : bsqFormatter;
+ return formatter.formatCoin(offer.getMakerFee(), appendCode);
+ } else {
+ Trade trade = (Trade) tradable;
+ String takerFeeTxId = trade.getTakerFeeTxId();
+ if (bsqWalletService.getTransaction(takerFeeTxId) == null) {
+ // Was BTC fee
+ return btcFormatter.formatCoin(trade.getTakerFee(), appendCode);
+ } else {
+ // BSQ fee
+ return bsqFormatter.formatCoin(trade.getTakerFee(), appendCode);
+ }
+ }
+ }
+
+ public String getTotalTradeFeeInBtcAsString(Coin totalTradeAmount, Coin totalTradeFee) {
+ double percentage = ((double) totalTradeFee.value) / totalTradeAmount.value;
+ return Res.get(I18N_KEY_TOTAL_TRADE_FEE_BTC,
+ btcFormatter.formatCoin(totalTradeFee, true),
+ formatToPercentWithSymbol(percentage));
+ }
+
+ public String getBuyerSecurityDepositAsString(Tradable tradable) {
+ if (tradable.getOffer() != null)
+ return btcFormatter.formatCoin(tradable.getOffer().getBuyerSecurityDeposit());
+ else
+ return "";
+ }
+
+ public String getSellerSecurityDepositAsString(Tradable tradable) {
+ if (tradable.getOffer() != null)
+ return btcFormatter.formatCoin(tradable.getOffer().getSellerSecurityDeposit());
+ else
+ return "";
+ }
+
+ public String getMarketLabel(Tradable tradable) {
+ return CurrencyUtil.getCurrencyPair(tradable.getOffer().getCurrencyCode());
+ }
+
+ public int getNumPastTrades(Tradable tradable) {
+ if (!(tradable instanceof Trade))
+ return 0;
+
+ return closedTradableManager.getClosedTrades().stream()
+ .filter(candidate -> {
+ NodeAddress candidateAddress = candidate.getTradingPeerNodeAddress();
+ NodeAddress tradableAddress = ((Trade) tradable).getTradingPeerNodeAddress();
+ return candidateAddress != null
+ && tradableAddress != null
+ && candidateAddress.getFullAddress().equals(tradableAddress.getFullAddress());
+ })
+ .collect(Collectors.toSet())
+ .size();
+ }
+
+ public String getTotalTradeFeeInBsqAsString(Coin totalTradeFee,
+ Volume tradeAmountVolume,
+ Volume bsqVolumeInUsd) {
+ double percentage = ((double) bsqVolumeInUsd.getValue()) / tradeAmountVolume.getValue();
+ return Res.get(I18N_KEY_TOTAL_TRADE_FEE_BSQ,
+ bsqFormatter.formatCoin(totalTradeFee, true),
+ formatToPercentWithSymbol(percentage));
+ }
+
+
+ public String getStateAsString(Tradable tradable) {
+ if (tradable != null) {
+ if (tradable instanceof Trade) {
+ Trade trade = (Trade) tradable;
+
+ if (trade.isWithdrawn() || trade.isPayoutPublished()) {
+ return Res.get("portfolio.closed.completed");
+ } else if (trade.getDisputeState() == DISPUTE_CLOSED) {
+ return Res.get("portfolio.closed.ticketClosed");
+ } else if (trade.getDisputeState() == MEDIATION_CLOSED) {
+ return Res.get("portfolio.closed.mediationTicketClosed");
+ } else if (trade.getDisputeState() == REFUND_REQUEST_CLOSED) {
+ return Res.get("portfolio.closed.ticketClosed");
+ } else {
+ log.error("That must not happen. We got a pending state but we are in"
+ + " the closed trades list. state={}",
+ trade.getState().name());
+ return Res.get("shared.na");
+ }
+ } else if (tradable instanceof OpenOffer) {
+ OpenOffer.State state = ((OpenOffer) tradable).getState();
+ log.trace("OpenOffer state={}", state);
+ switch (state) {
+ case AVAILABLE:
+ case RESERVED:
+ case CLOSED:
+ case DEACTIVATED:
+ log.error("Invalid state {}", state);
+ return state.name();
+ case CANCELED:
+ return Res.get("portfolio.closed.canceled");
+ default:
+ log.error("Unhandled state {}", state);
+ return state.name();
+ }
+ }
+ }
+ return "";
+ }
+
+ protected long getTradeFee(Tradable tradable, boolean expectBtcFee) {
+ Offer offer = tradable.getOffer();
+ if (wasMyOffer(tradable) || tradable instanceof OpenOffer) {
+ String makerFeeTxId = offer.getOfferFeePaymentTxId();
+ boolean notInBsqWallet = bsqWalletService.getTransaction(makerFeeTxId) == null;
+ if (expectBtcFee) {
+ if (notInBsqWallet) {
+ return offer.getMakerFee().value;
+ } else {
+ return 0;
+ }
+ } else {
+ if (notInBsqWallet) {
+ return 0;
+ } else {
+ return offer.getMakerFee().value;
+ }
+ }
+ } else {
+ Trade trade = (Trade) tradable;
+ String takerFeeTxId = trade.getTakerFeeTxId();
+ boolean notInBsqWallet = bsqWalletService.getTransaction(takerFeeTxId) == null;
+ if (expectBtcFee) {
+ if (notInBsqWallet) {
+ return trade.getTakerFee().value;
+ } else {
+ return 0;
+ }
+ } else {
+ if (notInBsqWallet) {
+ return 0;
+ } else {
+ return trade.getTakerFee().value;
+ }
+ }
+ }
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java
index c5e0dcfd128..76026c6da47 100644
--- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java
+++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java
@@ -20,26 +20,19 @@
import bisq.desktop.common.model.ActivatableDataModel;
import bisq.desktop.main.PriceUtil;
-import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
-import bisq.core.offer.OpenOffer;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.Tradable;
-import bisq.core.trade.Trade;
import bisq.core.trade.closed.ClosedTradableManager;
-import bisq.core.trade.statistics.TradeStatisticsManager;
+import bisq.core.trade.closed.ClosedTradeUtil;
import bisq.core.user.Preferences;
-import bisq.core.util.AveragePriceUtil;
import bisq.core.util.VolumeUtil;
-import bisq.common.util.Tuple2;
-
import org.bitcoinj.core.Coin;
-import org.bitcoinj.utils.Fiat;
import com.google.inject.Inject;
@@ -47,32 +40,29 @@
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
-import java.util.HashMap;
+import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
class ClosedTradesDataModel extends ActivatableDataModel {
final ClosedTradableManager closedTradableManager;
- private final BsqWalletService bsqWalletService;
+ private final ClosedTradeUtil closedTradeUtil;
private final Preferences preferences;
- private final TradeStatisticsManager tradeStatisticsManager;
private final PriceFeedService priceFeedService;
private final ObservableList list = FXCollections.observableArrayList();
private final ListChangeListener tradesListChangeListener;
@Inject
public ClosedTradesDataModel(ClosedTradableManager closedTradableManager,
- BsqWalletService bsqWalletService,
+ ClosedTradeUtil closedTradeUtil,
Preferences preferences,
- TradeStatisticsManager tradeStatisticsManager,
PriceFeedService priceFeedService) {
this.closedTradableManager = closedTradableManager;
- this.bsqWalletService = bsqWalletService;
+ this.closedTradeUtil = closedTradeUtil;
this.preferences = preferences;
- this.tradeStatisticsManager = tradeStatisticsManager;
this.priceFeedService = priceFeedService;
tradesListChangeListener = change -> applyList();
@@ -93,6 +83,14 @@ public ObservableList getList() {
return list;
}
+ /**
+ * Supplies a List from this JFX ObservableList
+ * collection, for passing to core's ClosedTradeUtil which has no dependency on JFX.
+ */
+ public final Supplier> tradableList = () -> list.stream()
+ .map(ClosedTradableListItem::getTradable)
+ .collect(Collectors.toList());
+
public OfferPayload.Direction getDirection(Offer offer) {
return closedTradableManager.wasMyOffer(offer) ? offer.getDirection() : offer.getMirroredDirection();
}
@@ -106,33 +104,12 @@ private void applyList() {
list.sort((o1, o2) -> o2.getTradable().getDate().compareTo(o1.getTradable().getDate()));
}
- boolean wasMyOffer(Tradable tradable) {
- return closedTradableManager.wasMyOffer(tradable.getOffer());
- }
-
Coin getTotalAmount() {
- return Coin.valueOf(getList().stream()
- .map(ClosedTradableListItem::getTradable)
- .filter(e -> e instanceof Trade)
- .map(e -> (Trade) e)
- .mapToLong(Trade::getTradeAmountAsLong)
- .sum());
+ return closedTradeUtil.getTotalAmount(tradableList.get());
}
Map getTotalVolumeByCurrency() {
- Map map = new HashMap<>();
- getList().stream()
- .map(ClosedTradableListItem::getTradable)
- .filter(e -> e instanceof Trade)
- .map(e -> (Trade) e)
- .map(Trade::getTradeVolume)
- .filter(Objects::nonNull)
- .forEach(volume -> {
- String currencyCode = volume.getCurrencyCode();
- map.putIfAbsent(currencyCode, 0L);
- map.put(currencyCode, volume.getValue() + map.get(currencyCode));
- });
- return map;
+ return closedTradeUtil.getTotalVolumeByCurrency(tradableList.get());
}
public Optional getVolumeInUserFiatCurrency(Coin amount) {
@@ -145,73 +122,21 @@ public Optional getVolume(Coin amount, String currencyCode) {
return Optional.empty();
}
+ // TODO Move PriceUtil & it's validators to core.util & core.util.validation
+ // before refactoring this.getVolume() to ClosedTradeUtil.
Price price = PriceUtil.marketPriceToPrice(marketPrice);
return Optional.of(VolumeUtil.getVolume(amount, price));
}
public Volume getBsqVolumeInUsdWithAveragePrice(Coin amount) {
- Tuple2 tuple = AveragePriceUtil.getAveragePriceTuple(preferences, tradeStatisticsManager, 30);
- Price usdPrice = tuple.first;
- long value = Math.round(amount.value * usdPrice.getValue() / 100d);
- return new Volume(Fiat.valueOf("USD", value));
+ return closedTradeUtil.getBsqVolumeInUsdWithAveragePrice(amount);
}
public Coin getTotalTxFee() {
- return Coin.valueOf(getList().stream()
- .map(ClosedTradableListItem::getTradable)
- .mapToLong(tradable -> {
- if (wasMyOffer(tradable) || tradable instanceof OpenOffer) {
- return tradable.getOffer().getTxFee().value;
- } else {
- // taker pays for 3 transactions
- return ((Trade) tradable).getTxFee().multiply(3).value;
- }
- })
- .sum());
+ return closedTradeUtil.getTotalTxFee(tradableList.get());
}
public Coin getTotalTradeFee(boolean expectBtcFee) {
- return Coin.valueOf(getList().stream()
- .map(ClosedTradableListItem::getTradable)
- .mapToLong(tradable -> getTradeFee(tradable, expectBtcFee))
- .sum());
- }
-
- protected long getTradeFee(Tradable tradable, boolean expectBtcFee) {
- Offer offer = tradable.getOffer();
- if (wasMyOffer(tradable) || tradable instanceof OpenOffer) {
- String makerFeeTxId = offer.getOfferFeePaymentTxId();
- boolean notInBsqWallet = bsqWalletService.getTransaction(makerFeeTxId) == null;
- if (expectBtcFee) {
- if (notInBsqWallet) {
- return offer.getMakerFee().value;
- } else {
- return 0;
- }
- } else {
- if (notInBsqWallet) {
- return 0;
- } else {
- return offer.getMakerFee().value;
- }
- }
- } else {
- Trade trade = (Trade) tradable;
- String takerFeeTxId = trade.getTakerFeeTxId();
- boolean notInBsqWallet = bsqWalletService.getTransaction(takerFeeTxId) == null;
- if (expectBtcFee) {
- if (notInBsqWallet) {
- return trade.getTakerFee().value;
- } else {
- return 0;
- }
- } else {
- if (notInBsqWallet) {
- return 0;
- } else {
- return trade.getTakerFee().value;
- }
- }
- }
+ return closedTradeUtil.getTotalTradeFee(tradableList.get(), expectBtcFee);
}
}
diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java
index b782e65c8cb..9b359cbaad5 100644
--- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java
+++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java
@@ -22,52 +22,29 @@
import bisq.desktop.util.DisplayUtils;
import bisq.core.account.witness.AccountAgeWitnessService;
-import bisq.core.btc.wallet.BsqWalletService;
-import bisq.core.locale.CurrencyUtil;
-import bisq.core.locale.Res;
-import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Volume;
-import bisq.core.offer.Offer;
-import bisq.core.offer.OpenOffer;
import bisq.core.trade.Tradable;
-import bisq.core.trade.Trade;
-import bisq.core.util.FormattingUtils;
-import bisq.core.util.VolumeUtil;
-import bisq.core.util.coin.BsqFormatter;
-import bisq.core.util.coin.CoinFormatter;
-
-import bisq.network.p2p.NodeAddress;
+import bisq.core.trade.closed.ClosedTradeUtil;
import org.bitcoinj.core.Coin;
-import org.bitcoinj.core.Monetary;
-import org.bitcoinj.utils.Fiat;
import com.google.inject.Inject;
-import javax.inject.Named;
-
import javafx.collections.ObservableList;
import java.util.Map;
-import java.util.stream.Collectors;
public class ClosedTradesViewModel extends ActivatableWithDataModel implements ViewModel {
- private final BsqWalletService bsqWalletService;
- private final BsqFormatter bsqFormatter;
- private final CoinFormatter btcFormatter;
+ private final ClosedTradeUtil closedTradeUtil;
final AccountAgeWitnessService accountAgeWitnessService;
@Inject
public ClosedTradesViewModel(ClosedTradesDataModel dataModel,
- AccountAgeWitnessService accountAgeWitnessService,
- BsqWalletService bsqWalletService,
- BsqFormatter bsqFormatter,
- @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
+ ClosedTradeUtil closedTradeUtil,
+ AccountAgeWitnessService accountAgeWitnessService) {
super(dataModel);
+ this.closedTradeUtil = closedTradeUtil;
this.accountAgeWitnessService = accountAgeWitnessService;
- this.bsqWalletService = bsqWalletService;
- this.bsqFormatter = bsqFormatter;
- this.btcFormatter = btcFormatter;
}
public ObservableList getList() {
@@ -79,132 +56,43 @@ String getTradeId(ClosedTradableListItem item) {
}
String getAmount(ClosedTradableListItem item) {
- if (item != null && item.getTradable() instanceof Trade)
- return btcFormatter.formatCoin(((Trade) item.getTradable()).getTradeAmount());
- else
- return "";
+ return item != null ? closedTradeUtil.getAmountAsString(item.getTradable()) : "";
}
String getPrice(ClosedTradableListItem item) {
- if (item == null)
- return "";
- Tradable tradable = item.getTradable();
- if (tradable instanceof Trade)
- return FormattingUtils.formatPrice(((Trade) tradable).getTradePrice());
- else
- return FormattingUtils.formatPrice(tradable.getOffer().getPrice());
+ return item != null ? closedTradeUtil.getPriceAsString(item.getTradable()) : "";
}
String getPriceDeviation(ClosedTradableListItem item) {
- if (item == null)
- return "";
- Tradable tradable = item.getTradable();
- if (tradable.getOffer().isUseMarketBasedPrice()) {
- return FormattingUtils.formatPercentagePrice(tradable.getOffer().getMarketPriceMargin());
- } else {
- return Res.get("shared.na");
- }
+ return item != null ? closedTradeUtil.getPriceDeviationAsString(item.getTradable()) : "";
}
String getVolume(ClosedTradableListItem item, boolean appendCode) {
- if (item == null) {
- return "";
- }
-
- if (item.getTradable() instanceof OpenOffer) {
- return "";
- }
-
- Trade trade = (Trade) item.getTradable();
- return VolumeUtil.formatVolume(trade.getTradeVolume(), appendCode);
+ return item != null ? closedTradeUtil.getVolumeAsString(item.getTradable(), appendCode) : "";
}
String getVolumeCurrency(ClosedTradableListItem item) {
- if (item == null) {
- return "";
- }
- Volume volume;
- if (item.getTradable() instanceof OpenOffer) {
- OpenOffer openOffer = (OpenOffer) item.getTradable();
- volume = openOffer.getOffer().getVolume();
- } else {
- Trade trade = (Trade) item.getTradable();
- volume = trade.getTradeVolume();
- }
- return volume != null ? volume.getCurrencyCode() : "";
+ return item != null ? closedTradeUtil.getVolumeCurrencyAsString(item.getTradable()) : "";
}
String getTxFee(ClosedTradableListItem item) {
- if (item == null)
- return "";
- Tradable tradable = item.getTradable();
- if (!wasMyOffer(tradable) && (tradable instanceof Trade)) {
- // taker pays for 3 transactions
- return btcFormatter.formatCoin(((Trade) tradable).getTxFee().multiply(3));
- } else {
- return btcFormatter.formatCoin(tradable.getOffer().getTxFee());
- }
+ return item != null ? closedTradeUtil.getTxFeeAsString(item.getTradable()) : "";
}
boolean isCurrencyForTradeFeeBtc(ClosedTradableListItem item) {
- if (item == null) {
- return false;
- }
-
- Tradable tradable = item.getTradable();
- Offer offer = tradable.getOffer();
- if (wasMyOffer(tradable) || tradable instanceof OpenOffer) {
- // I was maker so we use offer
- return offer.isCurrencyForMakerFeeBtc();
- } else {
- Trade trade = (Trade) tradable;
- String takerFeeTxId = trade.getTakerFeeTxId();
- // If we find our tx in the bsq wallet its a BSQ trade fee tx
- return bsqWalletService.getTransaction(takerFeeTxId) == null;
- }
+ return item != null ? closedTradeUtil.isCurrencyForTradeFeeBtc(item.getTradable()) : false;
}
String getTradeFee(ClosedTradableListItem item, boolean appendCode) {
- if (item == null) {
- return "";
- }
-
- Tradable tradable = item.getTradable();
- Offer offer = tradable.getOffer();
- if (wasMyOffer(tradable) || tradable instanceof OpenOffer) {
- CoinFormatter formatter = offer.isCurrencyForMakerFeeBtc() ? btcFormatter : bsqFormatter;
- return formatter.formatCoin(offer.getMakerFee(), appendCode);
- } else {
- Trade trade = (Trade) tradable;
- String takerFeeTxId = trade.getTakerFeeTxId();
- if (bsqWalletService.getTransaction(takerFeeTxId) == null) {
- // Was BTC fee
- return btcFormatter.formatCoin(trade.getTakerFee(), appendCode);
- } else {
- // BSQ fee
- return bsqFormatter.formatCoin(trade.getTakerFee(), appendCode);
- }
- }
+ return item != null ? closedTradeUtil.getTradeFeeAsString(item.getTradable(), appendCode) : "";
}
String getBuyerSecurityDeposit(ClosedTradableListItem item) {
- if (item == null)
- return "";
- Tradable tradable = item.getTradable();
- if (tradable.getOffer() != null)
- return btcFormatter.formatCoin(tradable.getOffer().getBuyerSecurityDeposit());
- else
- return "";
+ return item != null ? closedTradeUtil.getBuyerSecurityDepositAsString(item.getTradable()) : "";
}
String getSellerSecurityDeposit(ClosedTradableListItem item) {
- if (item == null)
- return "";
- Tradable tradable = item.getTradable();
- if (tradable.getOffer() != null)
- return btcFormatter.formatCoin(tradable.getOffer().getSellerSecurityDeposit());
- else
- return "";
+ return item != null ? closedTradeUtil.getSellerSecurityDepositAsString(item.getTradable()) : "";
}
String getDirectionLabel(ClosedTradableListItem item) {
@@ -216,69 +104,15 @@ String getDate(ClosedTradableListItem item) {
}
String getMarketLabel(ClosedTradableListItem item) {
- if ((item == null))
- return "";
-
- return CurrencyUtil.getCurrencyPair(item.getTradable().getOffer().getCurrencyCode());
+ return item != null ? closedTradeUtil.getMarketLabel(item.getTradable()) : "";
}
String getState(ClosedTradableListItem item) {
- if (item != null) {
- if (item.getTradable() instanceof Trade) {
- Trade trade = (Trade) item.getTradable();
-
- if (trade.isWithdrawn() || trade.isPayoutPublished()) {
- return Res.get("portfolio.closed.completed");
- } else if (trade.getDisputeState() == Trade.DisputeState.DISPUTE_CLOSED) {
- return Res.get("portfolio.closed.ticketClosed");
- } else if (trade.getDisputeState() == Trade.DisputeState.MEDIATION_CLOSED) {
- return Res.get("portfolio.closed.mediationTicketClosed");
- } else if (trade.getDisputeState() == Trade.DisputeState.REFUND_REQUEST_CLOSED) {
- return Res.get("portfolio.closed.ticketClosed");
- } else {
- log.error("That must not happen. We got a pending state but we are in the closed trades list. state={}", trade.getState().toString());
- return Res.get("shared.na");
- }
- } else if (item.getTradable() instanceof OpenOffer) {
- OpenOffer.State state = ((OpenOffer) item.getTradable()).getState();
- log.trace("OpenOffer state {}", state);
- switch (state) {
- case AVAILABLE:
- case RESERVED:
- case CLOSED:
- log.error("Invalid state {}", state);
- return state.toString();
- case CANCELED:
- return Res.get("portfolio.closed.canceled");
- case DEACTIVATED:
- log.error("Invalid state {}", state);
- return state.toString();
- default:
- log.error("Unhandled state {}", state);
- return state.toString();
- }
- }
- }
- return "";
+ return item != null ? closedTradeUtil.getStateAsString(item.getTradable()) : "";
}
int getNumPastTrades(Tradable tradable) {
- return dataModel.closedTradableManager.getObservableList().stream()
- .filter(candidate -> {
- if (!(candidate instanceof Trade) ||
- !(tradable instanceof Trade)) return false;
- NodeAddress candidateAddress = ((Trade) candidate).getTradingPeerNodeAddress();
- NodeAddress tradableAddress = ((Trade) tradable).getTradingPeerNodeAddress();
- return candidateAddress != null &&
- tradableAddress != null &&
- candidateAddress.getFullAddress().equals(tradableAddress.getFullAddress());
- })
- .collect(Collectors.toSet())
- .size();
- }
-
- boolean wasMyOffer(Tradable tradable) {
- return dataModel.wasMyOffer(tradable);
+ return closedTradeUtil.getNumPastTrades(tradable);
}
public Coin getTotalTradeAmount() {
@@ -287,44 +121,22 @@ public Coin getTotalTradeAmount() {
public String getTotalAmountWithVolume(Coin totalTradeAmount) {
return dataModel.getVolumeInUserFiatCurrency(totalTradeAmount)
- .map(volume -> {
- return Res.get("closedTradesSummaryWindow.totalAmount.value",
- btcFormatter.formatCoin(totalTradeAmount, true),
- VolumeUtil.formatVolumeWithCode(volume));
- })
+ .map(volume -> closedTradeUtil.getTotalAmountWithVolumeAsString(totalTradeAmount, volume))
.orElse("");
}
public Map getTotalVolumeByCurrency() {
- return dataModel.getTotalVolumeByCurrency().entrySet().stream()
- .collect(Collectors.toMap(Map.Entry::getKey,
- entry -> {
- String currencyCode = entry.getKey();
- Monetary monetary;
- if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
- monetary = Altcoin.valueOf(currencyCode, entry.getValue());
- } else {
- monetary = Fiat.valueOf(currencyCode, entry.getValue());
- }
- return VolumeUtil.formatVolumeWithCode(new Volume(monetary));
- }
- ));
+ return closedTradeUtil.getTotalVolumeByCurrencyAsString(dataModel.tradableList.get());
}
public String getTotalTxFee(Coin totalTradeAmount) {
Coin totalTxFee = dataModel.getTotalTxFee();
- double percentage = ((double) totalTxFee.value) / totalTradeAmount.value;
- return Res.get("closedTradesSummaryWindow.totalMinerFee.value",
- btcFormatter.formatCoin(totalTxFee, true),
- FormattingUtils.formatToPercentWithSymbol(percentage));
+ return closedTradeUtil.getTotalTxFeeAsString(totalTradeAmount, totalTxFee);
}
public String getTotalTradeFeeInBtc(Coin totalTradeAmount) {
Coin totalTradeFee = dataModel.getTotalTradeFee(true);
- double percentage = ((double) totalTradeFee.value) / totalTradeAmount.value;
- return Res.get("closedTradesSummaryWindow.totalTradeFeeInBtc.value",
- btcFormatter.formatCoin(totalTradeFee, true),
- FormattingUtils.formatToPercentWithSymbol(percentage));
+ return closedTradeUtil.getTotalTradeFeeInBtcAsString(totalTradeAmount, totalTradeFee);
}
public String getTotalTradeFeeInBsq(Coin totalTradeAmount) {
@@ -333,10 +145,9 @@ public String getTotalTradeFeeInBsq(Coin totalTradeAmount) {
.map(tradeAmountVolume -> {
Coin totalTradeFee = dataModel.getTotalTradeFee(false);
Volume bsqVolumeInUsd = dataModel.getBsqVolumeInUsdWithAveragePrice(totalTradeFee); // with 4 decimal
- double percentage = ((double) bsqVolumeInUsd.getValue()) / tradeAmountVolume.getValue();
- return Res.get("closedTradesSummaryWindow.totalTradeFeeInBsq.value",
- bsqFormatter.formatCoin(totalTradeFee, true),
- FormattingUtils.formatToPercentWithSymbol(percentage));
+ return closedTradeUtil.getTotalTradeFeeInBsqAsString(totalTradeFee,
+ tradeAmountVolume,
+ bsqVolumeInUsd);
})
.orElse("");
}