/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.covariance;
import java.util.Iterator;
import org.apache.commons.lang.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.analytics.financial.timeseries.returns.ContinuouslyCompoundedTimeSeriesReturnCalculator;
import com.opengamma.analytics.financial.timeseries.returns.TimeSeriesReturnCalculator;
import com.opengamma.timeseries.date.localdate.LocalDateDoubleTimeSeries;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.CalculationMode;
/**
*
* Exponentially weighted moving average (EWMA) volatility calculations put
* variable weight on the values in a time series. The weight is controlled by
* a parameter $\lambda$ which can take any positive value: in most markets,
* the most suitable range is between 0.75 and 1.
* <p>
* The exponential moving average of a time series is given by:
* $$
* \begin{eqnarray*}
* \frac{x_{t-1} + \lambda x_{t-2} + \lambda^2 x_{t-3} + \dots + \lambda^{n-1} x_{t-n}}{1 + \lambda + \lambda^2 + \dots + \lambda^{n-1}}
* \end{eqnarray*}
* $$
* where $x_i$ is the $i^\text{th}$ value in the time series and $\lambda$ is
* the weight.
* <p>
* The exponential weighted volatility is:
* $$
* \begin{eqnarray*}
* \sigma_t = \sqrt{\lambda \sigma_{t-1}^2 + (1 - \lambda)r_t^2}
* \end{eqnarray*}
* $$
* where $\sigma_{t-1}$ is the previous volatility calculation and $r_t$ is the
* continuously compounded return over a single period. As with other
* historical volatility calculations, the volatility can be annualized by
* scaling by the square root of the number of periods in a year.
*/
public class ExponentialWeightedMovingAverageHistoricalVolatilityCalculator extends HistoricalVolatilityCalculator {
/** The logger */
private static final Logger s_logger = LoggerFactory.getLogger(ExponentialWeightedMovingAverageHistoricalVolatilityCalculator.class);
/** The return calculator */
private final TimeSeriesReturnCalculator _returnCalculator;
/** Lambda, the volatility weighting parameter */
private final double _lambda;
/** Lambda minus one */
private final double _lambdaM1;
/**
* Although the return calculator can be any {@link TimeSeriesReturnCalculator}, to obtain correct results a {@link ContinuouslyCompoundedTimeSeriesReturnCalculator} should be
* used. The calculation mode is set to be the default (strict). Although the weight parameter can take any positive value, for most use the range should be $\lambda < 1$;
* if a value higher outside of this range is used then greater weight will be placed on older return values.
* @param lambda The weight parameter, not negative
* @param returnCalculator The return calculator, not null
*/
public ExponentialWeightedMovingAverageHistoricalVolatilityCalculator(final double lambda, final TimeSeriesReturnCalculator returnCalculator) {
this(lambda, returnCalculator, getDefaultCalculationMode());
}
/**
* Although the return calculator can be any {@link TimeSeriesReturnCalculator}, to obtain correct results a {@link ContinuouslyCompoundedTimeSeriesReturnCalculator} should be
* used. Although the weight parameter can take any positive value, for most use the range should be $\lambda < 1$; if a value higher outside of this range is used then
* greater weight will be placed on older return values.
* @param lambda The weight parameter, not negative
* @param returnCalculator The return calculator, not null
* @param mode The calculation mode, not null
*/
public ExponentialWeightedMovingAverageHistoricalVolatilityCalculator(final double lambda, final TimeSeriesReturnCalculator returnCalculator, final CalculationMode mode) {
super(mode);
ArgumentChecker.notNull(returnCalculator, "return calculator");
ArgumentChecker.notNull(mode, "calculation mode");
ArgumentChecker.notNegative(lambda, "lambda");
if (lambda > 1) {
s_logger.warn("Weight for EWMA series is greater than one: this is probably not what was intended");
}
_lambda = lambda;
_lambdaM1 = 1 - lambda;
_returnCalculator = returnCalculator;
}
/**
* @param x The array of price time series. The first time series should be the price; any other arrays are assumed to be a timeseries of dividend payments.
* @return The exponential weighted historical volatility
* @throws IllegalArgumentException If x is null, empty or if the first element of the array is null; if the number of values in the time series is less than three; if the
* dates in the different time series do not coincide
*/
@Override
public Double evaluate(final LocalDateDoubleTimeSeries... x) {
testTimeSeries(x, 3);
final LocalDateDoubleTimeSeries returnTS = _returnCalculator.evaluate(x);
final Iterator<Double> iter = returnTS.valuesIterator();
double returnValue = iter.next();
double variance = returnValue * returnValue;
while (iter.hasNext()) {
returnValue = iter.next();
variance = _lambda * variance + _lambdaM1 * returnValue * returnValue;
}
return Math.sqrt(variance);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
long temp;
temp = Double.doubleToLongBits(_lambda);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((_returnCalculator == null) ? 0 : _returnCalculator.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ExponentialWeightedMovingAverageHistoricalVolatilityCalculator other = (ExponentialWeightedMovingAverageHistoricalVolatilityCalculator) obj;
if (Double.doubleToLongBits(_lambda) != Double.doubleToLongBits(other._lambda)) {
return false;
}
return ObjectUtils.equals(_returnCalculator, other._returnCalculator);
}
}