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 org.slf4j.LoggerFactory;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
@ -85,6 +86,11 @@ public class MathUtils {
return ((double) value) * factor; 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) { public static double scaleDownByPowerOf10(double value, int exponent) {
double factor = Math.pow(10, exponent); double factor = Math.pow(10, exponent);
return value / factor; return value / factor;
@ -95,6 +101,11 @@ public class MathUtils {
return ((double) value) / factor; 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) { public static double exactMultiply(double value1, double value2) {
return BigDecimal.valueOf(value1).multiply(BigDecimal.valueOf(value2)).doubleValue(); 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.locale.CurrencyUtil;
import haveno.core.monetary.CryptoMoney; import haveno.core.monetary.CryptoMoney;
import haveno.core.monetary.TraditionalMoney; import haveno.core.monetary.TraditionalMoney;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.statistics.TradeStatistics3; import haveno.core.trade.statistics.TradeStatistics3;
import haveno.desktop.main.market.trades.charts.CandleData; import haveno.desktop.main.market.trades.charts.CandleData;
import haveno.desktop.util.DisplayUtils; import haveno.desktop.util.DisplayUtils;
import javafx.scene.chart.XYChart; import javafx.scene.chart.XYChart;
import javafx.util.Pair; import javafx.util.Pair;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.math.BigInteger;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
@ -47,6 +50,7 @@ import java.util.stream.Collectors;
import static haveno.desktop.main.market.trades.TradesChartsViewModel.MAX_TICKS; import static haveno.desktop.main.market.trades.TradesChartsViewModel.MAX_TICKS;
@Slf4j
public class ChartCalculations { public class ChartCalculations {
static final ZoneId ZONE_ID = ZoneId.systemDefault(); static final ZoneId ZONE_ID = ZoneId.systemDefault();
@ -211,15 +215,15 @@ public class ChartCalculations {
} }
private static long getAverageTraditionalPrice(List<TradeStatistics3> tradeStatisticsList) { private static long getAverageTraditionalPrice(List<TradeStatistics3> tradeStatisticsList) {
long accumulatedAmount = 0; // TODO: use BigInteger BigInteger accumulatedAmount = BigInteger.ZERO;
long accumulatedVolume = 0; BigInteger accumulatedVolume = BigInteger.ZERO;
for (TradeStatistics3 tradeStatistics : tradeStatisticsList) { for (TradeStatistics3 tradeStatistics : tradeStatisticsList) {
accumulatedAmount += tradeStatistics.getAmount(); accumulatedAmount = accumulatedAmount.add(BigInteger.valueOf(tradeStatistics.getAmount()));
accumulatedVolume += tradeStatistics.getTradeVolume().getValue(); accumulatedVolume = accumulatedVolume.add(BigInteger.valueOf(tradeStatistics.getTradeVolume().getValue()));
} }
double accumulatedVolumeAsDouble = MathUtils.scaleUpByPowerOf10((double) accumulatedVolume, 4 + TraditionalMoney.SMALLEST_UNIT_EXPONENT); BigInteger accumulatedVolumeAsBI = MathUtils.scaleUpByPowerOf10(accumulatedVolume, TraditionalMoney.SMALLEST_UNIT_EXPONENT + 4);
return MathUtils.roundDoubleToLong(accumulatedVolumeAsDouble / accumulatedAmount); return MathUtils.roundDoubleToLong(HavenoUtils.divide(accumulatedVolumeAsBI, accumulatedAmount));
} }
@VisibleForTesting @VisibleForTesting
@ -232,8 +236,8 @@ public class ChartCalculations {
long close = 0; long close = 0;
long high = 0; long high = 0;
long low = 0; long low = 0;
long accumulatedVolume = 0; // TODO: use BigInteger BigInteger accumulatedVolume = BigInteger.ZERO;
long accumulatedAmount = 0; BigInteger accumulatedAmount = BigInteger.ZERO;
long numTrades = set.size(); long numTrades = set.size();
List<Long> tradePrices = new ArrayList<>(); List<Long> tradePrices = new ArrayList<>();
for (TradeStatistics3 item : set) { for (TradeStatistics3 item : set) {
@ -242,8 +246,8 @@ public class ChartCalculations {
low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong; low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong;
high = (high != 0) ? Math.max(high, tradePriceAsLong) : tradePriceAsLong; high = (high != 0) ? Math.max(high, tradePriceAsLong) : tradePriceAsLong;
accumulatedVolume += item.getTradeVolume().getValue(); accumulatedVolume = accumulatedVolume.add(BigInteger.valueOf(item.getTradeVolume().getValue()));
accumulatedAmount += item.getTradeAmount().longValueExact(); accumulatedAmount = accumulatedAmount.add(item.getTradeAmount());
tradePrices.add(tradePriceAsLong); tradePrices.add(tradePriceAsLong);
} }
Collections.sort(tradePrices); Collections.sort(tradePrices);
@ -262,12 +266,12 @@ public class ChartCalculations {
boolean isBullish; boolean isBullish;
if (CurrencyUtil.isCryptoCurrency(currencyCode)) { if (CurrencyUtil.isCryptoCurrency(currencyCode)) {
isBullish = close < open; isBullish = close < open;
double accumulatedAmountAsDouble = MathUtils.scaleUpByPowerOf10((double) accumulatedAmount, CryptoMoney.SMALLEST_UNIT_EXPONENT - 4); BigInteger accumulatedAmountAsBI = MathUtils.scaleUpByPowerOf10(accumulatedAmount, CryptoMoney.SMALLEST_UNIT_EXPONENT - 4);
averagePrice = MathUtils.roundDoubleToLong(accumulatedAmountAsDouble / accumulatedVolume); averagePrice = MathUtils.roundDoubleToLong(HavenoUtils.divide(accumulatedAmountAsBI, accumulatedVolume));
} else { } else {
isBullish = close > open; isBullish = close > open;
double accumulatedVolumeAsDouble = MathUtils.scaleUpByPowerOf10((double) accumulatedVolume, 4 + TraditionalMoney.SMALLEST_UNIT_EXPONENT); BigInteger accumulatedVolumeAsBI = MathUtils.scaleUpByPowerOf10(accumulatedVolume, TraditionalMoney.SMALLEST_UNIT_EXPONENT + 4);
averagePrice = MathUtils.roundDoubleToLong(accumulatedVolumeAsDouble / accumulatedAmount); averagePrice = MathUtils.roundDoubleToLong(HavenoUtils.divide(accumulatedVolumeAsBI, accumulatedAmount));
} }
Date dateFrom = new Date(getTimeFromTickIndex(tick, itemsPerInterval)); 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. // 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); 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. // 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); 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); numTrades, isBullish, dateString, volumeInUsd);
} }