Files
2026-02-10 21:33:16 -08:00

75 lines
2.5 KiB
C#

using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// MAPE: Mean Absolute Percentage Error
/// </summary>
/// <remarks>
/// MAPE measures the average absolute percentage error between actual and predicted values.
/// It expresses accuracy as a percentage, making it scale-independent.
///
/// Formula:
/// MAPE = (100/n) * Σ|((actual - predicted) / actual)|
///
/// Key properties:
/// - Scale-independent (expressed as percentage)
/// - Cannot be calculated when actual = 0
/// - Asymmetric: penalizes under-predictions more than over-predictions
/// - Undefined for zero actual values
/// </remarks>
[SkipLocalsInit]
public sealed class Mape : BiInputIndicatorBase
{
private const double Epsilon = 1e-10;
/// <summary>
/// Creates MAPE with specified period.
/// </summary>
/// <param name="period">Number of values to average (must be > 0)</param>
public Mape(int period) : base(period, $"Mape({period})") { }
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override double ComputeError(double actual, double predicted)
{
// Avoid division by zero - use small epsilon if actual is zero
double divisor = Math.Abs(actual) < Epsilon ? Epsilon : actual;
return 100.0 * Math.Abs((actual - predicted) / divisor);
}
/// <summary>
/// Calculates MAPE for entire series.
/// </summary>
public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
/// <summary>
/// Batch calculation using percentage error computation with rolling mean.
/// </summary>
public static void Batch(ReadOnlySpan<double> actual, ReadOnlySpan<double> predicted, Span<double> output, int period)
{
ValidateBatchInputs(actual, predicted, output, period);
int len = actual.Length;
if (len == 0)
{
return;
}
const int StackAllocThreshold = 256;
Span<double> percentErrors = len <= StackAllocThreshold
? stackalloc double[len]
: new double[len];
ErrorHelpers.ComputePercentageErrors(actual, predicted, percentErrors, Epsilon);
ErrorHelpers.ApplyRollingMean(percentErrors, output, period);
}
public static (TSeries Results, Mape Indicator) Calculate(TSeries actual, TSeries predicted, int period)
{
var indicator = new Mape(period);
TSeries results = Batch(actual, predicted, period);
return (results, indicator);
}
}