package de.invesdwin.util.math.decimal.stream;
import javax.annotation.concurrent.NotThreadSafe;
import de.invesdwin.util.math.decimal.ADecimal;
import de.invesdwin.util.math.decimal.Decimal;
/**
* http://quant.stackexchange.com/questions/4286/detrending-price-data-for-analysis-of-signal-returns
*
* http://www.automated-trading-system.com/detrending-for-trend-following/
*/
@NotThreadSafe
public class DecimalStreamRelativeDetrending<Y extends ADecimal<Y>>
implements IDecimalStreamAlgorithm<DecimalPoint<Decimal, Y>, DecimalPoint<Decimal, Y>> {
private final DecimalPoint<Decimal, Y> from;
private final DecimalPoint<Decimal, Y> to;
private final Decimal fromX;
private final double fromY;
private final double logAvgChangeYperX;
public DecimalStreamRelativeDetrending(final DecimalPoint<Decimal, Y> from, final DecimalPoint<Decimal, Y> to) {
this.from = from;
this.to = to;
this.fromX = from.getX();
final double xChange = scaleChangeInX(to.getX().subtract(fromX)).getDefaultValue().doubleValueRaw();
if (xChange <= 0D) {
throw new IllegalArgumentException(
"from [" + from + "] -> to [" + to + "] has negative change per x: " + xChange);
}
this.fromY = getY(from).getDefaultValue().doubleValueRaw();
final double toAdjY = getY(to).getDefaultValue().doubleValueRaw();
this.logAvgChangeYperX = Math.log(toAdjY / fromY) / xChange;
}
/**
* logAvgChangeYperX = LOG(toY/fromY) / (toX - fromX)
*
* logDetrendedAdjustment = LOG(curY/fromY) - logAvgChangeYperX*(curX-fromX)
*
* detrendedY = fromY * EXP(logDetrendedProfit)
*/
@Override
public DecimalPoint<Decimal, Y> process(final DecimalPoint<Decimal, Y> value) {
final Decimal curX = value.getX();
final double curY = getY(value).getDefaultValue().doubleValueRaw();
final double changeInX = scaleChangeInX(curX.subtract(fromX)).getDefaultValue().doubleValueRaw();
final double logCurProfit = Math.log(curY / fromY);
final double logMinusProfit = logAvgChangeYperX * changeInX;
final double logDetrendedProfit = logCurProfit - logMinusProfit;
final double detrendedY = fromY * Math.exp(logDetrendedProfit);
return new DecimalPoint<Decimal, Y>(curX, value.getY().fromDefaultValue(new Decimal(detrendedY)));
}
private Y getY(final DecimalPoint<Decimal, Y> value) {
final Y curY = value.getY();
if (curY.isNegativeOrZero()) {
throw new IllegalArgumentException("Current value [" + value
+ "] is negative or zero. Please preprocess the data so this does not happen because we cannot create a logarithm of a negative value.");
}
return curY;
}
/**
* It might be needed to scale a change in X from e.g. milliseconds to minutes in order to make the detrending
* algorithm numerically valid when using double as the underlying decimal implementation.
*/
protected Decimal scaleChangeInX(final Decimal changeInX) {
return changeInX;
}
public DecimalPoint<Decimal, Y> getFrom() {
return from;
}
public DecimalPoint<Decimal, Y> getTo() {
return to;
}
}