using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MarketData.Numerical { public class LeastSquaresResult { public double[] LeastSquares { get; set; } public double YIntercept { get; set; } public double XIntercept { get; set; } public double Slope { get; set; } public void Extend(int itemCount, double slope) // extend the result set by itemCount by applying the slope to subsequent values { if (null == LeastSquares || 0 == LeastSquares.Length) return; double[] extendedItems = new double[itemCount + LeastSquares.Length]; Array.Copy(LeastSquares, 0, extendedItems, itemCount, LeastSquares.Length); for (int index = itemCount - 1; index >= 0; index--) { extendedItems[index] = extendedItems[index + 1] + slope; } LeastSquares = extendedItems; } } public class LeastSquaresResultWithR2 : LeastSquaresResult { public double RSquared { get; set; } } // ********************************************************************************************************************************************** public class LeastSquaresHelper { private LeastSquaresHelper() { } // This version reverses the slope because the input data is in reverse order // The input data should be in the following manner observations[count-1]=earliest data point, observations[0]=most recent data point // This version should be retained as-is because it is being referenced in models etc., public static LeastSquaresResult CalculateLeastSquares(double[] observations) { try { LeastSquaresResult leastSquaresResult = new LeastSquaresResult(); double[] xSeries = new double[observations.Length]; double[] xMinusXMean = new double[observations.Length]; double[] yMinusYMean = new double[observations.Length]; double[] meanProduct = new double[observations.Length]; double[] xMinusXMeanSquared = new double[observations.Length]; double[] leastSquares = new double[observations.Length]; double xMean = double.NaN; double yMean = double.NaN; double slope = double.NaN; double yIntercept = double.NaN; for (int index = 0; index < xSeries.Length; index++) xSeries[index] = index + 1; xMean = Numerics.Mean(ref xSeries); yMean = Numerics.Mean(ref observations); for (int index = 0; index < observations.Length; index++) { xMinusXMean[index] = xSeries[index] - xMean; yMinusYMean[index] = observations[index] - yMean; meanProduct[index] = xMinusXMean[index] * yMinusYMean[index]; xMinusXMeanSquared[index] = xMinusXMean[index] * xMinusXMean[index]; } slope = Numerics.Sum(ref meanProduct) / Numerics.Sum(ref xMinusXMeanSquared); yIntercept = Numerics.YIntercept(xMean, yMean, slope); for (int index = 0; index < leastSquares.Length; index++) leastSquares[index] = slope * xSeries[index] + yIntercept; leastSquaresResult.LeastSquares = leastSquares; leastSquaresResult.YIntercept = yIntercept; leastSquaresResult.XIntercept = xMean; leastSquaresResult.Slope = slope * -1.00; return leastSquaresResult; } catch (Exception exception) { MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString()); return null; } } // This version uses the true slope whereas the version above reverses the slope due to the arrangement of the input data // The input data should be in the following manner observations[0]=earliest data point, observations[count-1]=most recent data point public static LeastSquaresResultWithR2 CalculateLeastSquaresWithR2(double[] observations) { try { LeastSquaresResultWithR2 leastSquaresResultWithR2 = new LeastSquaresResultWithR2(); double[] xSeries = new double[observations.Length]; double[] xMinusXMean = new double[observations.Length]; double[] yMinusYMean = new double[observations.Length]; double[] meanProduct = new double[observations.Length]; double[] xMinusXMeanSquared = new double[observations.Length]; double[] leastSquares = new double[observations.Length]; double xMean = double.NaN; double yMean = double.NaN; double slope = double.NaN; double yIntercept = double.NaN; for (int index = 0; index < xSeries.Length; index++) xSeries[index] = index + 1; xMean = Numerics.Mean(ref xSeries); yMean = Numerics.Mean(ref observations); for (int index = 0; index < observations.Length; index++) { xMinusXMean[index] = xSeries[index] - xMean; yMinusYMean[index] = observations[index] - yMean; meanProduct[index] = xMinusXMean[index] * yMinusYMean[index]; xMinusXMeanSquared[index] = xMinusXMean[index] * xMinusXMean[index]; } slope = Numerics.Sum(ref meanProduct) / Numerics.Sum(ref xMinusXMeanSquared); yIntercept = Numerics.YIntercept(xMean, yMean, slope); for (int index = 0; index < leastSquares.Length; index++) leastSquares[index] = slope * xSeries[index] + yIntercept; leastSquaresResultWithR2.LeastSquares = leastSquares; leastSquaresResultWithR2.YIntercept = yIntercept; leastSquaresResultWithR2.XIntercept = xMean; leastSquaresResultWithR2.Slope = slope; // calculate the R2 double[] estimated = new double[observations.Length]; double[] estimatedLessMeanSquared = new double[observations.Length]; double sumOfEstimatedLessMeanSquared = 0.00; double sumofsquares = 0.00; foreach (double observation in observations) sumofsquares += Math.Pow(observation - yMean, 2); for (int index = 0; index < observations.Length; index++) estimated[index] = yIntercept + (slope * (index + 1)); for (int index = 0; index < observations.Length; index++) estimatedLessMeanSquared[index] = Math.Pow(estimated[index] - yMean, 2); foreach (double value in estimatedLessMeanSquared) sumOfEstimatedLessMeanSquared += value; leastSquaresResultWithR2.RSquared = sumOfEstimatedLessMeanSquared / sumofsquares; return leastSquaresResultWithR2; } catch (Exception exception) { MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString()); return null; } } } }