package name.abuchen.portfolio.math;
import java.time.LocalDate;
import java.util.Objects;
import java.util.function.Predicate;
import name.abuchen.portfolio.util.Interval;
public final class Risk
{
public static class Drawdown
{
private double maxDD;
private Interval maxDDDuration;
private Interval intervalMaxDD;
private Interval recoveryTime;
public Drawdown(double[] values, LocalDate[] dates)
{
double peak = values[0] + 1;
double bottom = values[0] + 1;
LocalDate lastPeakDate = dates[0];
LocalDate lastBottomDate = dates[0];
maxDD = 0;
intervalMaxDD = Interval.of(lastPeakDate, lastPeakDate);
maxDDDuration = Interval.of(lastPeakDate, lastPeakDate);
recoveryTime = Interval.of(lastBottomDate, lastPeakDate);
Interval currentDrawdownDuration = null;
Interval currentRecoveryTime = null;
for (int ii = 0; ii < values.length; ii++)
{
double value = values[ii] + 1;
currentDrawdownDuration = Interval.of(lastPeakDate, dates[ii]);
currentRecoveryTime = Interval.of(lastBottomDate, dates[ii]);
if (value > peak)
{
peak = value;
lastPeakDate = dates[ii];
if (currentDrawdownDuration.isLongerThan(maxDDDuration))
maxDDDuration = currentDrawdownDuration;
if (currentRecoveryTime.isLongerThan(recoveryTime))
recoveryTime = currentRecoveryTime;
// Reset the recovery time calculation, as the recovery is
// now complete
lastBottomDate = dates[ii];
bottom = value;
}
else
{
double drawdown = (peak - value) / peak;
if (drawdown > maxDD)
{
maxDD = drawdown;
intervalMaxDD = Interval.of(lastPeakDate, dates[ii]);
}
}
if (value < bottom)
{
bottom = value;
lastBottomDate = dates[ii];
}
}
// check if current drawdown duration is longer than the max
// drawdown duration currently calculated --> use it because it is
// the longest duration even if we do not know how much longer it
// will get
if (currentDrawdownDuration != null && currentDrawdownDuration.isLongerThan(maxDDDuration))
maxDDDuration = currentDrawdownDuration;
if (currentRecoveryTime != null && currentRecoveryTime.isLongerThan(recoveryTime))
recoveryTime = currentRecoveryTime;
}
public Interval getLongestRecoveryTime()
{
return recoveryTime;
}
public double getMaxDrawdown()
{
return maxDD;
}
public Interval getIntervalOfMaxDrawdown()
{
return intervalMaxDD;
}
public Interval getMaxDrawdownDuration()
{
return maxDDDuration;
}
}
public static class Volatility
{
private final double stdDeviation;
private final double semiDeviation;
public Volatility(double[] returns, Predicate<Integer> filter)
{
Objects.requireNonNull(returns);
double tempStandard = 0;
double tempSemi = 0;
int count = 0;
double averageLogReturn = logAverage(returns, filter);
for (int ii = 0; ii < returns.length; ii++)
{
if (!filter.test(ii))
continue;
double logReturn = Math.log(1 + returns[ii]);
double add = Math.pow(logReturn - averageLogReturn, 2);
tempStandard = tempStandard + add;
count++;
if (logReturn < averageLogReturn)
tempSemi = tempSemi + add;
}
stdDeviation = Math.sqrt(tempStandard / (count - 1) * count);
semiDeviation = Math.sqrt(tempSemi / (count - 1) * count);
}
private double logAverage(double[] returns, Predicate<Integer> filter)
{
double sum = 0;
int count = 0;
for (int ii = 0; ii < returns.length; ii++)
{
if (!filter.test(ii))
continue;
sum += Math.log(1 + returns[ii]);
count++;
}
return sum / count;
}
public double getStandardDeviation()
{
return stdDeviation;
}
public double getSemiDeviation()
{
return semiDeviation;
}
public double getExpectedSemiDeviation()
{
return stdDeviation / Math.sqrt(2);
}
public String getNormalizedSemiDeviationComparison()
{
double expectedSemiDeviation = getExpectedSemiDeviation();
if (expectedSemiDeviation > semiDeviation)
return ">"; //$NON-NLS-1$
else if (expectedSemiDeviation < semiDeviation)
return "<"; //$NON-NLS-1$
return "="; //$NON-NLS-1$
}
}
private Risk()
{}
}