diff --git a/Indicators/AutoRegressiveIntegratedMovingAverage.cs b/Indicators/AutoRegressiveIntegratedMovingAverage.cs index faff213cd634..dd5fe5a75573 100644 --- a/Indicators/AutoRegressiveIntegratedMovingAverage.cs +++ b/Indicators/AutoRegressiveIntegratedMovingAverage.cs @@ -262,10 +262,16 @@ private void MovingAverageStep(double[][] lags, double[] data, double errorMa) { var appendedData = new List(); var laggedErrors = LaggedSeries(_maOrder, _residuals.ToArray()); - for (var i = 0; i < laggedErrors.Length; i++) + // lags[i] is the AR row for time i + _arOrder and laggedErrors[j] the MA row for + // time j + _maOrder, so the two are only the same row when the orders match. + // Walking laggedErrors and indexing lags by the same counter ran off the end of + // lags whenever _arOrder > _maOrder, and paired rows from different times when + // it was the other way round. + var offset = Math.Max(_arOrder, _maOrder); + for (var t = offset; t < data.Length; t++) { - var doubles = lags[i].ToList(); - doubles.AddRange(laggedErrors[i]); + var doubles = lags[t - _arOrder].ToList(); + doubles.AddRange(laggedErrors[t - _maOrder]); appendedData.Add(doubles.ToArray()); } @@ -274,7 +280,7 @@ private void MovingAverageStep(double[][] lags, double[] data, double errorMa) { try { - maFits = Fit.MultiDim(appendedData.ToArray(), data.Skip(_maOrder).ToArray(), + maFits = Fit.MultiDim(appendedData.ToArray(), data.Skip(offset).ToArray(), method: DirectRegressionMethod.NormalEquations, intercept: _intercept); } catch (Exception ex) @@ -293,22 +299,24 @@ private void MovingAverageStep(double[][] lags, double[] data, double errorMa) // It's worth saying that if intercept flag is set to true, the number of columns of the initial // matrix (appendedData) is increased in one. For more information, please see the implementation // of Fit.MultiDim() method (Ctrl + right click) - var size = appendedData.ToArray()[0].Length + (_intercept ? 1 : 0); + // Each row is _arOrder AR lags followed by _maOrder lagged errors, which + // is the width even when there are no rows at all to read it off. + var size = _arOrder + _maOrder + (_intercept ? 1 : 0); maFits = new double[size]; } } else { - maFits = Fit.MultiDim(appendedData.ToArray(), data.Skip(_maOrder).ToArray(), + maFits = Fit.MultiDim(appendedData.ToArray(), data.Skip(offset).ToArray(), method: DirectRegressionMethod.NormalEquations, intercept: _intercept); } - for (var i = _maOrder; i < data.Length; i++) // Calculate the error assoc. with model. + for (var i = offset; i < data.Length; i++) // Calculate the error assoc. with model. { var paramVector = _intercept ? Vector.Build.Dense(maFits.Skip(1).ToArray()) : Vector.Build.Dense(maFits); - var residual = data[i] - Vector.Build.Dense(appendedData[i - _maOrder]).DotProduct(paramVector); + var residual = data[i] - Vector.Build.Dense(appendedData[i - offset]).DotProduct(paramVector); errorMa += Math.Pow(residual, 2); } @@ -359,7 +367,9 @@ private void AutoRegressiveStep(double[][] lags, double[] data, double errorAr) // aforementioned matrix and b is the data. Thus, the size of the response x is n // // For more information, please see the implementation of Fit.MultiDim() method (Ctrl + right click) - var size = lags.ToArray()[0].Length; + // Each row holds _arOrder lags, and reading that width off row zero threw + // when the fit had failed because there were no rows at all. + var size = _arOrder; arFits = new double[size]; } } diff --git a/Tests/Indicators/AutoregressiveIntegratedMovingAverageTests.cs b/Tests/Indicators/AutoregressiveIntegratedMovingAverageTests.cs index f2d2c4cc8269..0cca88629ec9 100644 --- a/Tests/Indicators/AutoregressiveIntegratedMovingAverageTests.cs +++ b/Tests/Indicators/AutoregressiveIntegratedMovingAverageTests.cs @@ -53,6 +53,22 @@ public override void ComparesAgainstExternalDataAfterReset() (ind, expected) => Assert.AreEqual(expected, (double) ARIMA.Current.Value, 10d)); } + [TestCase(2, 0, 1)] + [TestCase(3, 1, 1)] + [TestCase(4, 0, 2)] + public void AcceptsAnAutoRegressiveOrderAboveTheMovingAverageOrder(int arOrder, int diffOrder, int maOrder) + { + var arima = new AutoRegressiveIntegratedMovingAverage(arOrder, diffOrder, maOrder, 50, true); + var reference = new DateTime(2020, 1, 1); + + for (var i = 0; i < 60; i++) + { + arima.Update(reference.AddDays(i), 100m + (decimal)Math.Sin(i / 3d) * 5m); + } + + Assert.IsTrue(arima.IsReady); + } + [Test] public void PredictionErrorAgainstExternalData() {