Skip to content

Commit

Permalink
ARIMA indicator error handling (#8218)
Browse files Browse the repository at this point in the history
* First draft of the solution

* Add comments

* Address requests

* Nit change

* Fix bug
  • Loading branch information
Marinovsky authored Jul 22, 2024
1 parent 97959d5 commit 9a92933
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 12 deletions.
80 changes: 75 additions & 5 deletions Indicators/AutoRegressiveIntegratedMovingAverage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class AutoRegressiveIntegratedMovingAverage : TimeSeriesIndicator
{
private List<double> _residuals;
private readonly bool _intercept;
private bool _loggedOnceInMovingAverageStep;
private bool _loggedOnceInAutoRegressiveStep;
private readonly RollingWindow<double> _rollingData;

/// <summary>
Expand All @@ -53,6 +55,12 @@ public class AutoRegressiveIntegratedMovingAverage : TimeSeriesIndicator
/// </summary>
private readonly int _maOrder;

/// <summary>
/// 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
/// </summary>
public bool HandleExceptions { get; set; } = true;

/// <summary>
/// Fitted AR parameters (φ terms).
/// </summary>
Expand Down Expand Up @@ -101,7 +109,7 @@ public class AutoRegressiveIntegratedMovingAverage : TimeSeriesIndicator
/// <param name="diffOrder">Difference order (d) -- defines how many times to difference the model before fitting parameters.</param>
/// <param name="maOrder">MA order -- defines the number of past values to consider in the MA component of the model.</param>
/// <param name="period">Size of the rolling series to fit onto</param>
/// <param name="intercept">Whether ot not to include the intercept term</param>
/// <param name="intercept">Whether or not to include the intercept term</param>
public AutoRegressiveIntegratedMovingAverage(
string name,
int arOrder,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<IndicatorDataPoint> CreateIndicator()
{
var ARIMA = new AutoRegressiveIntegratedMovingAverage("ARIMA", 1, 0, 1, 50);
Expand Down

0 comments on commit 9a92933

Please sign in to comment.