Merge pull request #4529 from chimp1984/make-moving-average-code-more-safe

Make moving average code more safe
This commit is contained in:
Christoph Atteneder 2020-09-16 15:51:14 +02:00 committed by GitHub
commit 8cb4b5016c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 45 deletions

View File

@ -29,6 +29,8 @@ import java.util.Optional;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkArgument;
public class MathUtils { public class MathUtils {
private static final Logger log = LoggerFactory.getLogger(MathUtils.class); private static final Logger log = LoggerFactory.getLogger(MathUtils.class);
@ -127,24 +129,30 @@ public class MathUtils {
} }
public Optional<Double> next(long val) { public Optional<Double> next(long val) {
var fullAtStart = isFull(); try {
if (fullAtStart) { var fullAtStart = isFull();
if (outlier > 0) { if (fullAtStart) {
// Return early if it's an outlier if (outlier > 0) {
var avg = (double) sum / size; // Return early if it's an outlier
if (Math.abs(avg - val) / avg > outlier) { checkArgument(size != 0);
return Optional.empty(); var avg = (double) sum / size;
if (Math.abs(avg - val) / avg > outlier) {
return Optional.empty();
}
} }
sum -= window.remove();
} }
sum -= window.remove(); window.add(val);
sum += val;
if (!fullAtStart && isFull() && outlier != 0) {
removeInitialOutlier();
}
// When discarding outliers, the first n non discarded elements return Optional.empty()
return outlier > 0 && !isFull() ? Optional.empty() : current();
} catch (Throwable t) {
log.error(t.toString());
return Optional.empty();
} }
window.add(val);
sum += val;
if (!fullAtStart && isFull() && outlier != 0) {
removeInitialOutlier();
}
// When discarding outliers, the first n non discarded elements return Optional.empty()
return outlier > 0 && !isFull() ? Optional.empty() : current();
} }
boolean isFull() { boolean isFull() {
@ -155,7 +163,9 @@ public class MathUtils {
var element = window.iterator(); var element = window.iterator();
while (element.hasNext()) { while (element.hasNext()) {
var val = element.next(); var val = element.next();
var avgExVal = (double) (sum - val) / (size - 1); int div = size - 1;
checkArgument(div != 0);
var avgExVal = (double) (sum - val) / div;
if (Math.abs(avgExVal - val) / avgExVal > outlier) { if (Math.abs(avgExVal - val) / avgExVal > outlier) {
element.remove(); element.remove();
break; break;

View File

@ -335,37 +335,42 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) { private void setSuggestedSecurityDeposit(PaymentAccount paymentAccount) {
var minSecurityDeposit = preferences.getBuyerSecurityDepositAsPercent(getPaymentAccount()); var minSecurityDeposit = preferences.getBuyerSecurityDepositAsPercent(getPaymentAccount());
if (getTradeCurrency() == null) { try {
setBuyerSecurityDeposit(minSecurityDeposit, false); if (getTradeCurrency() == null) {
return; setBuyerSecurityDeposit(minSecurityDeposit, false);
} return;
// Get average historic prices over for the prior trade period equaling the lock time }
var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset()); // Get average historic prices over for the prior trade period equaling the lock time
var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000); var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset());
var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000);
.filter(e -> e.getCurrencyCode().equals(getTradeCurrency().getCode())) var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getTradeDate().compareTo(startDate) >= 0) .filter(e -> e.getCurrencyCode().equals(getTradeCurrency().getCode()))
.sorted(Comparator.comparing(TradeStatistics2::getTradeDate)) .filter(e -> e.getTradeDate().compareTo(startDate) >= 0)
.collect(Collectors.toList()); .sorted(Comparator.comparing(TradeStatistics2::getTradeDate))
var movingAverage = new MathUtils.MovingAverage(10, 0.2); .collect(Collectors.toList());
double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE}; var movingAverage = new MathUtils.MovingAverage(10, 0.2);
sortedRangeData.forEach(e -> { double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE};
var price = e.getTradePrice().getValue(); sortedRangeData.forEach(e -> {
movingAverage.next(price).ifPresent(val -> { var price = e.getTradePrice().getValue();
if (val < extremes[0]) extremes[0] = val; movingAverage.next(price).ifPresent(val -> {
if (val > extremes[1]) extremes[1] = val; if (val < extremes[0]) extremes[0] = val;
if (val > extremes[1]) extremes[1] = val;
});
}); });
}); var min = extremes[0];
var min = extremes[0]; var max = extremes[1];
var max = extremes[1]; if (min == 0d || max == 0d) {
if (min == 0d || max == 0d) { setBuyerSecurityDeposit(minSecurityDeposit, false);
setBuyerSecurityDeposit(minSecurityDeposit, false); return;
return; }
// Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit
var suggestedSecurityDeposit =
Math.min(2 * (max - min) / max, Restrictions.getMaxBuyerSecurityDepositAsPercent());
buyerSecurityDeposit.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit));
} catch (Throwable t) {
log.error(t.toString());
buyerSecurityDeposit.set(minSecurityDeposit);
} }
// Suggested deposit is double the trade range over the previous lock time period, bounded by min/max deposit
var suggestedSecurityDeposit =
Math.min(2 * (max - min) / max, Restrictions.getMaxBuyerSecurityDepositAsPercent());
buyerSecurityDeposit.set(Math.max(suggestedSecurityDeposit, minSecurityDeposit));
} }