package com.after_sunrise.cryptocurrency.cryptotrader.service.estimator; import com.after_sunrise.cryptocurrency.cryptotrader.framework.Context; import com.after_sunrise.cryptocurrency.cryptotrader.framework.Context.Key; import com.after_sunrise.cryptocurrency.cryptotrader.framework.Request; import com.after_sunrise.cryptocurrency.cryptotrader.framework.Trade; import com.google.common.annotations.VisibleForTesting; import java.math.BigDecimal; import java.time.Duration; import java.time.Instant; import java.util.Comparator; import java.util.Objects; import java.util.Optional; import static java.math.BigDecimal.ONE; import static java.math.BigDecimal.ZERO; import static java.math.RoundingMode.HALF_UP; import static java.time.temporal.ChronoUnit.HOURS; import static org.apache.commons.lang3.math.NumberUtils.LONG_ONE; /** * @author takanori.takase * @version 0.0.1 */ public class LastEstimator extends AbstractEstimator { private static final Comparator<Trade> COMPARATOR = Comparator.comparing(Trade::getTimestamp).reversed(); @Override public Estimation estimate(Context context, Request request) { Key key = getKey(context, request); return estimate(context, key); } protected Estimation estimate(Context context, Key key) { Instant now = key.getTimestamp(); if (now == null) { return BAIL; } Instant from = now.minus(LONG_ONE, HOURS); Optional<Trade> value = trimToEmpty(context.listTrades(key, from)).stream() .filter(Objects::nonNull) .filter(t -> Objects.nonNull(t.getTimestamp())) .filter(t -> Objects.nonNull(t.getPrice())) .sorted(COMPARATOR) .findFirst(); if (!value.isPresent()) { return BAIL; } Instant time = value.get().getTimestamp(); BigDecimal confidence = calculateConfidence(time, now); BigDecimal price = value.get().getPrice(); log.debug("Estimated : {} (confidence=[{}] time=[{}])", price, confidence, time); return Estimation.builder().price(price).confidence(confidence).build(); } @VisibleForTesting BigDecimal calculateConfidence(Instant time, Instant now) { Duration duration = Duration.between(time, now); if (duration.isZero() || duration.isNegative()) { return ONE; } // Number from 0 to 29 (= 100 years) double scaled = Math.log(duration.toMillis()); // Number from 0.00 to 0.79 double multiplied = scaled * Math.E / 100; // Number from 100.00 to 0.21 double confidence = 1 - multiplied; // Sanitize in case if +3000 years... return BigDecimal.valueOf(confidence).max(ZERO).setScale(SCALE, HALF_UP); } }