using System.Buffers; using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// MAE: Mean Absolute Error /// /// /// MAE measures the average magnitude of errors between paired observations, /// without considering their direction. It is the mean of the absolute differences /// between actual and predicted values. /// /// Formula: /// MAE = (1/n) * Σ|actual - predicted| /// /// Uses a RingBuffer for O(1) streaming updates with running sum. /// /// Key properties: /// - Always non-negative (MAE ≥ 0) /// - Same units as the original data /// - Less sensitive to outliers than MSE/RMSE /// - MAE = 0 indicates perfect prediction /// [SkipLocalsInit] public sealed class Mae : BiInputIndicatorBase { /// /// Creates MAE with specified period. /// /// Number of values to average (must be > 0) public Mae(int period) : base(period, $"Mae({period})") { } /// /// Computes absolute error: |actual - predicted| /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override double ComputeError(double actual, double predicted) => Math.Abs(actual - predicted); /// /// Calculates MAE for the entire series pair. /// /// Actual values series /// Predicted values series /// MAE period /// MAE series public static TSeries Batch(TSeries actual, TSeries predicted, int period) => CalculateImpl(actual, predicted, period, Batch); /// /// Calculates MAE in-place using pre-allocated spans. /// Uses SIMD acceleration when available. /// /// Actual values /// Predicted values /// Output span (must be same length as inputs) /// MAE period (must be > 0) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Batch(ReadOnlySpan actual, ReadOnlySpan predicted, Span output, int period) { ValidateBatchInputs(actual, predicted, output, period); if (actual.Length == 0) { return; } // Allocate temporary buffer for absolute errors const int StackAllocThreshold = 256; int len = actual.Length; if (len <= StackAllocThreshold) { Span absErrors = stackalloc double[len]; ErrorHelpers.ComputeAbsoluteErrors(actual, predicted, absErrors); ErrorHelpers.ApplyRollingMean(absErrors, output, period); } else { double[] rented = ArrayPool.Shared.Rent(len); try { Span absErrors = rented.AsSpan(0, len); ErrorHelpers.ComputeAbsoluteErrors(actual, predicted, absErrors); ErrorHelpers.ApplyRollingMean(absErrors, output, period); } finally { ArrayPool.Shared.Return(rented); } } } public static (TSeries Results, Mae Indicator) Calculate(TSeries actual, TSeries predicted, int period) { var indicator = new Mae(period); TSeries results = Batch(actual, predicted, period); return (results, indicator); } }