mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Add BSQ market cap chart.
This commit is contained in:
parent
4d4c9e3186
commit
997bf0d6b8
@ -2837,6 +2837,8 @@ dao.factsAndFigures.supply.tradeVolumeInBtc=Trade volume in BTC
|
||||
dao.factsAndFigures.supply.bsqUsdPrice=BSQ/USD price
|
||||
dao.factsAndFigures.supply.bsqBtcPrice=BSQ/BTC price
|
||||
dao.factsAndFigures.supply.btcUsdPrice=BTC/USD price
|
||||
dao.factsAndFigures.supply.bsqUsdMarketCap=Market Cap (USD)
|
||||
dao.factsAndFigures.supply.bsqBtcMarketCap=Market Cap (BTC)
|
||||
|
||||
dao.factsAndFigures.supply.locked=Global state of locked BSQ
|
||||
dao.factsAndFigures.supply.totalLockedUpAmount=Locked up in bonds
|
||||
|
@ -401,7 +401,7 @@ public abstract class ChartView<T extends ChartViewModel<? extends ChartDataMode
|
||||
hBox.setSpacing(10);
|
||||
collection.forEach(series -> {
|
||||
AutoTooltipSlideToggleButton toggle = new AutoTooltipSlideToggleButton();
|
||||
toggle.setMinWidth(300);
|
||||
toggle.setMinWidth(collection.size() > 3 ? 220 : 300);
|
||||
toggle.setAlignment(Pos.TOP_LEFT);
|
||||
String seriesId = getSeriesId(series);
|
||||
legendToggleBySeriesName.put(seriesId, toggle);
|
||||
|
@ -19,6 +19,9 @@ package bisq.desktop.main.dao.economy.dashboard.price;
|
||||
|
||||
import bisq.desktop.components.chart.ChartDataModel;
|
||||
|
||||
import bisq.core.dao.state.DaoStateService;
|
||||
import bisq.core.dao.state.model.blockchain.Tx;
|
||||
import bisq.core.dao.state.model.governance.Issuance;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
|
||||
@ -29,19 +32,34 @@ import javax.inject.Inject;
|
||||
import java.time.Instant;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Slf4j
|
||||
public class PriceChartDataModel extends ChartDataModel {
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private Map<Long, Double> bsqUsdPriceByInterval, bsqBtcPriceByInterval, btcUsdPriceByInterval;
|
||||
private final DaoStateService daoStateService;
|
||||
private Map<Long, Double>
|
||||
bsqUsdPriceByInterval,
|
||||
bsqBtcPriceByInterval,
|
||||
btcUsdPriceByInterval,
|
||||
bsqUsdMarketCapByInterval,
|
||||
bsqBtcMarketCapByInterval;
|
||||
private final Function<Issuance, Long> blockTimeOfIssuanceFunction;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -49,10 +67,16 @@ public class PriceChartDataModel extends ChartDataModel {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public PriceChartDataModel(TradeStatisticsManager tradeStatisticsManager) {
|
||||
public PriceChartDataModel(TradeStatisticsManager tradeStatisticsManager, DaoStateService daoStateService) {
|
||||
super();
|
||||
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.daoStateService = daoStateService;
|
||||
|
||||
blockTimeOfIssuanceFunction = memoize(issuance -> {
|
||||
int height = daoStateService.getStartHeightOfCurrentCycle(issuance.getChainHeight()).orElse(0);
|
||||
return daoStateService.getBlockTime(height);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -65,6 +89,8 @@ public class PriceChartDataModel extends ChartDataModel {
|
||||
bsqUsdPriceByInterval = null;
|
||||
bsqBtcPriceByInterval = null;
|
||||
btcUsdPriceByInterval = null;
|
||||
bsqUsdMarketCapByInterval = null;
|
||||
bsqBtcMarketCapByInterval = null;
|
||||
}
|
||||
|
||||
|
||||
@ -98,21 +124,38 @@ public class PriceChartDataModel extends ChartDataModel {
|
||||
return bsqUsdPriceByInterval;
|
||||
}
|
||||
|
||||
Map<Long, Double> getBsqUsdMarketCapByInterval() {
|
||||
if (bsqUsdMarketCapByInterval != null) {
|
||||
return bsqUsdMarketCapByInterval;
|
||||
}
|
||||
bsqUsdMarketCapByInterval = getBsqMarketCapByInterval(tradeStatistics -> tradeStatistics.getCurrency().equals("BSQ") ||
|
||||
tradeStatistics.getCurrency().equals("USD"),
|
||||
PriceChartDataModel::getAverageBsqUsdPrice);
|
||||
return bsqUsdMarketCapByInterval;
|
||||
}
|
||||
|
||||
Map<Long, Double> getBsqBtcPriceByInterval() {
|
||||
if (bsqBtcPriceByInterval != null) {
|
||||
return bsqBtcPriceByInterval;
|
||||
}
|
||||
|
||||
bsqBtcPriceByInterval = getPriceByInterval(tradeStatistics -> tradeStatistics.getCurrency().equals("BSQ"),
|
||||
PriceChartDataModel::getAverageBsqBtcPrice);
|
||||
return bsqBtcPriceByInterval;
|
||||
}
|
||||
|
||||
Map<Long, Double> getBsqBtcMarketCapByInterval() {
|
||||
if (bsqBtcMarketCapByInterval != null) {
|
||||
return bsqBtcMarketCapByInterval;
|
||||
}
|
||||
bsqBtcMarketCapByInterval = getBsqMarketCapByInterval(tradeStatistics -> tradeStatistics.getCurrency().equals("BSQ"),
|
||||
PriceChartDataModel::getAverageBsqBtcPrice);
|
||||
return bsqBtcMarketCapByInterval;
|
||||
}
|
||||
|
||||
Map<Long, Double> getBtcUsdPriceByInterval() {
|
||||
if (btcUsdPriceByInterval != null) {
|
||||
return btcUsdPriceByInterval;
|
||||
}
|
||||
|
||||
btcUsdPriceByInterval = getPriceByInterval(tradeStatistics -> tradeStatistics.getCurrency().equals("USD"),
|
||||
PriceChartDataModel::getAverageBtcUsdPrice);
|
||||
return btcUsdPriceByInterval;
|
||||
@ -221,4 +264,104 @@ public class PriceChartDataModel extends ChartDataModel {
|
||||
.filter(tradeStatistics -> dateFilter.test(tradeStatistics.getDateAsLong() / 1000))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private Map<Long, Double> getBsqMarketCapByInterval(Predicate<TradeStatistics3> collectionFilter,
|
||||
Function<List<TradeStatistics3>, Double> getAveragePriceFunction) {
|
||||
var toTimeIntervalFn = toCachedTimeIntervalFn();
|
||||
return getBsqMarketCapByInterval(tradeStatisticsManager.getObservableTradeStatisticsSet(),
|
||||
collectionFilter,
|
||||
tradeStatistics -> toTimeIntervalFn.applyAsLong(Instant.ofEpochMilli(tradeStatistics.getDateAsLong())),
|
||||
dateFilter,
|
||||
getAveragePriceFunction);
|
||||
}
|
||||
|
||||
private Map<Long, Double> getBsqMarketCapByInterval(Collection<TradeStatistics3> tradeStatistics3s,
|
||||
Predicate<TradeStatistics3> collectionFilter,
|
||||
Function<TradeStatistics3, Long> groupByDateFunction,
|
||||
Predicate<Long> dateFilter,
|
||||
Function<List<TradeStatistics3>, Double> getAveragePriceFunction) {
|
||||
|
||||
Map<Long, List<TradeStatistics3>> pricesGroupedByDate = tradeStatistics3s.stream()
|
||||
.filter(collectionFilter)
|
||||
.collect(Collectors.groupingBy(groupByDateFunction));
|
||||
|
||||
Stream<Map.Entry<Long,List<TradeStatistics3>>> filteredByDate =
|
||||
pricesGroupedByDate.entrySet().stream()
|
||||
.filter(entry -> dateFilter.test(entry.getKey()));
|
||||
|
||||
Map<Long, Double> resultsByDateBucket = filteredByDate
|
||||
.map(entry -> new AbstractMap.SimpleEntry<>(
|
||||
entry.getKey(),
|
||||
getAveragePriceFunction.apply(entry.getValue())))
|
||||
.filter(e -> e.getValue() > 0d)
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
// apply the available BSQ to the data set
|
||||
Map<Long, Double> totalSupplyByInterval = getOutstandingBsqByInterval();
|
||||
resultsByDateBucket.keySet().forEach(dateKey -> {
|
||||
Double availableBsq = issuanceAsOfDate(totalSupplyByInterval, dateKey)/100;
|
||||
resultsByDateBucket.put(dateKey, resultsByDateBucket.get(dateKey) * availableBsq); // market cap (price * available BSQ)
|
||||
});
|
||||
return resultsByDateBucket;
|
||||
}
|
||||
|
||||
private Double issuanceAsOfDate(@NotNull Map<Long, Double> totalSupplyByInterval, Long dateKey) {
|
||||
ArrayList<Long> list = new ArrayList<>(totalSupplyByInterval.keySet());
|
||||
list.sort(Collections.reverseOrder());
|
||||
Optional<Long> foundKey = list.stream()
|
||||
.filter(d -> dateKey >= d)
|
||||
.findFirst();
|
||||
if (foundKey.isPresent()) {
|
||||
return totalSupplyByInterval.get(foundKey.get());
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private Map<Long, Double> getOutstandingBsqByInterval() {
|
||||
Stream<Tx> txStream = daoStateService.getBlocks().stream()
|
||||
.flatMap(b -> b.getTxs().stream())
|
||||
.filter(tx -> tx.getBurntFee() > 0);
|
||||
Map<Long, Double> simpleBurns = txStream
|
||||
.collect(Collectors.groupingBy(tx ->
|
||||
toTimeInterval(Instant.ofEpochMilli(tx.getTime()))))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> entry.getValue().stream()
|
||||
.mapToDouble(Tx::getBurntBsq)
|
||||
.sum()));
|
||||
simpleBurns.forEach((k,v) -> simpleBurns.put(k, -v));
|
||||
|
||||
Collection<Issuance> issuanceSet = daoStateService.getIssuanceItems();
|
||||
Map<Long, Double> simpleIssuance = issuanceSet.stream()
|
||||
.collect(Collectors.groupingBy(issuance ->
|
||||
toTimeInterval(Instant.ofEpochMilli(blockTimeOfIssuanceFunction.apply(issuance)))))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> entry.getValue().stream()
|
||||
.mapToDouble(Issuance::getAmount)
|
||||
.sum()));
|
||||
|
||||
Map<Long, Double> supplyByInterval = Stream.concat(simpleIssuance.entrySet().stream(),
|
||||
simpleBurns.entrySet().stream())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey,
|
||||
Map.Entry::getValue,
|
||||
Double::sum));
|
||||
|
||||
ArrayList<Long> listCombined = new ArrayList<>(supplyByInterval.keySet());
|
||||
Collections.sort(listCombined);
|
||||
AtomicReference<Double> atomicSum = new AtomicReference<>((double) (daoStateService.getGenesisTotalSupply().value));
|
||||
listCombined.forEach(k -> supplyByInterval.put(k, atomicSum.accumulateAndGet(supplyByInterval.get(k), Double::sum)));
|
||||
return supplyByInterval;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static <T, R> Function<T, R> memoize(Function<T, R> fn) {
|
||||
Map<T, R> map = new ConcurrentHashMap<>();
|
||||
return x -> map.computeIfAbsent(x, fn);
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class PriceChartView extends ChartView<PriceChartViewModel> {
|
||||
private XYChart.Series<Number, Number> seriesBsqUsdPrice, seriesBsqBtcPrice, seriesBtcUsdPrice;
|
||||
private XYChart.Series<Number, Number> seriesBsqUsdPrice, seriesBsqBtcPrice, seriesBtcUsdPrice,
|
||||
seriesBsqUsdMarketCap, seriesBsqBtcMarketCap;
|
||||
private final DoubleProperty averageBsqUsdPriceProperty = new SimpleDoubleProperty();
|
||||
private final DoubleProperty averageBsqBtcPriceProperty = new SimpleDoubleProperty();
|
||||
|
||||
@ -77,8 +78,12 @@ public class PriceChartView extends ChartView<PriceChartViewModel> {
|
||||
model.setBsqUsdPriceFormatter();
|
||||
} else if (series == seriesBsqBtcPrice) {
|
||||
model.setBsqBtcPriceFormatter();
|
||||
} else {
|
||||
} else if (series == seriesBtcUsdPrice){
|
||||
model.setBtcUsdPriceFormatter();
|
||||
} else if (series == seriesBsqUsdMarketCap) {
|
||||
model.setBsqUsdMarketCapPriceFormatter();
|
||||
} else {
|
||||
model.setBsqBtcMarketCapPriceFormatter();
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +93,7 @@ public class PriceChartView extends ChartView<PriceChartViewModel> {
|
||||
|
||||
@Override
|
||||
protected Collection<XYChart.Series<Number, Number>> getSeriesForLegend1() {
|
||||
return List.of(seriesBsqUsdPrice, seriesBsqBtcPrice, seriesBtcUsdPrice);
|
||||
return List.of(seriesBsqUsdPrice, seriesBsqBtcPrice, seriesBtcUsdPrice, seriesBsqUsdMarketCap, seriesBsqBtcMarketCap);
|
||||
}
|
||||
|
||||
|
||||
@ -108,17 +113,26 @@ public class PriceChartView extends ChartView<PriceChartViewModel> {
|
||||
|
||||
@Override
|
||||
protected void createSeries() {
|
||||
int index = -1;
|
||||
seriesBsqUsdPrice = new XYChart.Series<>();
|
||||
seriesBsqUsdPrice.setName(Res.get("dao.factsAndFigures.supply.bsqUsdPrice"));
|
||||
seriesIndexMap.put(getSeriesId(seriesBsqUsdPrice), 0);
|
||||
seriesIndexMap.put(getSeriesId(seriesBsqUsdPrice), ++index);
|
||||
|
||||
seriesBsqBtcPrice = new XYChart.Series<>();
|
||||
seriesBsqBtcPrice.setName(Res.get("dao.factsAndFigures.supply.bsqBtcPrice"));
|
||||
seriesIndexMap.put(getSeriesId(seriesBsqBtcPrice), 1);
|
||||
seriesIndexMap.put(getSeriesId(seriesBsqBtcPrice), ++index);
|
||||
|
||||
seriesBtcUsdPrice = new XYChart.Series<>();
|
||||
seriesBtcUsdPrice.setName(Res.get("dao.factsAndFigures.supply.btcUsdPrice"));
|
||||
seriesIndexMap.put(getSeriesId(seriesBtcUsdPrice), 2);
|
||||
seriesIndexMap.put(getSeriesId(seriesBtcUsdPrice), ++index);
|
||||
|
||||
seriesBsqUsdMarketCap = new XYChart.Series<>();
|
||||
seriesBsqUsdMarketCap.setName(Res.get("dao.factsAndFigures.supply.bsqUsdMarketCap"));
|
||||
seriesIndexMap.put(getSeriesId(seriesBsqUsdMarketCap), ++index);
|
||||
|
||||
seriesBsqBtcMarketCap = new XYChart.Series<>();
|
||||
seriesBsqBtcMarketCap.setName(Res.get("dao.factsAndFigures.supply.bsqBtcMarketCap"));
|
||||
seriesIndexMap.put(getSeriesId(seriesBsqBtcMarketCap), ++index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -151,6 +165,16 @@ public class PriceChartView extends ChartView<PriceChartViewModel> {
|
||||
allFutures.add(task3Done);
|
||||
applyBtcUsdPriceChartData(task3Done);
|
||||
}
|
||||
if (activeSeries.contains(seriesBsqUsdMarketCap)) {
|
||||
CompletableFuture<Boolean> taskDone = new CompletableFuture<>();
|
||||
allFutures.add(taskDone);
|
||||
applyBsqUsdMarketCapChartData(taskDone);
|
||||
}
|
||||
if (activeSeries.contains(seriesBsqBtcMarketCap)) {
|
||||
CompletableFuture<Boolean> taskDone = new CompletableFuture<>();
|
||||
allFutures.add(taskDone);
|
||||
applyBsqBtcMarketCapChartData(taskDone);
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> task4Done = new CompletableFuture<>();
|
||||
allFutures.add(task4Done);
|
||||
@ -203,4 +227,22 @@ public class PriceChartView extends ChartView<PriceChartViewModel> {
|
||||
completeFuture.complete(true);
|
||||
}));
|
||||
}
|
||||
|
||||
private void applyBsqUsdMarketCapChartData(CompletableFuture<Boolean> completeFuture) {
|
||||
model.getBsqUsdMarketCapChartData()
|
||||
.whenComplete((data, t) ->
|
||||
mapToUserThread(() -> {
|
||||
seriesBsqUsdMarketCap.getData().setAll(data);
|
||||
completeFuture.complete(true);
|
||||
}));
|
||||
}
|
||||
|
||||
private void applyBsqBtcMarketCapChartData(CompletableFuture<Boolean> completeFuture) {
|
||||
model.getBsqBtcMarketCapChartData()
|
||||
.whenComplete((data, t) ->
|
||||
mapToUserThread(() -> {
|
||||
seriesBsqBtcMarketCap.getData().setAll(data);
|
||||
completeFuture.complete(true);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,14 @@ public class PriceChartViewModel extends ChartViewModel<PriceChartDataModel> {
|
||||
return CompletableFuture.supplyAsync(() -> toChartDoubleData(dataModel.getBtcUsdPriceByInterval()));
|
||||
}
|
||||
|
||||
CompletableFuture<List<XYChart.Data<Number, Number>>> getBsqUsdMarketCapChartData() {
|
||||
return CompletableFuture.supplyAsync(() -> toChartDoubleData(dataModel.getBsqUsdMarketCapByInterval()));
|
||||
}
|
||||
|
||||
CompletableFuture<List<XYChart.Data<Number, Number>>> getBsqBtcMarketCapChartData() {
|
||||
return CompletableFuture.supplyAsync(() -> toChartDoubleData(dataModel.getBsqBtcMarketCapByInterval()));
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Formatters/Converters
|
||||
@ -117,4 +125,17 @@ public class PriceChartViewModel extends ChartViewModel<PriceChartDataModel> {
|
||||
priceFormat.setMaximumFractionDigits(0);
|
||||
yAxisFormatter = value -> priceFormat.format(value) + " BTC/USD";
|
||||
}
|
||||
|
||||
void setBsqUsdMarketCapPriceFormatter() {
|
||||
priceFormat.setMaximumFractionDigits(0);
|
||||
yAxisFormatter = value -> priceFormat.format(value) + " USD";
|
||||
}
|
||||
|
||||
void setBsqBtcMarketCapPriceFormatter() {
|
||||
priceFormat.setMaximumFractionDigits(8);
|
||||
yAxisFormatter = value -> {
|
||||
value = MathUtils.scaleDownByPowerOf10(value.longValue(), 8);
|
||||
return priceFormat.format(value) + " BTC";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user