mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 17:18:05 +00:00
first iteration
This commit is contained in:
@@ -1,130 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// Huber Loss: A robust error metric that combines squared error for small deviations
|
||||
/// and absolute error for large deviations. This provides a balance between the high
|
||||
/// sensitivity of MSE to outliers and the constant gradient of MAE.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Huber Loss calculation process:
|
||||
/// 1. For each point, calculates error between actual and predicted values
|
||||
/// 2. If absolute error ≤ delta: uses squared error (like MSE)
|
||||
/// 3. If absolute error > delta: uses linear error (like MAE)
|
||||
/// 4. Averages the losses over the period
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Combines benefits of MSE and MAE
|
||||
/// - Less sensitive to outliers than MSE
|
||||
/// - More sensitive to small errors than MAE
|
||||
/// - Differentiable at all points
|
||||
/// - Adjustable via delta parameter
|
||||
///
|
||||
/// Formula:
|
||||
/// For error e = actual - predicted:
|
||||
/// L(e) = 0.5 * e² if |e| ≤ δ
|
||||
/// L(e) = δ * (|e| - 0.5δ) if |e| > δ
|
||||
///
|
||||
/// Sources:
|
||||
/// Peter J. Huber - "Robust Estimation of a Location Parameter"
|
||||
/// https://projecteuclid.org/euclid.aoms/1177703732
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Huber : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
private readonly double _delta;
|
||||
private readonly double _halfDelta;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the loss.</param>
|
||||
/// <param name="delta">The threshold between squared and linear loss (default 1.0).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1 or delta is not positive.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Huber(int period, double delta = 1.0)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(delta, 0);
|
||||
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
_delta = delta;
|
||||
_halfDelta = delta * 0.5;
|
||||
Name = $"Huberloss(period={period}, delta={delta})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the loss.</param>
|
||||
/// <param name="delta">The threshold between squared and linear loss (default 1.0).</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Huber(object source, int period, double delta = 1.0) : this(period, delta)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private double CalculateHuberLoss(double error)
|
||||
{
|
||||
double absError = Math.Abs(error);
|
||||
if (absError <= _delta)
|
||||
{
|
||||
// Squared error for small deviations
|
||||
return 0.5 * error * error;
|
||||
}
|
||||
// Linear error for large deviations
|
||||
return _delta * (absError - _halfDelta);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double huberloss = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumLoss = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
double error = actualValues[i] - predictedValues[i];
|
||||
sumLoss += CalculateHuberLoss(error);
|
||||
}
|
||||
|
||||
huberloss = sumLoss / actualValues.Length;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return huberloss;
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MAE: Mean Absolute Error
|
||||
/// A straightforward error metric that measures the average magnitude of errors
|
||||
/// between predicted and actual values, without considering their direction.
|
||||
/// MAE treats all individual differences equally in the average.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The MAE calculation process:
|
||||
/// 1. Calculates absolute difference between each actual and predicted value
|
||||
/// 2. Sums all absolute differences
|
||||
/// 3. Divides by the number of observations
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Linear scale (all differences weighted equally)
|
||||
/// - Robust to outliers compared to MSE
|
||||
/// - Easy to interpret (same units as data)
|
||||
/// - Constant gradient for optimization
|
||||
/// - Less sensitive to large errors than MSE
|
||||
///
|
||||
/// Formula:
|
||||
/// MAE = (1/n) * Σ|actual - predicted|
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Mean_absolute_error
|
||||
/// https://www.statisticshowto.com/absolute-error/
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mae : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the MAE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mae(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Mae(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the MAE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mae(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double mae = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumAbsoluteError = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumAbsoluteError += Math.Abs(actualValues[i] - predictedValues[i]);
|
||||
}
|
||||
|
||||
mae = sumAbsoluteError / actualValues.Length;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return mae;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MAPD: Mean Absolute Percentage Deviation
|
||||
/// A percentage-based error metric that measures the average absolute percentage
|
||||
/// difference between predicted and actual values. MAPD expresses accuracy as a
|
||||
/// percentage, making it scale-independent and easy to interpret.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The MAPD calculation process:
|
||||
/// 1. Calculates absolute percentage difference for each point
|
||||
/// 2. Sums all absolute percentage differences
|
||||
/// 3. Divides by the number of observations
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-independent (percentage-based)
|
||||
/// - Easy to interpret (0-100% range)
|
||||
/// - Useful for comparing different scales
|
||||
/// - Cannot handle zero actual values
|
||||
/// - Asymmetric (treats over/under predictions differently)
|
||||
///
|
||||
/// Formula:
|
||||
/// MAPD = (1/n) * Σ|((actual - predicted) / actual)|
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Mean_absolute_percentage_error
|
||||
/// https://www.statisticshowto.com/mean-absolute-percentage-error-mape/
|
||||
///
|
||||
/// Note: Also known as MAPE (Mean Absolute Percentage Error) in some contexts
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mapd : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the MAPD.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mapd(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Mapd(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the MAPD.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mapd(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculatePercentageDeviation(double actual, double predicted)
|
||||
{
|
||||
return actual >= double.Epsilon ? Math.Abs((actual - predicted) / actual) : 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double mapd = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumAbsolutePercentageDeviation = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumAbsolutePercentageDeviation += CalculatePercentageDeviation(actualValues[i], predictedValues[i]);
|
||||
}
|
||||
|
||||
mapd = sumAbsolutePercentageDeviation / actualValues.Length;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return mapd;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MAPE: Mean Absolute Percentage Error
|
||||
/// A percentage-based error metric that measures the average absolute percentage
|
||||
/// difference between predicted and actual values. MAPE expresses accuracy as a
|
||||
/// percentage, making it scale-independent and easy to interpret.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The MAPE calculation process:
|
||||
/// 1. Calculates absolute percentage error for each point
|
||||
/// 2. Sums all absolute percentage errors
|
||||
/// 3. Divides by the number of observations
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-independent (percentage-based)
|
||||
/// - Easy to interpret (0-100% range)
|
||||
/// - Useful for comparing different scales
|
||||
/// - Cannot handle zero actual values
|
||||
/// - Asymmetric (treats over/under predictions differently)
|
||||
///
|
||||
/// Formula:
|
||||
/// MAPE = (1/n) * Σ|((actual - predicted) / actual)| * 100%
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Mean_absolute_percentage_error
|
||||
/// https://www.statisticshowto.com/mean-absolute-percentage-error-mape/
|
||||
///
|
||||
/// Note: Also known as MAPD (Mean Absolute Percentage Deviation) in some contexts
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mape : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the MAPE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mape(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Mape(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the MAPE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mape(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculatePercentageError(double actual, double predicted)
|
||||
{
|
||||
return actual >= double.Epsilon ? Math.Abs((actual - predicted) / actual) : 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double mape = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumAbsolutePercentageError = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumAbsolutePercentageError += CalculatePercentageError(actualValues[i], predictedValues[i]);
|
||||
}
|
||||
|
||||
mape = sumAbsolutePercentageError / actualValues.Length;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return mape;
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MASE: Mean Absolute Scaled Error
|
||||
/// A scale-free error metric that compares the mean absolute error of the forecast
|
||||
/// with the mean absolute error of the naive forecast. MASE is particularly useful
|
||||
/// for comparing forecast accuracy across different datasets.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The MASE calculation process:
|
||||
/// 1. Calculates mean absolute error of the forecast
|
||||
/// 2. Calculates mean absolute error of naive forecast (using previous value)
|
||||
/// 3. Divides forecast error by naive forecast error
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-free (independent of data scale)
|
||||
/// - Handles zero values unlike percentage errors
|
||||
/// - Symmetric (treats over/under predictions equally)
|
||||
/// - Easy interpretation (MASE < 1 means better than naive forecast)
|
||||
/// - Robust to outliers
|
||||
///
|
||||
/// Formula:
|
||||
/// MASE = MAE(forecast) / MAE(naive_forecast)
|
||||
/// where naive_forecast[t] = actual[t-1]
|
||||
///
|
||||
/// Sources:
|
||||
/// Rob J. Hyndman - "Another Look at Forecast-Accuracy Metrics for Intermittent Demand"
|
||||
/// https://robjhyndman.com/papers/another-look-at-measures-of-forecast-accuracy/
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mase : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
private readonly CircularBuffer _naiveBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the MASE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mase(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
_naiveBuffer = new CircularBuffer(period);
|
||||
Name = $"Mase(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the MASE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mase(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
_naiveBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
// Naive forecast uses previous actual value
|
||||
if (_actualBuffer.Count > 1)
|
||||
{
|
||||
_naiveBuffer.Add(_actualBuffer.GetSpan()[^2], Input.IsNew);
|
||||
}
|
||||
|
||||
double mase = CalculateMase();
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return mase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the MASE value by comparing forecast error to naive forecast error.
|
||||
/// </summary>
|
||||
/// <returns>The calculated MASE value, or positive infinity if naive error is zero.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private double CalculateMase()
|
||||
{
|
||||
if (_actualBuffer.Count <= 1) return 0;
|
||||
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
ReadOnlySpan<double> naiveValues = _naiveBuffer.GetSpan();
|
||||
|
||||
double sumAbsoluteError = CalculateSumAbsoluteError(actualValues, predictedValues);
|
||||
double naiveForecastError = CalculateNaiveForecastError(actualValues, naiveValues);
|
||||
|
||||
return naiveForecastError >= double.Epsilon ? (sumAbsoluteError / _actualBuffer.Count) / naiveForecastError : double.PositiveInfinity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the sum of absolute errors between actual and predicted values.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSumAbsoluteError(ReadOnlySpan<double> actualValues, ReadOnlySpan<double> predictedValues)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sum += Math.Abs(actualValues[i] - predictedValues[i]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the naive forecast error using the previous value as prediction.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateNaiveForecastError(ReadOnlySpan<double> actualValues, ReadOnlySpan<double> naiveValues)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 1; i < actualValues.Length; i++)
|
||||
{
|
||||
sum += Math.Abs(actualValues[i] - naiveValues[i - 1]);
|
||||
}
|
||||
return sum / (actualValues.Length - 1);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MDA: Mean Directional Accuracy
|
||||
/// A metric that measures how well a forecast predicts the direction of change
|
||||
/// rather than the magnitude. MDA focuses on whether the predicted movement
|
||||
/// (up or down) matches the actual movement.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The MDA calculation process:
|
||||
/// 1. For each consecutive pair of points:
|
||||
/// - Calculate direction of actual change
|
||||
/// - Calculate direction of predicted change
|
||||
/// - Compare directions (match = 1, mismatch = 0)
|
||||
/// 2. Average the directional matches
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-independent (only considers direction)
|
||||
/// - Range is 0 to 1 (easy interpretation)
|
||||
/// - Useful for trend prediction evaluation
|
||||
/// - Ignores magnitude of changes
|
||||
/// - Equal weight to all directional changes
|
||||
///
|
||||
/// Formula:
|
||||
/// MDA = (1/(n-1)) * Σ(sign(actual[t] - actual[t-1]) == sign(pred[t] - pred[t-1]))
|
||||
///
|
||||
/// Sources:
|
||||
/// https://www.sciencedirect.com/science/article/abs/pii/S0169207016000121
|
||||
/// "Evaluating Forecasting Performance" - International Journal of Forecasting
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mda : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the MDA.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mda(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Mda(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the MDA.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mda(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static int CompareDirections(double current, double previous)
|
||||
{
|
||||
return Math.Sign(current - previous);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double mda = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumDirectionalAccuracy = 0;
|
||||
for (int i = 1; i < actualValues.Length; i++)
|
||||
{
|
||||
int actualDirection = CompareDirections(actualValues[i], actualValues[i - 1]);
|
||||
int predictedDirection = CompareDirections(predictedValues[i], predictedValues[i - 1]);
|
||||
sumDirectionalAccuracy += (actualDirection == predictedDirection) ? 1 : 0;
|
||||
}
|
||||
|
||||
mda = sumDirectionalAccuracy / (actualValues.Length - 1);
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return mda;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// ME: Mean Error
|
||||
/// A basic error metric that measures the average difference between actual and
|
||||
/// predicted values. Unlike MAE, it allows positive and negative errors to cancel
|
||||
/// out, making it useful for detecting systematic bias in predictions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The ME calculation process:
|
||||
/// 1. Calculates error (actual - predicted) for each point
|
||||
/// 2. Sums all errors (allowing cancellation)
|
||||
/// 3. Divides by the number of observations
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Same units as input data
|
||||
/// - Can detect systematic bias
|
||||
/// - Positive ME indicates underprediction
|
||||
/// - Negative ME indicates overprediction
|
||||
/// - Errors can cancel out
|
||||
///
|
||||
/// Formula:
|
||||
/// ME = (1/n) * Σ(actual - predicted)
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Mean_signed_difference
|
||||
/// https://www.statisticshowto.com/mean-error/
|
||||
///
|
||||
/// Note: Also known as Mean Bias Error (MBE) or Mean Signed Difference (MSD)
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Me : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the ME.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Me(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Me(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the ME.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Me(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateError(double actual, double predicted)
|
||||
{
|
||||
return actual - predicted;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double me = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumError = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumError += CalculateError(actualValues[i], predictedValues[i]);
|
||||
}
|
||||
|
||||
me = sumError / actualValues.Length;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return me;
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MPE: Mean Percentage Error
|
||||
/// A percentage-based error metric that measures the average percentage difference
|
||||
/// between actual and predicted values. Like ME, it allows positive and negative
|
||||
/// errors to cancel out, but expresses the bias in percentage terms.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The MPE calculation process:
|
||||
/// 1. Calculates percentage error for each point
|
||||
/// 2. Sums all percentage errors (allowing cancellation)
|
||||
/// 3. Divides by the number of observations
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-independent (percentage-based)
|
||||
/// - Can detect systematic bias
|
||||
/// - Positive MPE indicates underprediction
|
||||
/// - Negative MPE indicates overprediction
|
||||
/// - Cannot handle zero actual values
|
||||
/// - Errors can cancel out
|
||||
///
|
||||
/// Formula:
|
||||
/// MPE = (1/n) * Σ((actual - predicted) / actual) * 100%
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Mean_percentage_error
|
||||
/// https://www.statisticshowto.com/mean-percentage-error/
|
||||
///
|
||||
/// Note: Similar to MAPE but allows error cancellation
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mpe : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the MPE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mpe(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Mpe(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the MPE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mpe(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculatePercentageError(double actual, double predicted)
|
||||
{
|
||||
return actual >= double.Epsilon ? (actual - predicted) / actual : 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double mpe = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumPercentageError = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumPercentageError += CalculatePercentageError(actualValues[i], predictedValues[i]);
|
||||
}
|
||||
|
||||
mpe = sumPercentageError / actualValues.Length;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return mpe;
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MSE: Mean Squared Error
|
||||
/// A fundamental error metric that measures the average of squared differences
|
||||
/// between predicted and actual values. MSE heavily penalizes large errors due
|
||||
/// to the squaring operation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The MSE calculation process:
|
||||
/// 1. Calculates error (actual - predicted) for each point
|
||||
/// 2. Squares each error value
|
||||
/// 3. Averages the squared errors
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Heavily penalizes large errors
|
||||
/// - Always non-negative
|
||||
/// - Units are squared (harder to interpret)
|
||||
/// - More sensitive to outliers than MAE
|
||||
/// - Differentiable (useful for optimization)
|
||||
///
|
||||
/// Formula:
|
||||
/// MSE = (1/n) * Σ(actual - predicted)²
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Mean_squared_error
|
||||
/// https://www.statisticshowto.com/probability-and-statistics/statistics-definitions/mean-squared-error/
|
||||
///
|
||||
/// Note: Often used in optimization due to its mathematical properties
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mse : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the MSE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mse(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Mse(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the MSE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mse(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSquaredError(double actual, double predicted)
|
||||
{
|
||||
double error = actual - predicted;
|
||||
return error * error;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double mse = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumSquaredError = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumSquaredError += CalculateSquaredError(actualValues[i], predictedValues[i]);
|
||||
}
|
||||
|
||||
mse = sumSquaredError / actualValues.Length;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return mse;
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// MSLE: Mean Squared Logarithmic Error
|
||||
/// A variation of MSE that operates on log-transformed values. MSLE is particularly
|
||||
/// useful for data with exponential growth or when errors in larger values should
|
||||
/// not be penalized more heavily than errors in smaller values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The MSLE calculation process:
|
||||
/// 1. Adds 1 to both actual and predicted values (to handle zeros)
|
||||
/// 2. Takes natural log of both values
|
||||
/// 3. Calculates squared difference of logs
|
||||
/// 4. Averages the squared differences
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-independent due to log transformation
|
||||
/// - Penalizes underestimates more than overestimates
|
||||
/// - Handles exponential trends well
|
||||
/// - More sensitive to relative differences
|
||||
/// - Can handle zero values (adds 1 before log)
|
||||
///
|
||||
/// Formula:
|
||||
/// MSLE = (1/n) * Σ(log(actual + 1) - log(predicted + 1))²
|
||||
///
|
||||
/// Sources:
|
||||
/// https://scikit-learn.org/stable/modules/model_evaluation.html#mean-squared-logarithmic-error
|
||||
/// https://medium.com/analytics-vidhya/root-mean-square-log-error-rmse-vs-rmlse-935c6cc1802a
|
||||
///
|
||||
/// Note: Often used in cases where target values follow exponential growth
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Msle : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the MSLE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Msle(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Msle(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the MSLE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Msle(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSquaredLogError(double actual, double predicted)
|
||||
{
|
||||
double logActual = Math.Log(actual + 1);
|
||||
double logPredicted = Math.Log(predicted + 1);
|
||||
double error = logActual - logPredicted;
|
||||
return error * error;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double msle = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumSquaredLogError = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumSquaredLogError += CalculateSquaredLogError(actualValues[i], predictedValues[i]);
|
||||
}
|
||||
|
||||
msle = sumSquaredLogError / actualValues.Length;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return msle;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// RAE: Relative Absolute Error
|
||||
/// A normalized error metric that compares the total absolute error to the total
|
||||
/// magnitude of actual values. RAE provides a scale-independent measure of error
|
||||
/// that is robust to the overall magnitude of the data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The RAE calculation process:
|
||||
/// 1. Calculates sum of absolute errors
|
||||
/// 2. Calculates sum of absolute actual values
|
||||
/// 3. Divides total error by total actual magnitude
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-independent (normalized by actual values)
|
||||
/// - Range typically between 0 and 1
|
||||
/// - Easy to interpret (0 is perfect, 1 means error equals data magnitude)
|
||||
/// - Robust to data scale changes
|
||||
/// - Less sensitive to outliers than squared errors
|
||||
///
|
||||
/// Formula:
|
||||
/// RAE = Σ|actual - predicted| / Σ|actual|
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Relative_absolute_error
|
||||
/// https://www.sciencedirect.com/topics/engineering/relative-absolute-error
|
||||
///
|
||||
/// Note: Values greater than 1 indicate predictions worse than using zero
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Rae : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the RAE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rae(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Rae(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the RAE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rae(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double error, double magnitude) CalculateErrorAndMagnitude(double actual, double predicted)
|
||||
{
|
||||
return (Math.Abs(actual - predicted), Math.Abs(actual));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double rae = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumAbsoluteError = 0;
|
||||
double sumAbsoluteActual = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
var (error, magnitude) = CalculateErrorAndMagnitude(actualValues[i], predictedValues[i]);
|
||||
sumAbsoluteError += error;
|
||||
sumAbsoluteActual += magnitude;
|
||||
}
|
||||
|
||||
rae = sumAbsoluteActual > 0 ? sumAbsoluteError / sumAbsoluteActual : 0;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return rae;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// RMSE: Root Mean Square Error
|
||||
/// A widely used error metric that measures the square root of the average squared
|
||||
/// differences between predicted and actual values. RMSE provides error measurements
|
||||
/// in the same units as the original data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The RMSE calculation process:
|
||||
/// 1. Calculates error (actual - predicted) for each point
|
||||
/// 2. Squares each error value
|
||||
/// 3. Averages the squared errors
|
||||
/// 4. Takes the square root of the average
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Same units as input data (unlike MSE)
|
||||
/// - Penalizes large errors more than small ones
|
||||
/// - Always non-negative
|
||||
/// - More interpretable than MSE
|
||||
/// - Commonly used in regression problems
|
||||
///
|
||||
/// Formula:
|
||||
/// RMSE = √((1/n) * Σ(actual - predicted)²)
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Root-mean-square_deviation
|
||||
/// https://www.statisticshowto.com/probability-and-statistics/regression-analysis/rmse-root-mean-square-error/
|
||||
///
|
||||
/// Note: Square root of MSE, making it more interpretable in original units
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Rmse : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the RMSE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rmse(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Rmse(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the RMSE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rmse(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSquaredError(double actual, double predicted)
|
||||
{
|
||||
double error = actual - predicted;
|
||||
return error * error;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double rmse = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumSquaredError = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumSquaredError += CalculateSquaredError(actualValues[i], predictedValues[i]);
|
||||
}
|
||||
|
||||
rmse = Math.Sqrt(sumSquaredError / actualValues.Length);
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return rmse;
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// RMSLE: Root Mean Square Logarithmic Error
|
||||
/// A variation of RMSE that operates on log-transformed values. RMSLE is particularly
|
||||
/// useful for data with exponential growth or when relative errors in larger values
|
||||
/// should be treated similarly to relative errors in smaller values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The RMSLE calculation process:
|
||||
/// 1. Adds 1 to both actual and predicted values (to handle zeros)
|
||||
/// 2. Takes natural log of both values
|
||||
/// 3. Calculates squared difference of logs
|
||||
/// 4. Averages the squared differences
|
||||
/// 5. Takes the square root
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-independent due to log transformation
|
||||
/// - Penalizes underestimates more than overestimates
|
||||
/// - Handles exponential trends well
|
||||
/// - More sensitive to relative differences
|
||||
/// - Can handle zero values (adds 1 before log)
|
||||
///
|
||||
/// Formula:
|
||||
/// RMSLE = √((1/n) * Σ(log(actual + 1) - log(predicted + 1))²)
|
||||
///
|
||||
/// Sources:
|
||||
/// https://www.kaggle.com/wiki/RootMeanSquaredLogarithmicError
|
||||
/// https://medium.com/analytics-vidhya/root-mean-square-log-error-rmse-vs-rmlse-935c6cc1802a
|
||||
///
|
||||
/// Note: Square root of MSLE, useful for data with exponential growth
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Rmsle : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the RMSLE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rmsle(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Rmsle(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the RMSLE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rmsle(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSquaredLogError(double actual, double predicted)
|
||||
{
|
||||
double logActual = Math.Log(actual + 1);
|
||||
double logPredicted = Math.Log(predicted + 1);
|
||||
double error = logActual - logPredicted;
|
||||
return error * error;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double rmsle = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumSquaredLogError = 0;
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
sumSquaredLogError += CalculateSquaredLogError(actualValues[i], predictedValues[i]);
|
||||
}
|
||||
|
||||
rmsle = Math.Sqrt(sumSquaredLogError / actualValues.Length);
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return rmsle;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// RSE: Relative Squared Error
|
||||
/// A normalized error metric that compares the squared error of predictions to
|
||||
/// the variance of actual values. RSE provides a scale-independent measure of
|
||||
/// prediction accuracy relative to the inherent variability in the data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The RSE calculation process:
|
||||
/// 1. Calculates sum of squared prediction errors
|
||||
/// 2. Calculates sum of squared deviations from mean (variance)
|
||||
/// 3. Divides squared error by variance and takes square root
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Scale-independent (normalized by data variance)
|
||||
/// - Range typically between 0 and 1
|
||||
/// - Easy interpretation relative to data variance
|
||||
/// - Penalizes large errors more than small ones
|
||||
/// - Accounts for data variability
|
||||
///
|
||||
/// Formula:
|
||||
/// RSE = √(Σ(actual - predicted)² / Σ(actual - mean(actual))²)
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Relative_squared_error
|
||||
/// https://www.sciencedirect.com/topics/engineering/relative-squared-error
|
||||
///
|
||||
/// Note: Values less than 1 indicate predictions better than using mean
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Rse : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the RSE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rse(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Rse(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the RSE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rse(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double squaredError, double squaredDeviation) CalculateErrors(double actual, double predicted, double meanActual)
|
||||
{
|
||||
double error = actual - predicted;
|
||||
double deviation = actual - meanActual;
|
||||
return (error * error, deviation * deviation);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double rse = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumSquaredError = 0;
|
||||
double sumSquaredActual = 0;
|
||||
double meanActual = _actualBuffer.Average();
|
||||
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
var (squaredError, squaredDeviation) = CalculateErrors(actualValues[i], predictedValues[i], meanActual);
|
||||
sumSquaredError += squaredError;
|
||||
sumSquaredActual += squaredDeviation;
|
||||
}
|
||||
|
||||
rse = sumSquaredActual > 0 ? Math.Sqrt(sumSquaredError / sumSquaredActual) : 0;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return rse;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// R-squared: Coefficient of Determination
|
||||
/// A statistical measure that represents the proportion of variance in the dependent
|
||||
/// variable that is predictable from the independent variable. R-squared provides
|
||||
/// a measure of how well the predictions approximate the actual data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The R-squared calculation process:
|
||||
/// 1. Calculates total sum of squares (variance from mean)
|
||||
/// 2. Calculates residual sum of squares (prediction errors)
|
||||
/// 3. Computes 1 - (residual SS / total SS)
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Range is typically 0 to 1
|
||||
/// - 1 indicates perfect prediction
|
||||
/// - 0 indicates prediction no better than mean
|
||||
/// - Scale-independent
|
||||
/// - Widely used in regression analysis
|
||||
///
|
||||
/// Formula:
|
||||
/// R² = 1 - (Σ(actual - predicted)² / Σ(actual - mean(actual))²)
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Coefficient_of_determination
|
||||
/// https://www.statisticshowto.com/probability-and-statistics/coefficient-of-determination-r-squared/
|
||||
///
|
||||
/// Note: Can be negative if predictions are worse than using the mean
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Rsquared : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the R-squared value.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rsquared(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Rsquared(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the R-squared value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rsquared(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double squaredResidual, double squaredTotal) CalculateSquaredErrors(double actual, double predicted, double meanActual)
|
||||
{
|
||||
double deviation = actual - meanActual;
|
||||
double error = actual - predicted;
|
||||
return (error * error, deviation * deviation);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double rsquared = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double meanActual = _actualBuffer.Average();
|
||||
double sumSquaredTotal = 0;
|
||||
double sumSquaredResidual = 0;
|
||||
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
var (squaredResidual, squaredTotal) = CalculateSquaredErrors(actualValues[i], predictedValues[i], meanActual);
|
||||
sumSquaredResidual += squaredResidual;
|
||||
sumSquaredTotal += squaredTotal;
|
||||
}
|
||||
|
||||
rsquared = sumSquaredTotal >= double.Epsilon ? 1 - (sumSquaredResidual / sumSquaredTotal) : 0;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return rsquared;
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// SMAPE: Symmetric Mean Absolute Percentage Error
|
||||
/// A variation of MAPE that treats positive and negative errors symmetrically.
|
||||
/// SMAPE uses the average of actual and predicted values in the denominator,
|
||||
/// making it more robust than MAPE for values close to zero.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The SMAPE calculation process:
|
||||
/// 1. Calculates absolute difference between actual and predicted
|
||||
/// 2. Divides by sum of absolute actual and predicted values
|
||||
/// 3. Averages these ratios and multiplies by 200%
|
||||
///
|
||||
/// Key characteristics:
|
||||
/// - Symmetric treatment of errors
|
||||
/// - Range is 0% to 200%
|
||||
/// - More robust than MAPE near zero
|
||||
/// - Scale-independent
|
||||
/// - Handles both positive and negative values
|
||||
///
|
||||
/// Formula:
|
||||
/// SMAPE = (200/n) * Σ|actual - predicted| / (|actual| + |predicted|)
|
||||
///
|
||||
/// Sources:
|
||||
/// https://en.wikipedia.org/wiki/Symmetric_mean_absolute_percentage_error
|
||||
/// https://www.sciencedirect.com/science/article/abs/pii/0169207085900059
|
||||
///
|
||||
/// Note: More stable than MAPE when actual values are close to zero
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Smape : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _actualBuffer;
|
||||
private readonly CircularBuffer _predictedBuffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
|
||||
/// <param name="period">The number of points over which to calculate the SMAPE.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Smape(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
WarmupPeriod = period;
|
||||
_actualBuffer = new CircularBuffer(period);
|
||||
_predictedBuffer = new CircularBuffer(period);
|
||||
Name = $"Smape(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points over which to calculate the SMAPE.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Smape(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_actualBuffer.Clear();
|
||||
_predictedBuffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSymmetricError(double actual, double predicted)
|
||||
{
|
||||
double denominator = Math.Abs(actual) + Math.Abs(predicted);
|
||||
return denominator > Epsilon ? Math.Abs(actual - predicted) / denominator : 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
double actual = Input.Value;
|
||||
_actualBuffer.Add(actual, Input.IsNew);
|
||||
|
||||
// If no predicted value provided, use mean of actual values
|
||||
double predicted = double.IsNaN(Input2.Value) ? _actualBuffer.Average() : Input2.Value;
|
||||
_predictedBuffer.Add(predicted, Input.IsNew);
|
||||
|
||||
double smape = 0;
|
||||
if (_actualBuffer.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<double> actualValues = _actualBuffer.GetSpan();
|
||||
ReadOnlySpan<double> predictedValues = _predictedBuffer.GetSpan();
|
||||
|
||||
double sumSymmetricAbsolutePercentageError = 0;
|
||||
int validCount = 0;
|
||||
|
||||
for (int i = 0; i < actualValues.Length; i++)
|
||||
{
|
||||
double error = CalculateSymmetricError(actualValues[i], predictedValues[i]);
|
||||
if (error > 0)
|
||||
{
|
||||
sumSymmetricAbsolutePercentageError += error;
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
|
||||
smape = validCount > 0 ? (200 * sumSymmetricAbsolutePercentageError / validCount) : 0;
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return smape;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
✔️ HUBER - Huber Loss
|
||||
✔️ MAE - Mean Absolute Error
|
||||
✔️ MAPD - Mean Absolute Percentage Deviation
|
||||
✔️ MAPE - Mean Absolute Percentage Error
|
||||
✔️ MASE - Mean Absolute Scaled Error
|
||||
✔️ MDA - Mean Directional Accuracy
|
||||
✔️ ME - Mean Error
|
||||
✔️ MPE - Mean Percentage Error
|
||||
✔️ MSE - Mean Squared Error
|
||||
✔️ MSLE - Mean Squared Logarithmic Error
|
||||
✔️ RAE - Relative Absolute Error
|
||||
✔️ RMSE - Root Mean Squared Error
|
||||
✔️ RMSLE - Root Mean Squared Logarithmic Error
|
||||
✔️ RSE - Relative Squared Error
|
||||
✔️ RSQUARED - R-Squared (Coefficient of Determination)
|
||||
✔️ SMAPE - Symmetric Mean Absolute Percentage Error
|
||||
Reference in New Issue
Block a user