Add BSQ market cap chart.

This commit is contained in:
jmacxx 2024-01-22 14:33:11 -06:00
parent 4d4c9e3186
commit 997bf0d6b8
No known key found for this signature in database
GPG Key ID: 155297BABFE94A1B
5 changed files with 219 additions and 11 deletions

View File

@ -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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}));
}
}

View File

@ -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";
};
}
}