diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4fe68119f48..bf5c37efe2f 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -303,7 +303,7 @@ market.spread.spreadColumn=Spread # TradesChartsView market.trades.nrOfTrades=Trades: {0} -market.trades.tooltip.volumeBar=Volume: {0}\nNo. of trades: {1}\nDate: {2} +market.trades.tooltip.volumeBar=Volume: {0}\nNo. of trades: {1}\nBisq Burn Ratio: {2}%\nDate: {3} market.trades.tooltip.candle.open=Open: market.trades.tooltip.candle.close=Close: market.trades.tooltip.candle.high=High: @@ -1784,6 +1784,7 @@ dao.proofOfBurn.verify=Verify dao.proofOfBurn.verify.header=Verify message with key from proof of burn transaction dao.proofOfBurn.verificationResult.ok=Verification succeeded dao.proofOfBurn.verificationResult.failed=Verification failed +dao.proofOfBurn.volumeRatio=Bisq Burn/Traded {0} Volume # suppress inspection "UnusedProperty" dao.phase.UNDEFINED=Undefined diff --git a/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java b/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java index bc8760f4f34..ce33e08c998 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java +++ b/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java @@ -51,6 +51,7 @@ import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; +import javafx.scene.chart.XYChart.Series; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.SingleSelectionModel; @@ -108,6 +109,7 @@ public class TradesChartsView extends ActivatableViewAndModel priceSeries; private XYChart.Series volumeSeries; + private XYChart.Series bisqBurnSeries; private ChangeListener priceAxisYWidthListener; private ChangeListener volumeAxisYWidthListener; private double priceAxisYWidth; @@ -327,6 +329,7 @@ private CurrencyListItem specialShowAllItem() { // Chart /////////////////////////////////////////////////////////////////////////////////////////// + @SuppressWarnings("unchecked") private void createCharts() { priceSeries = new XYChart.Series<>(); @@ -394,6 +397,10 @@ public Number fromString(String string) { priceChartPane.getChildren().add(priceChart); volumeSeries = new XYChart.Series<>(); + volumeSeries.setName(Res.get("shared.volumeWithCur", model.getCurrencyCode())); + + bisqBurnSeries = new XYChart.Series<>(); + bisqBurnSeries.setName(Res.get("dao.proofOfBurn.volumeRatio", model.getCurrencyCode())); volumeAxisX = new NumberAxis(0, model.maxTicks + 1, 1); volumeAxisX.setTickUnit(1); @@ -430,11 +437,12 @@ public Number fromString(String string) { }); //noinspection unchecked volumeChart.setId("volume-chart"); - volumeChart.setData(FXCollections.observableArrayList(volumeSeries)); + //TODO bisqBurnSeries should only be added when model.getCurrencyCode() == "BSQ" ??? + volumeChart.setData(FXCollections.observableArrayList(volumeSeries, bisqBurnSeries)); volumeChart.setMinHeight(128); volumeChart.setPrefHeight(128); volumeChart.setMaxHeight(200); - volumeChart.setLegendVisible(false); + volumeChart.setLegendVisible(true); volumeChart.setPadding(new Insets(0)); volumeChartPane = new AnchorPane(); @@ -450,6 +458,7 @@ public Number fromString(String string) { private void updateChartData() { volumeSeries.getData().setAll(model.volumeItems); + bisqBurnSeries.getData().setAll(model.burntBsqRatioItems); // At price chart we need to set the priceSeries new otherwise the lines are not rendered correctly // TODO should be fixed in candle chart diff --git a/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsViewModel.java b/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsViewModel.java index 6d6af50a825..f0235c1c703 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsViewModel.java @@ -27,7 +27,8 @@ import bisq.desktop.util.CurrencyListItem; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; - +import bisq.core.dao.state.DaoStateService; +import bisq.core.dao.state.model.blockchain.Tx; import bisq.core.locale.CryptoCurrency; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.GlobalSettings; @@ -38,7 +39,7 @@ import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.util.BSFormatter; - +import bisq.core.util.BsqFormatter; import bisq.common.util.MathUtils; import org.bitcoinj.core.Coin; @@ -60,6 +61,7 @@ import javafx.util.Pair; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -67,14 +69,19 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; import javax.annotation.Nullable; @@ -98,6 +105,8 @@ public enum TickUnit { private final TradeStatisticsManager tradeStatisticsManager; final Preferences preferences; private PriceFeedService priceFeedService; + private final DaoStateService daoStateService; + private final BsqFormatter bsqFormatter; private Navigation navigation; private BSFormatter formatter; @@ -107,9 +116,12 @@ public enum TickUnit { private final CurrencyList currencyListItems; private final CurrencyListItem showAllCurrencyListItem = new CurrencyListItem(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, ""), -1); final ObservableList tradeStatisticsByCurrency = FXCollections.observableArrayList(); + private final ObservableList burntBsqStatistics = FXCollections.observableArrayList(); final ObservableList> priceItems = FXCollections.observableArrayList(); final ObservableList> volumeItems = FXCollections.observableArrayList(); + final ObservableList> burntBsqRatioItems = FXCollections.observableArrayList(); private Map>> itemsPerInterval; + private Map>> burntBsqPerInterval; TickUnit tickUnit = TickUnit.DAY; final int maxTicks = 90; @@ -121,12 +133,16 @@ public enum TickUnit { @SuppressWarnings("WeakerAccess") @Inject - public TradesChartsViewModel(TradeStatisticsManager tradeStatisticsManager, Preferences preferences, PriceFeedService priceFeedService, Navigation navigation, BSFormatter formatter) { + public TradesChartsViewModel(TradeStatisticsManager tradeStatisticsManager, Preferences preferences, + PriceFeedService priceFeedService, Navigation navigation, BSFormatter formatter, + DaoStateService daoStateService, BsqFormatter bsqFormatter) { this.tradeStatisticsManager = tradeStatisticsManager; this.preferences = preferences; this.priceFeedService = priceFeedService; this.navigation = navigation; this.formatter = formatter; + this.daoStateService = daoStateService; + this.bsqFormatter = bsqFormatter; setChangeListener = change -> { updateChartData(); @@ -256,6 +272,8 @@ private void updateChartData() { tradeStatisticsByCurrency.setAll(tradeStatisticsManager.getObservableTradeStatisticsSet().stream() .filter(e -> showAllTradeCurrenciesProperty.get() || e.getCurrencyCode().equals(getCurrencyCode())) .collect(Collectors.toList())); + + burntBsqStatistics.setAll(daoStateService.getBurntFeeTxs()); // Generate date range and create sets for all ticks itemsPerInterval = new HashMap<>(); @@ -279,10 +297,31 @@ private void updateChartData() { } }); + burntBsqPerInterval = new HashMap<>(); + time = new Date(); + for (long i = maxTicks + 1; i >= 0; --i) { + Set set = new HashSet<>(); + Pair> pair = new Pair<>((Date) time.clone(), set); + burntBsqPerInterval.put(i, pair); + time.setTime(time.getTime() - 1); + time = roundToTick(time, tickUnit); + } + + // Get all entries for the defined time interval + burntBsqStatistics.stream().forEach(e -> { + for (long i = maxTicks; i > 0; --i) { + Pair> p = burntBsqPerInterval.get(i); + if (Date.from(Instant.ofEpochMilli(e.getTime())).after(p.getKey())) { + p.getValue().add(e); + break; + } + } + }); + // create CandleData for defined time interval List candleDataList = itemsPerInterval.entrySet().stream() .filter(entry -> entry.getKey() >= 0 && !entry.getValue().getValue().isEmpty()) - .map(entry -> getCandleData(entry.getKey(), entry.getValue().getValue())) + .map(entry -> getCandleData(entry.getKey(), entry.getValue().getValue(), burntBsqPerInterval.get(entry.getKey()).getValue())) .collect(Collectors.toList()); candleDataList.sort((o1, o2) -> (o1.tick < o2.tick ? -1 : (o1.tick == o2.tick ? 0 : 1))); @@ -295,16 +334,24 @@ private void updateChartData() { volumeItems.setAll(candleDataList.stream() .map(e -> new XYChart.Data(e.tick, e.accumulatedAmount, e)) .collect(Collectors.toList())); + + //noinspection Convert2Diamond + burntBsqRatioItems.setAll(candleDataList.stream() + .map(e -> new XYChart.Data(e.tick, e.bisqBurnVolumeRatio, e)) + .collect(Collectors.toList())); + } @VisibleForTesting - CandleData getCandleData(long tick, Set set) { + CandleData getCandleData(long tick, Set set, Set burntBsqSet) { long open = 0; long close = 0; long high = 0; long low = 0; long accumulatedVolume = 0; long accumulatedAmount = 0; + long accumulatedBisqBurn = 0; + double bisqBurnVolumeRatio = 0; long numTrades = set.size(); List tradePrices = new ArrayList<>(set.size()); @@ -333,13 +380,16 @@ CandleData getCandleData(long tick, Set set) { tradePrices.toArray(prices); long medianPrice = MathUtils.getMedian(prices); boolean isBullish; + double accumulatedMetricAsDouble; if (CurrencyUtil.isCryptoCurrency(getCurrencyCode())) { isBullish = close < open; double accumulatedAmountAsDouble = MathUtils.scaleUpByPowerOf10((double) accumulatedAmount, Altcoin.SMALLEST_UNIT_EXPONENT); + accumulatedMetricAsDouble = accumulatedAmount; averagePrice = MathUtils.roundDoubleToLong(accumulatedAmountAsDouble / (double) accumulatedVolume); } else { isBullish = close > open; double accumulatedVolumeAsDouble = MathUtils.scaleUpByPowerOf10((double) accumulatedVolume, Coin.SMALLEST_UNIT_EXPONENT); + accumulatedMetricAsDouble = accumulatedVolume; averagePrice = MathUtils.roundDoubleToLong(accumulatedVolumeAsDouble / (double) accumulatedAmount); } @@ -348,8 +398,17 @@ CandleData getCandleData(long tick, Set set) { String dateString = tickUnit.ordinal() > TickUnit.DAY.ordinal() ? DisplayUtils.formatDateTimeSpan(dateFrom, dateTo) : DisplayUtils.formatDate(dateFrom) + " - " + DisplayUtils.formatDate(dateTo); + + for (Tx item : burntBsqSet) { + accumulatedBisqBurn += item.getBurntBsq(); + } + + //TODO compute bisqBurnVolumeRatio + double accumulatedBisqBurnAsDouble = (double) accumulatedBisqBurn; + bisqBurnVolumeRatio = accumulatedMetricAsDouble != 0 ? accumulatedBisqBurnAsDouble * 100.0 / accumulatedMetricAsDouble : 0l;//Percentage calculation + return new CandleData(tick, open, close, high, low, averagePrice, medianPrice, accumulatedAmount, accumulatedVolume, - numTrades, isBullish, dateString); + numTrades, bisqBurnVolumeRatio, isBullish, dateString); } Date roundToTick(Date time, TickUnit tickUnit) { diff --git a/desktop/src/main/java/bisq/desktop/main/market/trades/charts/CandleData.java b/desktop/src/main/java/bisq/desktop/main/market/trades/charts/CandleData.java index 328d8a51397..3b9dd6a4b38 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/trades/charts/CandleData.java +++ b/desktop/src/main/java/bisq/desktop/main/market/trades/charts/CandleData.java @@ -28,11 +28,12 @@ public class CandleData { public final long accumulatedAmount; public final long accumulatedVolume; public final long numTrades; + public final double bisqBurnVolumeRatio; public final boolean isBullish; public final String date; public CandleData(long tick, long open, long close, long high, long low, long average, long median, - long accumulatedAmount, long accumulatedVolume, long numTrades, + long accumulatedAmount, long accumulatedVolume, long numTrades, double bisqBurnVolumeRatio, boolean isBullish, String date) { this.tick = tick; this.open = open; @@ -44,6 +45,7 @@ public CandleData(long tick, long open, long close, long high, long low, long av this.accumulatedAmount = accumulatedAmount; this.accumulatedVolume = accumulatedVolume; this.numTrades = numTrades; + this.bisqBurnVolumeRatio = bisqBurnVolumeRatio; this.isBullish = isBullish; this.date = date; } diff --git a/desktop/src/main/java/bisq/desktop/main/market/trades/charts/volume/VolumeBar.java b/desktop/src/main/java/bisq/desktop/main/market/trades/charts/volume/VolumeBar.java index 41d45dab767..4f8b569db38 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/trades/charts/volume/VolumeBar.java +++ b/desktop/src/main/java/bisq/desktop/main/market/trades/charts/volume/VolumeBar.java @@ -61,7 +61,7 @@ public void setSeriesAndDataStyleClasses(String seriesStyleClass, String dataSty public void update(double height, double candleWidth, CandleData candleData) { bar.resizeRelocate(-candleWidth / 2, 0, candleWidth, height); String vol = volumeStringConverter.toString(candleData.accumulatedAmount); - tooltip.setText(Res.get("market.trades.tooltip.volumeBar", vol, candleData.numTrades, candleData.date)); + tooltip.setText(Res.get("market.trades.tooltip.volumeBar", vol, candleData.numTrades, candleData.bisqBurnVolumeRatio, candleData.date)); } private void updateStyleClasses() { diff --git a/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java index 7e969c41006..1033ee4ef28 100644 --- a/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/market/trades/TradesChartsViewModelTest.java @@ -20,6 +20,9 @@ import bisq.desktop.Navigation; import bisq.desktop.main.market.trades.charts.CandleData; +import bisq.core.dao.state.DaoStateService; +import bisq.core.dao.state.model.blockchain.Tx; + import bisq.core.locale.FiatCurrency; import bisq.core.monetary.Price; import bisq.core.offer.OfferPayload; @@ -28,6 +31,7 @@ import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; import bisq.core.util.BSFormatter; +import bisq.core.util.BsqFormatter; import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyStorage; @@ -111,7 +115,7 @@ public class TradesChartsViewModelTest { public void setup() throws IOException { tradeStatisticsManager = mock(TradeStatisticsManager.class); model = new TradesChartsViewModel(tradeStatisticsManager, mock(Preferences.class), mock(PriceFeedService.class), - mock(Navigation.class), mock(BSFormatter.class)); + mock(Navigation.class), mock(BSFormatter.class), mock(DaoStateService.class), mock(BsqFormatter.class)); dir = File.createTempFile("temp_tests1", ""); //noinspection ResultOfMethodCallIgnored dir.delete(); @@ -143,7 +147,9 @@ public void testGetCandleData() { set.add(new TradeStatistics2(offer, Price.parse("EUR", "600"), Coin.parseCoin("1"), new Date(now.getTime() + 200), null, null)); set.add(new TradeStatistics2(offer, Price.parse("EUR", "580"), Coin.parseCoin("1"), new Date(now.getTime() + 300), null, null)); - CandleData candleData = model.getCandleData(model.roundToTick(now, TradesChartsViewModel.TickUnit.DAY).getTime(), set); + Set set2 = new HashSet<>(); + + CandleData candleData = model.getCandleData(model.roundToTick(now, TradesChartsViewModel.TickUnit.DAY).getTime(), set, set2); assertEquals(open, candleData.open); assertEquals(close, candleData.close); assertEquals(high, candleData.high);