/// <summary> /// Returns the sample variance of an array. /// </summary> /// <param name="array">Array of data for which we are calculating the variance. For time series, the last element (index = n-1), is the most recent.</param> /// <param name="decayFactor">In most applications, the decay factor is between 0 and 1. Weigth on the last element in array is 1.0, the 2nd to last element d, 3rd to last d^2, ...</param> public static double Variance(double[] array, double decayFactor) { if (array.Length == 1) { return(0.0); } int n = array.Length; double v = 0.0; double d = 1.0; for (int i = 0; i < n; i++) { v += d * array[n - 1 - i] * array[n - 1 - i]; d *= decayFactor; } double m = Mean(array, decayFactor); double s1 = GeometricSeries.SumOfGeometricSeries(decayFactor, n); double s2 = GeometricSeries.SumOfGeometricSeries(decayFactor * decayFactor, n); double a = s1 / (s1 * s1 - s2); double b = s1 * a; v = a * v - b * m * m; //Occasionally floating-point error will cause v to be less than zero when it should be exactly zero or very close to zero. if (v < 0) { bool allEqual = Tools.ArrayAllEqual(array); return(allEqual ? 0 : double.Epsilon); } return(v); }
/// <summary> /// Calculates the sample variance of an array using a rolling window. /// The first element (index = 0) of the output array is the variance of points [0] to [length - 1] in 'array', /// the second element of the output array is the variance of points [1] to [length] in 'array', and so on. /// </summary> /// <param name="array">Array of data for which we are calculating the variance. For time series, the last element (index = n-1), is the most recent.</param> /// <param name="decayFactor">In most applications, the decay factor is between 0 and 1. Weigth on the last element in array is 1.0, the 2nd to last element d, 3rd to last d^2, ...</param> /// <param name="length">>Length of rolling window length. Must be less than or equal to the lenght of the array of data.</param> /// <returns></returns> public static double[] RollingVariance(double[] array, double decayFactor, int length) { if (array.Length < length) { throw new ArgumentException("Not enought points in array. Number of points in array must be greater than or equal to length."); } double s1 = GeometricSeries.SumOfGeometricSeries(decayFactor, length); double s2 = GeometricSeries.SumOfGeometricSeries(decayFactor * decayFactor, length); double c = 1 / (s1 * s1 - s2); double a = s1 * c; double z1 = 0.0, z2 = 0.0; double d = 1.0; for (int i = 0; i < length; i++) { z1 += d * array[length - 1 - i] * array[length - 1 - i]; z2 += d * array[length - 1 - i]; d *= decayFactor; } double dToLength = Math.Pow(decayFactor, length); double[] vArray = new double[array.Length - length + 1]; vArray[0] = a * z1 - c * z2 * z2; for (int i = 1; i < vArray.Length; i++) { z1 = decayFactor * z1 + array[length - 1 + i] * array[length - 1 + i] - dToLength * array[i - 1] * array[i - 1]; z2 = decayFactor * z2 + array[length - 1 + i] - dToLength * array[i - 1]; vArray[i] = a * z1 - c * z2 * z2; } return(vArray); }
/// <summary> /// Hybrid VaR is based on historical data, but weights more recent data more heavily. /// </summary> /// <param name="returnArray">Historical returns from which VaR is to be calculated. The last value, with the highest index is assumed to be the most recent data point.</param> /// <param name="windowLength">Length of the VaR window. The number of historical returns that will be used to calculate the VaR.</param> /// <param name="confidenceLevel">VaR confidence level. 95% and 99% are typical values. If confidenceLevel is 95% then we expect 95% of days to be better (have a more positive return) than the VaR.</param> /// <param name="decayFactor">For a decay factor d, the most recent data point has weight 1, the second d, the third d^2, ...</param> public static double HybridValueAtRisk(double[] returnArray, int windowLength, double confidenceLevel, double decayFactor) { if (returnArray.Length < windowLength) { throw new ArgumentException(string.Format("returnArray does not have enough data points to calcualte VaR for nDay = {0}.", windowLength)); } if (confidenceLevel < 0.50) { double[] negReturnArray = new double[returnArray.Length]; for (int j = 0; j < returnArray.Length; j++) { negReturnArray[j] = -returnArray[j]; } return(-HybridValueAtRisk(negReturnArray, windowLength, 1.0 - confidenceLevel, decayFactor)); } //initialize VaR to the minimum return double var = double.MaxValue; for (int j = returnArray.Length - windowLength; j < returnArray.Length; j++) { if (returnArray[j] < var) { var = returnArray[j]; } } //Loop through all data to find the minimum return, then the second worst, ... //This is generally faster than sorting when confidenceLevel is high, becaus we only need to identify a few minimums. double totalWt = GeometricSeries.SumOfGeometricSeries(decayFactor, windowLength); double varLimitWt = (1 - confidenceLevel) * totalWt; double cummWt = 0; List <int> alreadyHaveMinIndexList = new List <int>(); for (int i = 0; i < windowLength; i++) { double min = double.MaxValue; int minIndex = -1; for (int j = returnArray.Length - windowLength; j < returnArray.Length; j++) { if (returnArray[j] >= var && returnArray[j] < min && alreadyHaveMinIndexList.Contains(j) == false) { min = returnArray[j]; minIndex = j; } } alreadyHaveMinIndexList.Add(minIndex); double wt = Math.Pow(decayFactor, returnArray.Length - minIndex - 1); cummWt += wt; if (cummWt >= varLimitWt) { break; } var = min; } return(-var); //By convention VaR is quoted as a positive value, even though it corresponds to a loss. }
/// <summary> /// Returns the sample covariance between two arrays. /// Arrays should be of equal length, and contain more than one element. /// </summary> /// <param name="array1"></param> /// <param name="array2"></param> /// <param name="decayFactor">In most applications, the decay factor is between 0 and 1. Weigth on the last element in arrays is 1.0, the 2nd to last element d, 3rd to last d^2, ...</param> public static double Covariance(double[] array1, double[] array2, double decayFactor) { if (array1.Length != array2.Length) { throw new ArgumentException("Arrays must be the same length"); } if (Tools.ArrayAllEqual(array1) || Tools.ArrayAllEqual(array2)) { return(0.0); } int n = array1.Length; if (n == 1) { return(double.NaN); } double covar = 0.0; double d = 1.0; for (int i = 0; i < n; i++) { covar += d * array1[n - 1 - i] * array2[n - 1 - i]; d *= decayFactor; } double m1 = Mean(array1, decayFactor); double m2 = Mean(array2, decayFactor); double S1 = GeometricSeries.SumOfGeometricSeries(decayFactor, n); double S2 = GeometricSeries.SumOfGeometricSeries(decayFactor * decayFactor, n); double A = S1 / (S1 * S1 - S2); double B = S1 * A; covar = A * covar - B * m1 * m2; return(covar); }