diff --git a/Indicators/AutoRegressiveIntegratedMovingAverage.cs b/Indicators/AutoRegressiveIntegratedMovingAverage.cs index 2346ad278886..faff213cd634 100644 --- a/Indicators/AutoRegressiveIntegratedMovingAverage.cs +++ b/Indicators/AutoRegressiveIntegratedMovingAverage.cs @@ -35,6 +35,8 @@ public class AutoRegressiveIntegratedMovingAverage : TimeSeriesIndicator { private List _residuals; private readonly bool _intercept; + private bool _loggedOnceInMovingAverageStep; + private bool _loggedOnceInAutoRegressiveStep; private readonly RollingWindow _rollingData; /// @@ -53,6 +55,12 @@ public class AutoRegressiveIntegratedMovingAverage : TimeSeriesIndicator /// private readonly int _maOrder; + /// + /// Whether or not to handle potential exceptions, returning a zero value. I.e, the values + /// provided as input are not valid by the Normal Equations direct regression method + /// + public bool HandleExceptions { get; set; } = true; + /// /// Fitted AR parameters (φ terms). /// @@ -101,7 +109,7 @@ public class AutoRegressiveIntegratedMovingAverage : TimeSeriesIndicator /// Difference order (d) -- defines how many times to difference the model before fitting parameters. /// MA order -- defines the number of past values to consider in the MA component of the model. /// Size of the rolling series to fit onto - /// Whether ot not to include the intercept term + /// Whether or not to include the intercept term public AutoRegressiveIntegratedMovingAverage( string name, int arOrder, @@ -261,8 +269,40 @@ private void MovingAverageStep(double[][] lags, double[] data, double errorMa) appendedData.Add(doubles.ToArray()); } - var maFits = Fit.MultiDim(appendedData.ToArray(), data.Skip(_maOrder).ToArray(), + double[] maFits = default; + if (HandleExceptions) + { + try + { + maFits = Fit.MultiDim(appendedData.ToArray(), data.Skip(_maOrder).ToArray(), + method: DirectRegressionMethod.NormalEquations, intercept: _intercept); + } + catch (Exception ex) + { + if (!_loggedOnceInMovingAverageStep) + { + Logging.Log.Error($"AutoRegressiveIntegratedMovingAverage.MovingAverageStep(): {ex.Message}"); + _loggedOnceInMovingAverageStep = true; + } + + // The method Fit.MultiDim takes the appendedData array of mxn(m rows, n columns), computes its + // transpose of size nxm, and then multiplies the tranpose with the original matrix, so the + // resultant matrix is of size nxn. Then a linear system Ax=b is solved where A is the + // aforementioned matrix and b is the data. Thus, the size of the response x is n + // + // 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); + maFits = new double[size]; + } + } + else + { + maFits = Fit.MultiDim(appendedData.ToArray(), data.Skip(_maOrder).ToArray(), method: DirectRegressionMethod.NormalEquations, intercept: _intercept); + } + for (var i = _maOrder; i < data.Length; i++) // Calculate the error assoc. with model. { var paramVector = _intercept @@ -297,9 +337,39 @@ private void MovingAverageStep(double[][] lags, double[] data, double errorMa) private void AutoRegressiveStep(double[][] lags, double[] data, double errorAr) { double[] arFits; - // The function (lags[time][lagged X]) |---> ΣᵢφᵢXₜ₋ᵢ - arFits = Fit.MultiDim(lags, data.Skip(_arOrder).ToArray(), - method: DirectRegressionMethod.NormalEquations); + if (HandleExceptions) + { + try + { + // The function (lags[time][lagged X]) |---> ΣᵢφᵢXₜ₋ᵢ + arFits = Fit.MultiDim(lags, data.Skip(_arOrder).ToArray(), + method: DirectRegressionMethod.NormalEquations); + } + catch (Exception ex) + { + if (!_loggedOnceInAutoRegressiveStep) + { + Logging.Log.Error($"AutoRegressiveIntegratedMovingAverage.AutoRegressiveStep(): {ex.Message}"); + _loggedOnceInAutoRegressiveStep = true; + } + + // The method Fit.MultiDim takes the lags array of mxn(m rows, n columns), computes its + // transpose of size nxm, and then multiplies the tranpose with the original matrix, so the + // resultant matrix is of size nxn. Then a linear system Ax=b is solved where A is the + // 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; + arFits = new double[size]; + } + } + else + { + // The function (lags[time][lagged X]) |---> ΣᵢφᵢXₜ₋ᵢ + arFits = Fit.MultiDim(lags, data.Skip(_arOrder).ToArray(), + method: DirectRegressionMethod.NormalEquations); + } + var fittedVec = Vector.Build.Dense(arFits); for (var i = 0; i < data.Length; i++) // Calculate the error assoc. with model. diff --git a/Tests/Indicators/AutoregressiveIntegratedMovingAverageTests.cs b/Tests/Indicators/AutoregressiveIntegratedMovingAverageTests.cs index 81a17f7dbe00..f2d2c4cc8269 100644 --- a/Tests/Indicators/AutoregressiveIntegratedMovingAverageTests.cs +++ b/Tests/Indicators/AutoregressiveIntegratedMovingAverageTests.cs @@ -98,13 +98,6 @@ public void ExpectedDifferenceFromExternal() Assert.LessOrEqual(1.19542348709062, betweenMethods.ToDoubleArray().StandardDeviation()); // Std. Dev } - [Test] - public override void WorksWithLowValues() - { - // Since this indicator uses the least squares fitting method, if the matrix computed with the input - // values is not positive definite it will fail. Thus we omit this test for this indicator. - } - protected override IndicatorBase CreateIndicator() { var ARIMA = new AutoRegressiveIntegratedMovingAverage("ARIMA", 1, 0, 1, 50);