first iteration

This commit is contained in:
Miha Kralj
2025-11-25 20:40:46 -08:00
parent b5881b9bb4
commit 33ffd3a37a
594 changed files with 117007 additions and 80111 deletions
-130
View File
@@ -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;
}
}
-109
View File
@@ -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;
}
}
-117
View File
@@ -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;
}
}
-117
View File
@@ -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;
}
}
-153
View File
@@ -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);
}
}
-119
View File
@@ -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;
}
}
-117
View File
@@ -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;
}
}
-118
View File
@@ -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;
}
}
-118
View File
@@ -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;
}
}
-121
View File
@@ -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;
}
}
-120
View File
@@ -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;
}
}
-119
View File
@@ -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;
}
}
-122
View File
@@ -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;
}
}
-124
View File
@@ -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;
}
}
-124
View File
@@ -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;
}
}
-126
View File
@@ -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;
}
}
-16
View File
@@ -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