use BigInteger for average chart calculations

This commit is contained in:
woodser 2025-06-29 15:10:10 -04:00 committed by woodser
parent 3546d3d931
commit 33c9628df3
2 changed files with 31 additions and 16 deletions

View file

@ -22,6 +22,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayDeque;
import java.util.Deque;
@ -85,6 +86,11 @@ public class MathUtils {
return ((double) value) * factor;
}
public static BigInteger scaleUpByPowerOf10(BigInteger value, int exponent) {
BigInteger factor = BigInteger.TEN.pow(exponent);
return value.multiply(factor);
}
public static double scaleDownByPowerOf10(double value, int exponent) {
double factor = Math.pow(10, exponent);
return value / factor;
@ -95,6 +101,11 @@ public class MathUtils {
return ((double) value) / factor;
}
public static BigInteger scaleDownByPowerOf10(BigInteger value, int exponent) {
BigInteger factor = BigInteger.TEN.pow(exponent);
return value.divide(factor);
}
public static double exactMultiply(double value1, double value2) {
return BigDecimal.valueOf(value1).multiply(BigDecimal.valueOf(value2)).doubleValue();
}

View file

@ -22,13 +22,16 @@ import haveno.common.util.MathUtils;
import haveno.core.locale.CurrencyUtil;
import haveno.core.monetary.CryptoMoney;
import haveno.core.monetary.TraditionalMoney;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.statistics.TradeStatistics3;
import haveno.desktop.main.market.trades.charts.CandleData;
import haveno.desktop.util.DisplayUtils;
import javafx.scene.chart.XYChart;
import javafx.util.Pair;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
@ -47,6 +50,7 @@ import java.util.stream.Collectors;
import static haveno.desktop.main.market.trades.TradesChartsViewModel.MAX_TICKS;
@Slf4j
public class ChartCalculations {
static final ZoneId ZONE_ID = ZoneId.systemDefault();
@ -211,15 +215,15 @@ public class ChartCalculations {
}
private static long getAverageTraditionalPrice(List<TradeStatistics3> tradeStatisticsList) {
long accumulatedAmount = 0; // TODO: use BigInteger
long accumulatedVolume = 0;
BigInteger accumulatedAmount = BigInteger.ZERO;
BigInteger accumulatedVolume = BigInteger.ZERO;
for (TradeStatistics3 tradeStatistics : tradeStatisticsList) {
accumulatedAmount += tradeStatistics.getAmount();
accumulatedVolume += tradeStatistics.getTradeVolume().getValue();
accumulatedAmount = accumulatedAmount.add(BigInteger.valueOf(tradeStatistics.getAmount()));
accumulatedVolume = accumulatedVolume.add(BigInteger.valueOf(tradeStatistics.getTradeVolume().getValue()));
}
double accumulatedVolumeAsDouble = MathUtils.scaleUpByPowerOf10((double) accumulatedVolume, 4 + TraditionalMoney.SMALLEST_UNIT_EXPONENT);
return MathUtils.roundDoubleToLong(accumulatedVolumeAsDouble / accumulatedAmount);
BigInteger accumulatedVolumeAsBI = MathUtils.scaleUpByPowerOf10(accumulatedVolume, TraditionalMoney.SMALLEST_UNIT_EXPONENT + 4);
return MathUtils.roundDoubleToLong(HavenoUtils.divide(accumulatedVolumeAsBI, accumulatedAmount));
}
@VisibleForTesting
@ -232,8 +236,8 @@ public class ChartCalculations {
long close = 0;
long high = 0;
long low = 0;
long accumulatedVolume = 0; // TODO: use BigInteger
long accumulatedAmount = 0;
BigInteger accumulatedVolume = BigInteger.ZERO;
BigInteger accumulatedAmount = BigInteger.ZERO;
long numTrades = set.size();
List<Long> tradePrices = new ArrayList<>();
for (TradeStatistics3 item : set) {
@ -242,8 +246,8 @@ public class ChartCalculations {
low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong;
high = (high != 0) ? Math.max(high, tradePriceAsLong) : tradePriceAsLong;
accumulatedVolume += item.getTradeVolume().getValue();
accumulatedAmount += item.getTradeAmount().longValueExact();
accumulatedVolume = accumulatedVolume.add(BigInteger.valueOf(item.getTradeVolume().getValue()));
accumulatedAmount = accumulatedAmount.add(item.getTradeAmount());
tradePrices.add(tradePriceAsLong);
}
Collections.sort(tradePrices);
@ -262,12 +266,12 @@ public class ChartCalculations {
boolean isBullish;
if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
isBullish = close < open;
double accumulatedAmountAsDouble = MathUtils.scaleUpByPowerOf10((double) accumulatedAmount, CryptoMoney.SMALLEST_UNIT_EXPONENT - 4);
averagePrice = MathUtils.roundDoubleToLong(accumulatedAmountAsDouble / accumulatedVolume);
BigInteger accumulatedAmountAsBI = MathUtils.scaleUpByPowerOf10(accumulatedAmount, CryptoMoney.SMALLEST_UNIT_EXPONENT - 4);
averagePrice = MathUtils.roundDoubleToLong(HavenoUtils.divide(accumulatedAmountAsBI, accumulatedVolume));
} else {
isBullish = close > open;
double accumulatedVolumeAsDouble = MathUtils.scaleUpByPowerOf10((double) accumulatedVolume, 4 + TraditionalMoney.SMALLEST_UNIT_EXPONENT);
averagePrice = MathUtils.roundDoubleToLong(accumulatedVolumeAsDouble / accumulatedAmount);
BigInteger accumulatedVolumeAsBI = MathUtils.scaleUpByPowerOf10(accumulatedVolume, TraditionalMoney.SMALLEST_UNIT_EXPONENT + 4);
averagePrice = MathUtils.roundDoubleToLong(HavenoUtils.divide(accumulatedVolumeAsBI, accumulatedAmount));
}
Date dateFrom = new Date(getTimeFromTickIndex(tick, itemsPerInterval));
@ -278,10 +282,10 @@ public class ChartCalculations {
// We do not need precision, so we scale down before multiplication otherwise we could get an overflow.
averageUsdPrice = (long) MathUtils.scaleDownByPowerOf10((double) averageUsdPrice, TraditionalMoney.SMALLEST_UNIT_EXPONENT);
long volumeInUsd = averageUsdPrice * (long) MathUtils.scaleDownByPowerOf10((double) accumulatedAmount, 4);
long volumeInUsd = averageUsdPrice * MathUtils.scaleDownByPowerOf10(accumulatedAmount, 4).longValue();
// We store USD value without decimals as its only total volume, no precision is needed.
volumeInUsd = (long) MathUtils.scaleDownByPowerOf10((double) volumeInUsd, TraditionalMoney.SMALLEST_UNIT_EXPONENT);
return new CandleData(tick, open, close, high, low, averagePrice, medianPrice, accumulatedAmount, accumulatedVolume,
return new CandleData(tick, open, close, high, low, averagePrice, medianPrice, accumulatedAmount.longValueExact(), accumulatedVolume.longValueExact(),
numTrades, isBullish, dateString, volumeInUsd);
}