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(""); }