mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 06:55:08 +01:00
Merge pull request #5315 from chimp1984/improve-portfolio-history
Improve portfolio history
This commit is contained in:
commit
64ab053b1b
13 changed files with 443 additions and 68 deletions
|
@ -17,8 +17,15 @@
|
|||
|
||||
package bisq.core.util;
|
||||
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.AltcoinExchangeRate;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.ExchangeRate;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
public class VolumeUtil {
|
||||
|
||||
public static Volume getRoundedFiatVolume(Volume volumeByAmount) {
|
||||
|
@ -47,4 +54,12 @@ public class VolumeUtil {
|
|||
roundedVolume = Math.max(factor, roundedVolume);
|
||||
return Volume.parse(String.valueOf(roundedVolume), volumeByAmount.getCurrencyCode());
|
||||
}
|
||||
|
||||
public static Volume getVolume(Coin amount, Price price) {
|
||||
if (price.getMonetary() instanceof Altcoin) {
|
||||
return new Volume(new AltcoinExchangeRate((Altcoin) price.getMonetary()).coinToAltcoin(amount));
|
||||
} else {
|
||||
return new Volume(new ExchangeRate((Fiat) price.getMonetary()).coinToFiat(amount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,6 @@ import java.util.Locale;
|
|||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class BsqFormatter implements CoinFormatter {
|
||||
|
@ -224,10 +222,15 @@ public class BsqFormatter implements CoinFormatter {
|
|||
}
|
||||
|
||||
public String formatCoin(Coin coin) {
|
||||
return immutableCoinFormatter.formatCoin(coin);
|
||||
return formatCoin(coin, false);
|
||||
}
|
||||
|
||||
public String formatCoin(Coin coin, boolean appendCode) {
|
||||
return appendCode ?
|
||||
immutableCoinFormatter.formatCoinWithCode(coin) :
|
||||
immutableCoinFormatter.formatCoin(coin);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String formatCoin(Coin coin, int decimalPlaces) {
|
||||
return immutableCoinFormatter.formatCoin(coin, decimalPlaces);
|
||||
}
|
||||
|
@ -240,7 +243,7 @@ public class BsqFormatter implements CoinFormatter {
|
|||
}
|
||||
|
||||
public String formatCoinWithCode(Coin coin) {
|
||||
return immutableCoinFormatter.formatCoinWithCode(coin);
|
||||
return formatCoin(coin, true);
|
||||
}
|
||||
|
||||
public String formatCoinWithCode(long value) {
|
||||
|
|
|
@ -2,12 +2,11 @@ package bisq.core.util.coin;
|
|||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface CoinFormatter {
|
||||
String formatCoin(Coin coin);
|
||||
|
||||
@NotNull
|
||||
String formatCoin(Coin coin, boolean appendCode);
|
||||
|
||||
String formatCoin(Coin coin, int decimalPlaces);
|
||||
|
||||
String formatCoin(Coin coin, int decimalPlaces, boolean decimalAligned, int maxNumberOfDigits);
|
||||
|
|
|
@ -27,8 +27,6 @@ import javax.inject.Inject;
|
|||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Slf4j
|
||||
public class ImmutableCoinFormatter implements CoinFormatter {
|
||||
|
||||
|
@ -56,7 +54,11 @@ public class ImmutableCoinFormatter implements CoinFormatter {
|
|||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String formatCoin(Coin coin, boolean appendCode) {
|
||||
return appendCode ? formatCoinWithCode(coin) : formatCoin(coin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatCoin(Coin coin, int decimalPlaces) {
|
||||
return formatCoin(coin, decimalPlaces, false, 0);
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@ shared.sendingConfirmation=Sending confirmation...
|
|||
shared.sendingConfirmationAgain=Please send confirmation again
|
||||
shared.exportCSV=Export to CSV
|
||||
shared.exportJSON=Export to JSON
|
||||
shared.summary=Show summary
|
||||
shared.noDateAvailable=No date available
|
||||
shared.noDetailsAvailable=No details available
|
||||
shared.notUsedYet=Not used yet
|
||||
|
@ -2722,6 +2723,17 @@ txDetailsWindow.bsq.note=You have sent BSQ funds. \
|
|||
txDetailsWindow.sentTo=Sent to
|
||||
txDetailsWindow.txId=TxId
|
||||
|
||||
closedTradesSummaryWindow.headline=Trade history summary
|
||||
closedTradesSummaryWindow.totalAmount.title=Total trade amount
|
||||
closedTradesSummaryWindow.totalAmount.value={0} ({1} with current market price)
|
||||
closedTradesSummaryWindow.totalVolume.title=Total amount traded in {0}
|
||||
closedTradesSummaryWindow.totalMinerFee.title=Sum of all miner fees
|
||||
closedTradesSummaryWindow.totalMinerFee.value={0} ({1} of total trade amount)
|
||||
closedTradesSummaryWindow.totalTradeFeeInBtc.title=Sum of all trade fees paid in BTC
|
||||
closedTradesSummaryWindow.totalTradeFeeInBtc.value={0} ({1} of total trade amount)
|
||||
closedTradesSummaryWindow.totalTradeFeeInBsq.title=Sum of all trade fees paid in BSQ
|
||||
closedTradesSummaryWindow.totalTradeFeeInBsq.value={0} ({1} of total trade amount)
|
||||
|
||||
walletPasswordWindow.headline=Enter password to unlock
|
||||
|
||||
torNetworkSettingWindow.header=Tor networks settings
|
||||
|
|
|
@ -106,6 +106,17 @@ public class PriceUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static Price marketPriceToPrice(MarketPrice marketPrice) {
|
||||
String currencyCode = marketPrice.getCurrencyCode();
|
||||
double priceAsDouble = marketPrice.getPrice();
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
double scaled = MathUtils.scaleUpByPowerOf10(priceAsDouble, precision);
|
||||
long roundedToLong = MathUtils.roundDoubleToLong(scaled);
|
||||
return Price.valueOf(currencyCode, roundedToLong);
|
||||
}
|
||||
|
||||
public void recalculateBsq30DayAveragePrice() {
|
||||
bsq30DayAveragePrice = null;
|
||||
bsq30DayAveragePrice = getBsq30DayAveragePrice();
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.desktop.main.overlays.windows;
|
||||
|
||||
import bisq.desktop.main.overlays.Overlay;
|
||||
import bisq.desktop.main.portfolio.closedtrades.ClosedTradesViewModel;
|
||||
import bisq.desktop.util.Layout;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addConfirmationLabelLabel;
|
||||
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
|
||||
|
||||
public class ClosedTradesSummaryWindow extends Overlay<ClosedTradesSummaryWindow> {
|
||||
private final ClosedTradesViewModel model;
|
||||
|
||||
@Inject
|
||||
public ClosedTradesSummaryWindow(ClosedTradesViewModel model) {
|
||||
this.model = model;
|
||||
type = Type.Information;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
rowIndex = 0;
|
||||
width = 900;
|
||||
createGridPane();
|
||||
addContent();
|
||||
addButtons();
|
||||
display();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void createGridPane() {
|
||||
super.createGridPane();
|
||||
gridPane.setPadding(new Insets(35, 40, 30, 40));
|
||||
gridPane.getStyleClass().add("grid-pane");
|
||||
}
|
||||
|
||||
private void addContent() {
|
||||
Map<String, String> totalVolumeByCurrency = model.getTotalVolumeByCurrency();
|
||||
int rowSpan = totalVolumeByCurrency.size() + 4;
|
||||
addTitledGroupBg(gridPane, rowIndex, rowSpan, Res.get("closedTradesSummaryWindow.headline"));
|
||||
Coin totalTradeAmount = model.getTotalTradeAmount();
|
||||
addConfirmationLabelLabel(gridPane, rowIndex,
|
||||
Res.get("closedTradesSummaryWindow.totalAmount.title"),
|
||||
model.getTotalAmountWithVolume(totalTradeAmount), Layout.TWICE_FIRST_ROW_DISTANCE);
|
||||
totalVolumeByCurrency.entrySet().forEach(entry -> {
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex,
|
||||
Res.get("closedTradesSummaryWindow.totalVolume.title", entry.getKey()), entry.getValue());
|
||||
});
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex,
|
||||
Res.get("closedTradesSummaryWindow.totalMinerFee.title"),
|
||||
model.getTotalTxFee(totalTradeAmount));
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex,
|
||||
Res.get("closedTradesSummaryWindow.totalTradeFeeInBtc.title"),
|
||||
model.getTotalTradeFeeInBtc(totalTradeAmount));
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex,
|
||||
Res.get("closedTradesSummaryWindow.totalTradeFeeInBsq.title") + " ", // lets give some extra space
|
||||
model.getTotalTradeFeeInBsq(totalTradeAmount));
|
||||
}
|
||||
}
|
|
@ -228,7 +228,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
|||
|
||||
String txFee = Res.get("shared.makerTxFee", formatter.formatCoinWithCode(offer.getTxFee())) +
|
||||
" / " +
|
||||
Res.get("shared.takerTxFee", formatter.formatCoinWithCode(offer.getTxFee().multiply(3L)));
|
||||
Res.get("shared.takerTxFee", formatter.formatCoinWithCode(trade.getTxFee().multiply(3)));
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.txFee"), txFee);
|
||||
|
||||
NodeAddress arbitratorNodeAddress = trade.getArbitratorNodeAddress();
|
||||
|
|
|
@ -18,11 +18,27 @@
|
|||
package bisq.desktop.main.portfolio.closedtrades;
|
||||
|
||||
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.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.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;
|
||||
|
||||
|
@ -30,17 +46,33 @@ import javafx.collections.FXCollections;
|
|||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class ClosedTradesDataModel extends ActivatableDataModel {
|
||||
|
||||
final ClosedTradableManager closedTradableManager;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final Preferences preferences;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final ObservableList<ClosedTradableListItem> list = FXCollections.observableArrayList();
|
||||
private final ListChangeListener<Tradable> tradesListChangeListener;
|
||||
|
||||
@Inject
|
||||
public ClosedTradesDataModel(ClosedTradableManager closedTradableManager) {
|
||||
public ClosedTradesDataModel(ClosedTradableManager closedTradableManager,
|
||||
BsqWalletService bsqWalletService,
|
||||
Preferences preferences,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
PriceFeedService priceFeedService) {
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.preferences = preferences;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.priceFeedService = priceFeedService;
|
||||
|
||||
tradesListChangeListener = change -> applyList();
|
||||
}
|
||||
|
@ -73,4 +105,112 @@ class ClosedTradesDataModel extends ActivatableDataModel {
|
|||
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());
|
||||
}
|
||||
|
||||
Map<String, Long> getTotalVolumeByCurrency() {
|
||||
Map<String, Long> 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;
|
||||
}
|
||||
|
||||
public Optional<Volume> getVolumeInUserFiatCurrency(Coin amount) {
|
||||
return getVolume(amount, preferences.getPreferredTradeCurrency().getCode());
|
||||
}
|
||||
|
||||
public Optional<Volume> getVolume(Coin amount, String currencyCode) {
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
if (marketPrice == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Price price = PriceUtil.marketPriceToPrice(marketPrice);
|
||||
return Optional.of(VolumeUtil.getVolume(amount, price));
|
||||
}
|
||||
|
||||
public Volume getBsqVolumeInUsdWithAveragePrice(Coin amount) {
|
||||
Tuple2<Price, Price> 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));
|
||||
}
|
||||
|
||||
public Coin getTotalTxFee() {
|
||||
return Coin.valueOf(getList().stream()
|
||||
.map(ClosedTradableListItem::getTradable)
|
||||
.mapToLong(tradable -> {
|
||||
if (wasMyOffer(tradable)) {
|
||||
return tradable.getOffer().getTxFee().value;
|
||||
} else {
|
||||
// taker pays for 3 transactions
|
||||
return ((Trade) tradable).getTxFee().multiply(3).value;
|
||||
}
|
||||
})
|
||||
.sum());
|
||||
}
|
||||
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
<HBox spacing="10">
|
||||
<Label fx:id="numItems"/>
|
||||
<Region fx:id="footerSpacer"/>
|
||||
<AutoTooltipButton fx:id="summaryButton"/>
|
||||
<AutoTooltipButton fx:id="exportButton"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
|
|
|
@ -25,6 +25,7 @@ import bisq.desktop.components.AutoTooltipTableColumn;
|
|||
import bisq.desktop.components.HyperlinkWithIcon;
|
||||
import bisq.desktop.components.InputTextField;
|
||||
import bisq.desktop.components.PeerInfoIcon;
|
||||
import bisq.desktop.main.overlays.windows.ClosedTradesSummaryWindow;
|
||||
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
||||
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
@ -68,7 +69,6 @@ import javafx.geometry.Insets;
|
|||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
|
||||
|
@ -89,8 +89,10 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
DEVIATION(Res.get("shared.deviation")),
|
||||
AMOUNT(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode())),
|
||||
VOLUME(Res.get("shared.amount")),
|
||||
VOLUME_CURRENCY(Res.get("shared.currency")),
|
||||
TX_FEE(Res.get("shared.txFee")),
|
||||
TRADE_FEE(Res.get("shared.tradeFee")),
|
||||
TRADE_FEE_BTC(Res.get("shared.tradeFee") + " BTC"),
|
||||
TRADE_FEE_BSQ(Res.get("shared.tradeFee") + " BSQ"),
|
||||
BUYER_SEC(Res.get("shared.buyerSecurityDeposit")),
|
||||
SELLER_SEC(Res.get("shared.sellerSecurityDeposit")),
|
||||
OFFER_TYPE(Res.get("shared.offerType")),
|
||||
|
@ -123,7 +125,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
@FXML
|
||||
Pane searchBoxSpacer;
|
||||
@FXML
|
||||
AutoTooltipButton exportButton;
|
||||
AutoTooltipButton exportButton, summaryButton;
|
||||
@FXML
|
||||
Label numItems;
|
||||
@FXML
|
||||
|
@ -157,7 +159,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
public void initialize() {
|
||||
widthListener = (observable, oldValue, newValue) -> onWidthChange((double) newValue);
|
||||
txFeeColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TX_FEE.toString()));
|
||||
tradeFeeColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TRADE_FEE.toString()));
|
||||
tradeFeeColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TRADE_FEE_BTC.toString().replace(" BTC", "")));
|
||||
buyerSecurityDepositColumn.setGraphic(new AutoTooltipLabel(ColumnNames.BUYER_SEC.toString()));
|
||||
sellerSecurityDepositColumn.setGraphic(new AutoTooltipLabel(ColumnNames.SELLER_SEC.toString()));
|
||||
priceColumn.setGraphic(new AutoTooltipLabel(ColumnNames.PRICE.toString()));
|
||||
|
@ -211,7 +213,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
|
||||
//
|
||||
tradeFeeColumn.setComparator(Comparator.comparing(item -> {
|
||||
String tradeFee = model.getTradeFee(item);
|
||||
String tradeFee = model.getTradeFee(item, true);
|
||||
// We want to separate BSQ and BTC fees so we use a prefix
|
||||
if (item.getTradable().getOffer().isCurrencyForMakerFeeBtc()) {
|
||||
return "BTC" + tradeFee;
|
||||
|
@ -241,6 +243,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
HBox.setHgrow(footerSpacer, Priority.ALWAYS);
|
||||
HBox.setMargin(exportButton, new Insets(0, 10, 0, 0));
|
||||
exportButton.updateText(Res.get("shared.exportCSV"));
|
||||
summaryButton.updateText(Res.get("shared.summary"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -254,7 +257,6 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
|
||||
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
|
||||
exportButton.setOnAction(event -> {
|
||||
final ObservableList<TableColumn<ClosedTradableListItem, ?>> tableColumns = tableView.getColumns();
|
||||
CSVEntryConverter<ClosedTradableListItem> headerConverter = item -> {
|
||||
String[] columns = new String[ColumnNames.values().length];
|
||||
for (ColumnNames m : ColumnNames.values()) {
|
||||
|
@ -270,9 +272,16 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
columns[ColumnNames.PRICE.ordinal()] = model.getPrice(item);
|
||||
columns[ColumnNames.DEVIATION.ordinal()] = model.getPriceDeviation(item);
|
||||
columns[ColumnNames.AMOUNT.ordinal()] = model.getAmount(item);
|
||||
columns[ColumnNames.VOLUME.ordinal()] = model.getVolume(item);
|
||||
columns[ColumnNames.VOLUME.ordinal()] = model.getVolume(item, false);
|
||||
columns[ColumnNames.VOLUME_CURRENCY.ordinal()] = model.getVolumeCurrency(item);
|
||||
columns[ColumnNames.TX_FEE.ordinal()] = model.getTxFee(item);
|
||||
columns[ColumnNames.TRADE_FEE.ordinal()] = model.getTradeFee(item);
|
||||
if (model.isCurrencyForTradeFeeBtc(item)) {
|
||||
columns[ColumnNames.TRADE_FEE_BTC.ordinal()] = model.getTradeFee(item, false);
|
||||
columns[ColumnNames.TRADE_FEE_BSQ.ordinal()] = "";
|
||||
} else {
|
||||
columns[ColumnNames.TRADE_FEE_BTC.ordinal()] = "";
|
||||
columns[ColumnNames.TRADE_FEE_BSQ.ordinal()] = model.getTradeFee(item, false);
|
||||
}
|
||||
columns[ColumnNames.BUYER_SEC.ordinal()] = model.getBuyerSecurityDeposit(item);
|
||||
columns[ColumnNames.SELLER_SEC.ordinal()] = model.getSellerSecurityDeposit(item);
|
||||
columns[ColumnNames.OFFER_TYPE.ordinal()] = model.getDirectionLabel(item);
|
||||
|
@ -284,6 +293,8 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
new ClosedTradableListItem(null), sortedList, (Stage) root.getScene().getWindow());
|
||||
});
|
||||
|
||||
summaryButton.setOnAction(event -> new ClosedTradesSummaryWindow(model).show());
|
||||
|
||||
filterTextField.textProperty().addListener(filterTextFieldListener);
|
||||
applyFilteredListPredicate(filterTextField.getText());
|
||||
root.widthProperty().addListener(widthListener);
|
||||
|
@ -294,6 +305,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
protected void deactivate() {
|
||||
sortedList.comparatorProperty().unbind();
|
||||
exportButton.setOnAction(null);
|
||||
summaryButton.setOnAction(null);
|
||||
|
||||
filterTextField.textProperty().removeListener(filterTextFieldListener);
|
||||
root.widthProperty().removeListener(widthListener);
|
||||
|
@ -343,13 +355,13 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
return true;
|
||||
}
|
||||
|
||||
if (model.getVolume(item).contains(filterString)) {
|
||||
if (model.getVolume(item, true).contains(filterString)) {
|
||||
return true;
|
||||
}
|
||||
if (model.getAmount(item).contains(filterString)) {
|
||||
return true;
|
||||
}
|
||||
if (model.getTradeFee(item).contains(filterString)) {
|
||||
if (model.getTradeFee(item, true).contains(filterString)) {
|
||||
return true;
|
||||
}
|
||||
if (model.getTxFee(item).contains(filterString)) {
|
||||
|
@ -607,7 +619,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
public void updateItem(final ClosedTradableListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setGraphic(new AutoTooltipLabel(model.getVolume(item)));
|
||||
setGraphic(new AutoTooltipLabel(model.getVolume(item, true)));
|
||||
else
|
||||
setGraphic(null);
|
||||
}
|
||||
|
@ -663,7 +675,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
|||
@Override
|
||||
public void updateItem(final ClosedTradableListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
setGraphic(new AutoTooltipLabel(model.getTradeFee(item)));
|
||||
setGraphic(new AutoTooltipLabel(model.getTradeFee(item, true)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,9 +23,10 @@ import bisq.desktop.util.DisplayUtils;
|
|||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
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;
|
||||
|
@ -36,11 +37,9 @@ import bisq.core.util.coin.CoinFormatter;
|
|||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Monetary;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
@ -48,10 +47,10 @@ import javax.inject.Named;
|
|||
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataModel> implements ViewModel {
|
||||
private final BtcWalletService btcWalletService;
|
||||
public class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataModel> implements ViewModel {
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
private final CoinFormatter btcFormatter;
|
||||
|
@ -60,13 +59,11 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
|
|||
@Inject
|
||||
public ClosedTradesViewModel(ClosedTradesDataModel dataModel,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
BtcWalletService btcWalletService,
|
||||
BsqWalletService bsqWalletService,
|
||||
BsqFormatter bsqFormatter,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
|
||||
super(dataModel);
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
this.btcFormatter = btcFormatter;
|
||||
|
@ -83,8 +80,6 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
|
|||
String getAmount(ClosedTradableListItem item) {
|
||||
if (item != null && item.getTradable() instanceof Trade)
|
||||
return btcFormatter.formatCoin(((Trade) item.getTradable()).getTradeAmount());
|
||||
else if (item != null && item.getTradable() instanceof OpenOffer)
|
||||
return "-";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
@ -110,52 +105,84 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
|
|||
}
|
||||
}
|
||||
|
||||
String getVolume(ClosedTradableListItem item) {
|
||||
if (item != null && item.getTradable() instanceof Trade)
|
||||
return DisplayUtils.formatVolumeWithCode(((Trade) item.getTradable()).getTradeVolume());
|
||||
else if (item != null && item.getTradable() instanceof OpenOffer)
|
||||
return "-";
|
||||
else
|
||||
String getVolume(ClosedTradableListItem item, boolean appendCode) {
|
||||
if (item == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (item.getTradable() instanceof OpenOffer) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Trade trade = (Trade) item.getTradable();
|
||||
return DisplayUtils.formatVolume(trade.getTradeVolume(), 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() : "";
|
||||
}
|
||||
|
||||
String getTxFee(ClosedTradableListItem item) {
|
||||
if (item == null)
|
||||
return "";
|
||||
Tradable tradable = item.getTradable();
|
||||
if (!wasMyOffer(tradable) && (tradable instanceof Trade))
|
||||
return btcFormatter.formatCoin(((Trade) tradable).getTxFee());
|
||||
else
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
String getTradeFee(ClosedTradableListItem item) {
|
||||
boolean isCurrencyForTradeFeeBtc(ClosedTradableListItem item) {
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Tradable tradable = item.getTradable();
|
||||
Offer offer = tradable.getOffer();
|
||||
if (wasMyOffer(tradable)) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
String getTradeFee(ClosedTradableListItem item, boolean appendCode) {
|
||||
if (item == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Tradable tradable = item.getTradable();
|
||||
Offer offer = tradable.getOffer();
|
||||
|
||||
if (!wasMyOffer(tradable) && (tradable instanceof Trade)) {
|
||||
Trade trade = (Trade) tradable;
|
||||
Transaction takerFeeTx = btcWalletService.getTransaction(trade.getTakerFeeTxId());
|
||||
if (takerFeeTx != null && takerFeeTx.getOutputs().size() > 1) {
|
||||
// First output is fee receiver address. If its a BSQ (change) address of our own wallet its a BSQ fee
|
||||
TransactionOutput output = takerFeeTx.getOutput(0);
|
||||
Address address = output.getScriptPubKey().getToAddress(Config.baseCurrencyNetworkParameters());
|
||||
if (bsqWalletService.getWallet().findKeyFromAddress(address) != null) {
|
||||
return bsqFormatter.formatCoinWithCode(trade.getTakerFee());
|
||||
} else {
|
||||
return btcFormatter.formatCoinWithCode(trade.getTakerFee());
|
||||
}
|
||||
} else {
|
||||
log.warn("takerFeeTx is null or has invalid structure. takerFeeTx={}", takerFeeTx);
|
||||
return Res.get("shared.na");
|
||||
}
|
||||
} else {
|
||||
if (wasMyOffer(tradable)) {
|
||||
CoinFormatter formatter = offer.isCurrencyForMakerFeeBtc() ? btcFormatter : bsqFormatter;
|
||||
return formatter.formatCoinWithCode(offer.getMakerFee());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,6 +277,66 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
|
|||
}
|
||||
|
||||
boolean wasMyOffer(Tradable tradable) {
|
||||
return dataModel.closedTradableManager.wasMyOffer(tradable.getOffer());
|
||||
return dataModel.wasMyOffer(tradable);
|
||||
}
|
||||
|
||||
public Coin getTotalTradeAmount() {
|
||||
return dataModel.getTotalAmount();
|
||||
}
|
||||
|
||||
public String getTotalAmountWithVolume(Coin totalTradeAmount) {
|
||||
return dataModel.getVolumeInUserFiatCurrency(totalTradeAmount)
|
||||
.map(volume -> {
|
||||
return Res.get("closedTradesSummaryWindow.totalAmount.value",
|
||||
btcFormatter.formatCoin(totalTradeAmount, true),
|
||||
DisplayUtils.formatVolumeWithCode(volume));
|
||||
})
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public Map<String, String> 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 DisplayUtils.formatVolumeWithCode(new Volume(monetary));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public String getTotalTradeFeeInBsq(Coin totalTradeAmount) {
|
||||
return dataModel.getVolume(totalTradeAmount, "USD")
|
||||
.filter(v -> v.getValue() > 0)
|
||||
.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));
|
||||
})
|
||||
.orElse("");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,11 @@ public class DisplayUtils {
|
|||
}
|
||||
|
||||
public static String formatVolumeWithCode(Volume volume) {
|
||||
return formatVolume(volume, FIAT_VOLUME_FORMAT, true);
|
||||
return formatVolume(volume, true);
|
||||
}
|
||||
|
||||
public static String formatVolume(Volume volume, boolean appendCode) {
|
||||
return formatVolume(volume, FIAT_VOLUME_FORMAT, appendCode);
|
||||
}
|
||||
|
||||
public static String formatAverageVolumeWithCode(Volume volume) {
|
||||
|
|
Loading…
Add table
Reference in a new issue