mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 02:28:05 +00:00
XML Documentation
This commit is contained in:
+139
-111
@@ -1,128 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
namespace QuanTAlib;
|
||||
|
||||
namespace QuanTAlib
|
||||
/// <summary>
|
||||
/// Calculates the rate of change of the slope over a specified period.
|
||||
/// Provides insights into trend acceleration or deceleration.
|
||||
/// </summary>
|
||||
public class Curvature : AbstractBase
|
||||
{
|
||||
public class Curvature : AbstractBase
|
||||
private readonly int _period;
|
||||
private readonly Slope _slopeCalculator;
|
||||
private readonly CircularBuffer _slopeBuffer;
|
||||
|
||||
public double? Intercept { get; private set; }
|
||||
public double? StdDev { get; private set; }
|
||||
public double? RSquared { get; private set; }
|
||||
public double? Line { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Curvature class.
|
||||
/// </summary>
|
||||
/// <param name="period">The number of data points to consider for calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the period is 2 or less.
|
||||
/// </exception>
|
||||
public Curvature(int period)
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly Slope _slopeCalculator;
|
||||
private readonly CircularBuffer _slopeBuffer;
|
||||
|
||||
public double? Intercept { get; private set; }
|
||||
public double? StdDev { get; private set; }
|
||||
public double? RSquared { get; private set; }
|
||||
public double? Line { get; private set; }
|
||||
|
||||
public Curvature(int period)
|
||||
if (period <= 2)
|
||||
{
|
||||
if (period <= 2)
|
||||
throw new ArgumentOutOfRangeException(nameof(period), period,
|
||||
"Period must be greater than 2 for Curvature calculation.");
|
||||
}
|
||||
_period = period;
|
||||
WarmupPeriod = period * 2 - 1; // Number of points needed for period number of slopes
|
||||
_slopeCalculator = new Slope(period);
|
||||
_slopeBuffer = new CircularBuffer(period);
|
||||
Name = $"Curvature(period={period})";
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Curvature class with a data source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object that publishes data.</param>
|
||||
/// <param name="period">The number of data points to consider.</param>
|
||||
public Curvature(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the Curvature indicator to its initial state.
|
||||
/// </summary>
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_slopeBuffer.Clear();
|
||||
Intercept = null;
|
||||
StdDev = null;
|
||||
RSquared = null;
|
||||
Line = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the indicator.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates if the current data point is new.</param>
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the curvature calculation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated curvature value. Positive for increasing slope, negative for decreasing.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Uses least squares method for optimal calculation. Also computes additional statistics
|
||||
/// such as Intercept, Standard Deviation, R-Squared, and Line value.
|
||||
/// </remarks>
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
var slopeResult = _slopeCalculator.Calc(Input);
|
||||
_slopeBuffer.Add(slopeResult.Value, Input.IsNew);
|
||||
|
||||
double curvature = 0;
|
||||
|
||||
if (_slopeBuffer.Count < 2)
|
||||
{
|
||||
return curvature; // Not enough points for calculation
|
||||
}
|
||||
|
||||
int count = Math.Min(_slopeBuffer.Count, _period);
|
||||
var slopes = _slopeBuffer.GetSpan().ToArray();
|
||||
|
||||
// Calculate averages
|
||||
double sumX = 0, sumY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sumX += i + 1;
|
||||
sumY += slopes[i];
|
||||
}
|
||||
double avgX = sumX / count;
|
||||
double avgY = sumY / count;
|
||||
|
||||
// Least squares method
|
||||
double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
double devX = (i + 1) - avgX;
|
||||
double devY = slopes[i] - avgY;
|
||||
sumSqX += devX * devX;
|
||||
sumSqY += devY * devY;
|
||||
sumSqXY += devX * devY;
|
||||
}
|
||||
|
||||
if (sumSqX > 0)
|
||||
{
|
||||
curvature = sumSqXY / sumSqX;
|
||||
Intercept = avgY - (curvature * avgX);
|
||||
|
||||
// Calculate Standard Deviation and R-Squared
|
||||
double stdDevX = Math.Sqrt(sumSqX / count);
|
||||
double stdDevY = Math.Sqrt(sumSqY / count);
|
||||
StdDev = stdDevY;
|
||||
|
||||
if (stdDevX * stdDevY != 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), period,
|
||||
"Period must be greater than 2 for Curvature calculation.");
|
||||
double r = sumSqXY / (stdDevX * stdDevY) / count;
|
||||
RSquared = r * r;
|
||||
}
|
||||
_period = period;
|
||||
WarmupPeriod = period * 2 - 1; // We need this many points to get period number of slopes
|
||||
_slopeCalculator = new Slope(period);
|
||||
_slopeBuffer = new CircularBuffer(period);
|
||||
Name = $"Curvature(period={period})";
|
||||
|
||||
Init();
|
||||
// Calculate last Line value (y = mx + b)
|
||||
Line = (curvature * count) + Intercept;
|
||||
}
|
||||
|
||||
public Curvature(object source, int period) : this(period)
|
||||
else
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_slopeBuffer.Clear();
|
||||
Intercept = null;
|
||||
StdDev = null;
|
||||
RSquared = null;
|
||||
Line = null;
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
// Calculate slope
|
||||
var slopeResult = _slopeCalculator.Calc(Input);
|
||||
_slopeBuffer.Add(slopeResult.Value, Input.IsNew);
|
||||
|
||||
double curvature = 0;
|
||||
|
||||
if (_slopeBuffer.Count < 2)
|
||||
{
|
||||
return curvature; // Return 0 when there are fewer than 2 slope points
|
||||
}
|
||||
|
||||
int count = Math.Min(_slopeBuffer.Count, _period);
|
||||
var slopes = _slopeBuffer.GetSpan().ToArray();
|
||||
|
||||
// Calculate averages
|
||||
double sumX = 0, sumY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sumX += i + 1;
|
||||
sumY += slopes[i];
|
||||
}
|
||||
double avgX = sumX / count;
|
||||
double avgY = sumY / count;
|
||||
|
||||
// Least squares method
|
||||
double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
double devX = (i + 1) - avgX;
|
||||
double devY = slopes[i] - avgY;
|
||||
sumSqX += devX * devX;
|
||||
sumSqY += devY * devY;
|
||||
sumSqXY += devX * devY;
|
||||
}
|
||||
|
||||
if (sumSqX > 0)
|
||||
{
|
||||
curvature = sumSqXY / sumSqX;
|
||||
Intercept = avgY - (curvature * avgX);
|
||||
|
||||
// Calculate Standard Deviation and R-Squared
|
||||
double stdDevX = Math.Sqrt(sumSqX / count);
|
||||
double stdDevY = Math.Sqrt(sumSqY / count);
|
||||
StdDev = stdDevY;
|
||||
|
||||
if (stdDevX * stdDevY != 0)
|
||||
{
|
||||
double r = sumSqXY / (stdDevX * stdDevY) / count;
|
||||
RSquared = r * r;
|
||||
}
|
||||
|
||||
// Calculate last Line value (y = mx + b)
|
||||
Line = (curvature * count) + Intercept;
|
||||
}
|
||||
else
|
||||
{
|
||||
Intercept = null;
|
||||
StdDev = null;
|
||||
RSquared = null;
|
||||
Line = null;
|
||||
}
|
||||
|
||||
IsHot = _slopeBuffer.Count == _period;
|
||||
return curvature;
|
||||
}
|
||||
IsHot = _slopeBuffer.Count == _period;
|
||||
return curvature;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,27 @@
|
||||
namespace QuanTAlib;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
// Shannon's Entropy calculation
|
||||
/// <summary>
|
||||
/// Measures the unpredictability of data using Shannon's Entropy.
|
||||
/// Provides insights into the randomness or information content of the time series.
|
||||
/// </summary>
|
||||
public class Entropy : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Entropy class.
|
||||
/// </summary>
|
||||
/// <param name="period">The number of data points to consider for calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the period is less than 2.
|
||||
/// </exception>
|
||||
public Entropy(int period) : base()
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for entropy calculation.");
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for entropy calculation.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = 2;
|
||||
@@ -22,18 +30,30 @@ public class Entropy : AbstractBase
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Entropy class with a data source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object that publishes data.</param>
|
||||
/// <param name="period">The number of data points to consider.</param>
|
||||
public Entropy(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the Entropy indicator to its initial state.
|
||||
/// </summary>
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the indicator.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates if the current data point is new.</param>
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -43,6 +63,17 @@ public class Entropy : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the entropy calculation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated entropy value, normalized between 0 and 1.
|
||||
/// 1 indicates maximum randomness, 0 indicates perfect predictability.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Uses Shannon's Entropy formula and normalizes the result based on the
|
||||
/// number of unique values in the current period.
|
||||
/// </remarks>
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -70,9 +101,11 @@ public class Entropy : AbstractBase
|
||||
double maxEntropy = Math.Log2(uniqueValueCount);
|
||||
|
||||
entropy = entropy == 0 ? 1 : entropy / maxEntropy;
|
||||
|
||||
}
|
||||
else { entropy = 1; }
|
||||
else
|
||||
{
|
||||
entropy = 1; // Default to maximum entropy when insufficient data
|
||||
}
|
||||
|
||||
IsHot = _buffer.Count >= Period;
|
||||
return entropy;
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
namespace QuanTAlib;
|
||||
|
||||
// Excess kurtosis calculated with Sheskin Algorithm
|
||||
/// <summary>
|
||||
/// Calculates excess kurtosis using the Sheskin Algorithm.
|
||||
/// Measures the "tailedness" of the probability distribution of a real-valued random variable.
|
||||
/// </summary>
|
||||
public class Kurtosis : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Kurtosis class.
|
||||
/// </summary>
|
||||
/// <param name="period">The number of data points to consider for calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the period is less than 4.
|
||||
/// </exception>
|
||||
public Kurtosis(int period) : base()
|
||||
{
|
||||
if (period < 4)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 4 for kurtosis calculation.");
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 4 for kurtosis calculation.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = Period - 1;
|
||||
@@ -19,18 +30,30 @@ public class Kurtosis : AbstractBase
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Kurtosis class with a data source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object that publishes data.</param>
|
||||
/// <param name="period">The number of data points to consider.</param>
|
||||
public Kurtosis(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the Kurtosis indicator to its initial state.
|
||||
/// </summary>
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the indicator.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates if the current data point is new.</param>
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -40,6 +63,17 @@ public class Kurtosis : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the kurtosis calculation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated excess kurtosis. Positive for heavy-tailed distributions,
|
||||
/// negative for light-tailed distributions.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Uses the Sheskin Algorithm for kurtosis calculation.
|
||||
/// Requires at least 4 data points for a valid calculation.
|
||||
/// </remarks>
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -65,7 +99,7 @@ public class Kurtosis : AbstractBase
|
||||
|
||||
double variance = s2 / (n - 1);
|
||||
|
||||
// Using the Sheskin Algorithm for kurtosis
|
||||
// Sheskin Algorithm
|
||||
kurtosis = (n * (n + 1) * s4) / (variance * variance * (n - 3) * (n - 1) * (n - 2))
|
||||
- (3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)));
|
||||
}
|
||||
|
||||
+100
-66
@@ -1,80 +1,114 @@
|
||||
using System;
|
||||
namespace QuanTAlib;
|
||||
|
||||
namespace QuanTAlib
|
||||
/// <summary>
|
||||
/// Calculates the maximum value over a specified period, with an optional decay factor.
|
||||
/// Useful for tracking the highest point in a time series with the ability to gradually forget old peaks.
|
||||
/// </summary>
|
||||
public class Max : AbstractBase
|
||||
{
|
||||
public class Max : AbstractBase
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly double _halfLife;
|
||||
private double _currentMax, _p_currentMax;
|
||||
private int _timeSinceNewMax, _p_timeSinceNewMax;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Max class.
|
||||
/// </summary>
|
||||
/// <param name="period">The number of data points to consider. Must be at least 1.</param>
|
||||
/// <param name="decay">Half-life decay factor. Set to 0 for no decay, higher for faster forgetting. Default is 0.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the period is less than 1 or decay is negative.
|
||||
/// </exception>
|
||||
public Max(int period, double decay = 0) : base()
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly double _halfLife;
|
||||
private double _currentMax, _p_currentMax;
|
||||
private int _timeSinceNewMax, _p_timeSinceNewMax;
|
||||
|
||||
public Max(int period, double decay = 0) : base()
|
||||
if (period < 1)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
if (decay < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_halfLife = decay * 0.1;
|
||||
Name = $"Max(period={period}, halfLife={decay:F2})";
|
||||
Init();
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 1.");
|
||||
}
|
||||
|
||||
public Max(object source, int period, double decay = 0) : this(period, decay)
|
||||
if (decay < 0)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
throw new ArgumentOutOfRangeException(nameof(decay),
|
||||
"Half-life must be non-negative.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_halfLife = decay * 0.1;
|
||||
Name = $"Max(period={period}, halfLife={decay:F2})";
|
||||
Init();
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Max class with a data source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object that publishes data.</param>
|
||||
/// <param name="period">The number of data points to consider.</param>
|
||||
/// <param name="decay">Half-life decay factor. Default is 0.</param>
|
||||
public Max(object source, int period, double decay = 0) : this(period, decay)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the Max indicator to its initial state.
|
||||
/// </summary>
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_currentMax = double.MinValue;
|
||||
_timeSinceNewMax = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the indicator.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates if the current data point is new.</param>
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
base.Init();
|
||||
_currentMax = double.MinValue;
|
||||
_p_currentMax = _currentMax;
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
_timeSinceNewMax++;
|
||||
_p_timeSinceNewMax = _timeSinceNewMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentMax = _p_currentMax;
|
||||
_timeSinceNewMax = _p_timeSinceNewMax;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the max calculation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The current maximum value, potentially adjusted by the decay factor.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Uses a decay factor to gradually forget old peaks. The max value is always
|
||||
/// capped by the highest value in the current period.
|
||||
/// </remarks>
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
if (Input.Value >= _currentMax)
|
||||
{
|
||||
_currentMax = Input.Value;
|
||||
_timeSinceNewMax = 0;
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_currentMax = _currentMax;
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
_timeSinceNewMax++;
|
||||
_p_timeSinceNewMax = _timeSinceNewMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentMax = _p_currentMax;
|
||||
_timeSinceNewMax = _p_timeSinceNewMax;
|
||||
}
|
||||
}
|
||||
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period);
|
||||
_currentMax = _currentMax - decayRate * (_currentMax - _buffer.Average());
|
||||
_currentMax = Math.Min(_currentMax, _buffer.Max());
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
if (Input.Value >= _currentMax)
|
||||
{
|
||||
_currentMax = Input.Value;
|
||||
_timeSinceNewMax = 0;
|
||||
}
|
||||
|
||||
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period);
|
||||
_currentMax = _currentMax - decayRate * (_currentMax - _buffer.Average());
|
||||
_currentMax = Math.Min(_currentMax, _buffer.Max());
|
||||
|
||||
IsHot = true;
|
||||
return _currentMax;
|
||||
}
|
||||
IsHot = true;
|
||||
return _currentMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+82
-52
@@ -1,69 +1,99 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
namespace QuanTAlib;
|
||||
|
||||
namespace QuanTAlib
|
||||
/// <summary>
|
||||
/// Calculates the median value over a specified period.
|
||||
/// Provides a measure of central tendency that is robust to outliers.
|
||||
/// </summary>
|
||||
public class Median : AbstractBase
|
||||
{
|
||||
public class Median : AbstractBase
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Median class.
|
||||
/// </summary>
|
||||
/// <param name="period">The number of data points to consider. Must be at least 1.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the period is less than 1.
|
||||
/// </exception>
|
||||
public Median(int period) : base()
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
public Median(int period) : base()
|
||||
if (period < 1)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = period;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"Median(period={period})";
|
||||
Init();
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 1.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = period;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"Median(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
public Median(object source, int period) : this(period)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Median class with a data source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object that publishes data.</param>
|
||||
/// <param name="period">The number of data points to consider.</param>
|
||||
public Median(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the indicator.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates if the current data point is new.</param>
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
/// <summary>
|
||||
/// Performs the median calculation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The current median value of the dataset.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Uses a sorting approach to find the median. If there's not enough data,
|
||||
/// it uses the average as a temporary measure.
|
||||
/// </remarks>
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double median;
|
||||
if (_index >= Period)
|
||||
{
|
||||
if (isNew)
|
||||
var sortedValues = _buffer.GetSpan().ToArray();
|
||||
Array.Sort(sortedValues);
|
||||
int middleIndex = sortedValues.Length / 2;
|
||||
|
||||
if (sortedValues.Length % 2 == 0)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double median;
|
||||
if (_index >= Period)
|
||||
{
|
||||
var sortedValues = _buffer.GetSpan().ToArray();
|
||||
Array.Sort(sortedValues);
|
||||
int middleIndex = sortedValues.Length / 2;
|
||||
|
||||
if (sortedValues.Length % 2 == 0)
|
||||
{
|
||||
median = (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
median = sortedValues[middleIndex];
|
||||
}
|
||||
// Even number of values: average of two middle values
|
||||
median = (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
median = _buffer.Average(); // Use average until we have enough data points
|
||||
// Odd number of values: middle value
|
||||
median = sortedValues[middleIndex];
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return median;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not enough data, use average as temporary measure
|
||||
median = _buffer.Average();
|
||||
}
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
return median;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+96
-70
@@ -1,80 +1,106 @@
|
||||
using System;
|
||||
namespace QuanTAlib;
|
||||
|
||||
namespace QuanTAlib
|
||||
{
|
||||
public class Min : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly double _halfLife;
|
||||
private double _currentMin, _p_currentMin;
|
||||
private int _timeSinceNewMin, _p_timeSinceNewMin;
|
||||
/// <summary>
|
||||
/// Represents a minimum value calculator with optional decay over a specified period.
|
||||
/// This class calculates the minimum value within a given period, with the ability to
|
||||
/// apply a decay factor to give more weight to recent values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Min class uses a circular buffer to store values and calculates the minimum
|
||||
/// efficiently. It also implements a decay mechanism to adjust the minimum value over
|
||||
/// time, allowing for a more responsive indicator in changing market conditions.
|
||||
/// </remarks>
|
||||
public class Min : AbstractBase {
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly double _halfLife;
|
||||
private double _currentMin, _p_currentMin;
|
||||
private int _timeSinceNewMin, _p_timeSinceNewMin;
|
||||
|
||||
public Min(int period, double decay = 0) : base()
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
if (decay < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_halfLife = decay * 0.1;
|
||||
Name = $"Min(period={period}, halfLife={decay:F2})";
|
||||
Init();
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Min class with the specified period and decay.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the minimum value.</param>
|
||||
/// <param name="decay">The decay factor to apply to older values (default is 0).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than 1 or decay is negative.
|
||||
/// </exception>
|
||||
public Min(int period, double decay = 0) : base() {
|
||||
if (period < 1) {
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
|
||||
public Min(object source, int period, double decay = 0) : this(period, decay)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
if (decay < 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(decay), "Half-life must be non-negative.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_halfLife = decay * 0.1;
|
||||
Name = $"Min(period={period}, halfLife={decay:F2})";
|
||||
Init();
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_currentMin = double.MaxValue;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Min class with the specified source, period, and decay.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object to subscribe to for value updates.</param>
|
||||
/// <param name="period">The period over which to calculate the minimum value.</param>
|
||||
/// <param name="decay">The decay factor to apply to older values (default is 0).</param>
|
||||
public Min(object source, int period, double decay = 0) : this(period, decay) {
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Min instance by setting initial values.
|
||||
/// </summary>
|
||||
public override void Init() {
|
||||
base.Init();
|
||||
_currentMin = double.MaxValue;
|
||||
_timeSinceNewMin = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the Min instance based on whether a new value is being processed.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||
protected override void ManageState(bool isNew) {
|
||||
if (isNew) {
|
||||
_p_currentMin = _currentMin;
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
_timeSinceNewMin++;
|
||||
_p_timeSinceNewMin = _timeSinceNewMin;
|
||||
} else {
|
||||
_currentMin = _p_currentMin;
|
||||
_timeSinceNewMin = _p_timeSinceNewMin;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the minimum value calculation with decay.
|
||||
/// </summary>
|
||||
/// <returns>The calculated minimum value for the current period.</returns>
|
||||
/// <remarks>
|
||||
/// This method updates the current minimum value based on the input, applies the decay
|
||||
/// factor, and ensures the result is not lower than the actual minimum in the buffer.
|
||||
/// The decay rate is calculated using an exponential function based on the time since
|
||||
/// the last new minimum and the specified half-life.
|
||||
/// </remarks>
|
||||
protected override double Calculation() {
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
if (Input.Value <= _currentMin) {
|
||||
_currentMin = Input.Value;
|
||||
_timeSinceNewMin = 0;
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_p_currentMin = _currentMin;
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
_timeSinceNewMin++;
|
||||
_p_timeSinceNewMin = _timeSinceNewMin;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentMin = _p_currentMin;
|
||||
_timeSinceNewMin = _p_timeSinceNewMin;
|
||||
}
|
||||
}
|
||||
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
|
||||
_currentMin = _currentMin + decayRate * (_buffer.Average() - _currentMin);
|
||||
_currentMin = Math.Max(_currentMin, _buffer.Min());
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
if (Input.Value <= _currentMin)
|
||||
{
|
||||
_currentMin = Input.Value;
|
||||
_timeSinceNewMin = 0;
|
||||
}
|
||||
|
||||
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
|
||||
_currentMin = _currentMin + decayRate * (_buffer.Average() - _currentMin);
|
||||
_currentMin = Math.Max(_currentMin, _buffer.Min());
|
||||
|
||||
IsHot = true;
|
||||
return _currentMin;
|
||||
}
|
||||
IsHot = true;
|
||||
return _currentMin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-19
@@ -1,14 +1,27 @@
|
||||
namespace QuanTAlib;
|
||||
|
||||
public class Mode : AbstractBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a mode calculator that determines the most frequent value in a specified period.
|
||||
/// If multiple values have the same highest frequency, it returns their average.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Mode class uses a circular buffer to store values and calculates the mode
|
||||
/// efficiently. Before the specified period is reached, it returns the average of
|
||||
/// the available values as an approximation.
|
||||
/// </remarks>
|
||||
public class Mode : AbstractBase {
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
public Mode(int period) : base()
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Mode class with the specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the mode.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than 1.
|
||||
/// </exception>
|
||||
public Mode(int period) : base() {
|
||||
if (period < 1) {
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
|
||||
}
|
||||
Period = period;
|
||||
@@ -18,29 +31,45 @@ public class Mode : AbstractBase
|
||||
Init();
|
||||
}
|
||||
|
||||
public Mode(object source, int period) : this(period)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Mode class with the specified source and period.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object to subscribe to for value updates.</param>
|
||||
/// <param name="period">The period over which to calculate the mode.</param>
|
||||
public Mode(object source, int period) : this(period) {
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the state of the Mode instance based on whether a new value is being processed.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||
protected override void ManageState(bool isNew) {
|
||||
if (isNew) {
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the mode calculation for the current period.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated mode (most frequent value) for the current period.
|
||||
/// If multiple values have the same highest frequency, returns their average.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Before the specified period is reached, this method returns the average of
|
||||
/// the available values as an approximation of the mode. Once the period is
|
||||
/// reached, it calculates the true mode by grouping and counting the values.
|
||||
/// </remarks>
|
||||
protected override double Calculation() {
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double mode;
|
||||
if (_index >= Period)
|
||||
{
|
||||
if (_index >= Period) {
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
var groupedValues = values.GroupBy(v => v)
|
||||
.OrderByDescending(g => g.Count())
|
||||
@@ -53,9 +82,7 @@ public class Mode : AbstractBase
|
||||
.ToList();
|
||||
|
||||
mode = modes.Average(); // If there are multiple modes, we return their average
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
mode = _buffer.Average(); // Use average until we have enough data points
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
namespace QuanTAlib;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
public class Percentile : AbstractBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a percentile calculator that determines the value at a specified percentile
|
||||
/// in a given period of data points.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Percentile class uses a circular buffer to store values and calculates the
|
||||
/// percentile efficiently. It uses linear interpolation when the percentile falls
|
||||
/// between two data points. Before the specified period is reached, it returns the
|
||||
/// average of the available values as an approximation.
|
||||
/// </remarks>
|
||||
public class Percentile : AbstractBase {
|
||||
private readonly int Period;
|
||||
private readonly double Percent;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
public Percentile(int period, double percent) : base()
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Percentile class with the specified period and percentile.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the percentile.</param>
|
||||
/// <param name="percent">The percentile to calculate (between 0 and 100).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than 2 or percent is not between 0 and 100.
|
||||
/// </exception>
|
||||
public Percentile(int period, double percent) : base() {
|
||||
if (period < 2) {
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for percentile calculation.");
|
||||
}
|
||||
if (percent < 0 || percent > 100)
|
||||
{
|
||||
if (percent < 0 || percent > 100) {
|
||||
throw new ArgumentOutOfRangeException(nameof(percent), "Percent must be between 0 and 100.");
|
||||
}
|
||||
Period = period;
|
||||
@@ -27,35 +38,54 @@ public class Percentile : AbstractBase
|
||||
Init();
|
||||
}
|
||||
|
||||
public Percentile(object source, int period, double percent) : this(period, percent)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Percentile class with the specified source, period, and percentile.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object to subscribe to for value updates.</param>
|
||||
/// <param name="period">The period over which to calculate the percentile.</param>
|
||||
/// <param name="percent">The percentile to calculate (between 0 and 100).</param>
|
||||
public Percentile(object source, int period, double percent) : this(period, percent) {
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the Percentile instance by clearing the buffer.
|
||||
/// </summary>
|
||||
public override void Init() {
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the state of the Percentile instance based on whether a new value is being processed.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||
protected override void ManageState(bool isNew) {
|
||||
if (isNew) {
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the percentile calculation for the current period.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated percentile value for the current period.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method uses linear interpolation when the percentile falls between two data points.
|
||||
/// Before the specified period is reached, it returns the average of the available values
|
||||
/// as an approximation. Once the period is reached, it calculates the true percentile by
|
||||
/// sorting the values and interpolating as necessary.
|
||||
/// </remarks>
|
||||
protected override double Calculation() {
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double result;
|
||||
if (_buffer.Count >= Period)
|
||||
{
|
||||
if (_buffer.Count >= Period) {
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
Array.Sort(values);
|
||||
|
||||
@@ -63,21 +93,16 @@ protected override double Calculation()
|
||||
int lowerIndex = (int)Math.Floor(position);
|
||||
int upperIndex = (int)Math.Ceiling(position);
|
||||
|
||||
if (lowerIndex == upperIndex)
|
||||
{
|
||||
if (lowerIndex == upperIndex) {
|
||||
result = values[lowerIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Interpolate between the two nearest values
|
||||
double lowerValue = values[lowerIndex];
|
||||
double upperValue = values[upperIndex];
|
||||
double fraction = position - lowerIndex;
|
||||
result = lowerValue + (upperValue - lowerValue) * fraction;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Use average for insufficient data, like the Median class
|
||||
result = _buffer.Average();
|
||||
}
|
||||
|
||||
+52
-25
@@ -1,17 +1,28 @@
|
||||
namespace QuanTAlib;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
public class Skew : AbstractBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a skewness calculator that measures the asymmetry of the probability
|
||||
/// distribution of a real-valued random variable about its mean.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Skew class uses a circular buffer to store values and calculates the skewness
|
||||
/// efficiently. It uses the adjusted Fisher-Pearson standardized moment coefficient
|
||||
/// for sample skewness calculation. A minimum of 3 data points is required for the
|
||||
/// calculation.
|
||||
/// </remarks>
|
||||
public class Skew : AbstractBase {
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
public Skew(int period) : base()
|
||||
{
|
||||
if (period < 3)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Skew class with the specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the skewness.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than 3.
|
||||
/// </exception>
|
||||
public Skew(int period) : base() {
|
||||
if (period < 3) {
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 3 for skewness calculation.");
|
||||
}
|
||||
Period = period;
|
||||
@@ -21,36 +32,54 @@ public class Skew : AbstractBase
|
||||
Init();
|
||||
}
|
||||
|
||||
public Skew(object source, int period) : this(period)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Skew class with the specified source and period.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object to subscribe to for value updates.</param>
|
||||
/// <param name="period">The period over which to calculate the skewness.</param>
|
||||
public Skew(object source, int period) : this(period) {
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the Skew instance by clearing the buffer.
|
||||
/// </summary>
|
||||
public override void Init() {
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the state of the Skew instance based on whether a new value is being processed.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||
protected override void ManageState(bool isNew) {
|
||||
if (isNew) {
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the skewness calculation for the current period.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated skewness value for the current period.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method uses the adjusted Fisher-Pearson standardized moment coefficient
|
||||
/// to calculate the sample skewness. It requires at least 3 data points for the
|
||||
/// calculation. If there are fewer than 3 data points, or if the standard
|
||||
/// deviation is zero, the method returns 0.
|
||||
/// </remarks>
|
||||
protected override double Calculation() {
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double skew = 0;
|
||||
if (_buffer.Count >= 3) // We need at least 3 data points for skewness
|
||||
{
|
||||
if (_buffer.Count >= 3) { // We need at least 3 data points for skewness
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double n = values.Length;
|
||||
@@ -58,8 +87,7 @@ public class Skew : AbstractBase
|
||||
double sumCubedDeviations = 0;
|
||||
double sumSquaredDeviations = 0;
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
foreach (var value in values) {
|
||||
double deviation = value - mean;
|
||||
sumCubedDeviations += Math.Pow(deviation, 3);
|
||||
sumSquaredDeviations += Math.Pow(deviation, 2);
|
||||
@@ -70,8 +98,7 @@ public class Skew : AbstractBase
|
||||
double m2 = sumSquaredDeviations / n;
|
||||
double s3 = Math.Pow(m2, 1.5);
|
||||
|
||||
if (s3 != 0) // Avoid division by zero
|
||||
{
|
||||
if (s3 != 0) { // Avoid division by zero
|
||||
skew = (Math.Sqrt(n * (n - 1)) / (n - 2)) * (m3 / s3);
|
||||
}
|
||||
}
|
||||
|
||||
+135
-116
@@ -1,128 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
namespace QuanTAlib;
|
||||
|
||||
namespace QuanTAlib
|
||||
{
|
||||
public class Slope : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly CircularBuffer _timeBuffer;
|
||||
/// <summary>
|
||||
/// Represents a slope calculator that performs linear regression on a series of data points.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Slope class calculates the slope of a linear regression line, along with other
|
||||
/// statistical measures such as intercept, standard deviation, R-squared, and the last
|
||||
/// point on the regression line. It uses the least squares method for calculation.
|
||||
/// </remarks>
|
||||
public class Slope : AbstractBase {
|
||||
private readonly int _period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly CircularBuffer _timeBuffer;
|
||||
public double? Intercept { get; private set; }
|
||||
public double? StdDev { get; private set; }
|
||||
public double? RSquared { get; private set; }
|
||||
public double? Line { get; private set; }
|
||||
|
||||
public double? Intercept { get; private set; }
|
||||
public double? StdDev { get; private set; }
|
||||
public double? RSquared { get; private set; }
|
||||
public double? Line { get; private set; }
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Slope class with the specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the slope.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than or equal to 1.
|
||||
/// </exception>
|
||||
public Slope(int period) {
|
||||
if (period <= 1) {
|
||||
throw new ArgumentOutOfRangeException(nameof(period), period,
|
||||
"Period must be greater than 1 for Slope/Linear Regression.");
|
||||
}
|
||||
_period = period;
|
||||
WarmupPeriod = period;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_timeBuffer = new CircularBuffer(period);
|
||||
Name = $"Slope(period={period})";
|
||||
|
||||
public Slope(int period)
|
||||
{
|
||||
if (period <= 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), period,
|
||||
"Period must be greater than 1 for Slope/Linear Regression.");
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Slope class with the specified source and period.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object to subscribe to for value updates.</param>
|
||||
/// <param name="period">The period over which to calculate the slope.</param>
|
||||
public Slope(object source, int period) : this(period) {
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Slope instance by clearing buffers and resetting calculated values.
|
||||
/// </summary>
|
||||
public override void Init() {
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
_timeBuffer.Clear();
|
||||
Intercept = null;
|
||||
StdDev = null;
|
||||
RSquared = null;
|
||||
Line = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the state of the Slope instance based on whether a new value is being processed.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||
protected override void ManageState(bool isNew) {
|
||||
if (isNew) {
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the slope calculation using linear regression for the current period.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated slope value for the current period.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method uses the least squares method to calculate the slope of the regression line.
|
||||
/// It also calculates and updates the Intercept, StdDev, RSquared, and Line properties.
|
||||
/// If there are fewer than 2 data points, or if the sum of squared x deviations is 0,
|
||||
/// the method returns 0 and sets the additional properties to null.
|
||||
/// </remarks>
|
||||
protected override double Calculation() {
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
_timeBuffer.Add(Input.Time.Ticks, Input.IsNew);
|
||||
|
||||
double slope = 0;
|
||||
|
||||
if (_buffer.Count < 2) {
|
||||
return slope; // Return 0 when there are fewer than 2 points
|
||||
}
|
||||
|
||||
int count = Math.Min(_buffer.Count, _period);
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
|
||||
// Calculate averages
|
||||
double sumX = 0, sumY = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
sumX += i + 1;
|
||||
sumY += values[i];
|
||||
}
|
||||
double avgX = sumX / count;
|
||||
double avgY = sumY / count;
|
||||
|
||||
// Least squares method
|
||||
double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
double devX = (i + 1) - avgX;
|
||||
double devY = values[i] - avgY;
|
||||
sumSqX += devX * devX;
|
||||
sumSqY += devY * devY;
|
||||
sumSqXY += devX * devY;
|
||||
}
|
||||
|
||||
if (sumSqX > 0) {
|
||||
slope = sumSqXY / sumSqX;
|
||||
Intercept = avgY - (slope * avgX);
|
||||
|
||||
// Calculate Standard Deviation and R-Squared
|
||||
double stdDevX = Math.Sqrt(sumSqX / count);
|
||||
double stdDevY = Math.Sqrt(sumSqY / count);
|
||||
StdDev = stdDevY;
|
||||
|
||||
if (stdDevX * stdDevY != 0) {
|
||||
double r = sumSqXY / (stdDevX * stdDevY) / count;
|
||||
RSquared = r * r;
|
||||
}
|
||||
_period = period;
|
||||
WarmupPeriod = period;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_timeBuffer = new CircularBuffer(period);
|
||||
Name = $"Slope(period={period})";
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public Slope(object source, int period) : this(period)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
_timeBuffer.Clear();
|
||||
// Calculate last Line value (y = mx + b)
|
||||
Line = (slope * count) + Intercept;
|
||||
} else {
|
||||
Intercept = null;
|
||||
StdDev = null;
|
||||
RSquared = null;
|
||||
Line = null;
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
_timeBuffer.Add(Input.Time.Ticks, Input.IsNew);
|
||||
|
||||
double slope = 0;
|
||||
|
||||
if (_buffer.Count < 2)
|
||||
{
|
||||
return slope; // Return 0 when there are fewer than 2 points
|
||||
}
|
||||
|
||||
int count = Math.Min(_buffer.Count, _period);
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
|
||||
// Calculate averages
|
||||
double sumX = 0, sumY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sumX += i + 1;
|
||||
sumY += values[i];
|
||||
}
|
||||
double avgX = sumX / count;
|
||||
double avgY = sumY / count;
|
||||
|
||||
// Least squares method
|
||||
double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
double devX = (i + 1) - avgX;
|
||||
double devY = values[i] - avgY;
|
||||
sumSqX += devX * devX;
|
||||
sumSqY += devY * devY;
|
||||
sumSqXY += devX * devY;
|
||||
}
|
||||
|
||||
if (sumSqX > 0)
|
||||
{
|
||||
slope = sumSqXY / sumSqX;
|
||||
Intercept = avgY - (slope * avgX);
|
||||
|
||||
// Calculate Standard Deviation and R-Squared
|
||||
double stdDevX = Math.Sqrt(sumSqX / count);
|
||||
double stdDevY = Math.Sqrt(sumSqY / count);
|
||||
StdDev = stdDevY;
|
||||
|
||||
if (stdDevX * stdDevY != 0)
|
||||
{
|
||||
double r = sumSqXY / (stdDevX * stdDevY) / count;
|
||||
RSquared = r * r;
|
||||
}
|
||||
|
||||
// Calculate last Line value (y = mx + b)
|
||||
Line = (slope * count) + Intercept;
|
||||
}
|
||||
else
|
||||
{
|
||||
Intercept = null;
|
||||
StdDev = null;
|
||||
RSquared = null;
|
||||
Line = null;
|
||||
}
|
||||
|
||||
IsHot = _buffer.Count == _period;
|
||||
return slope;
|
||||
}
|
||||
IsHot = _buffer.Count == _period;
|
||||
return slope;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+97
-61
@@ -1,69 +1,105 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
namespace QuanTAlib;
|
||||
|
||||
namespace QuanTAlib
|
||||
{
|
||||
public class Stddev : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly bool IsPopulation;
|
||||
private readonly CircularBuffer _buffer;
|
||||
/// <summary>
|
||||
/// Represents a standard deviation calculator that measures the amount of variation or
|
||||
/// dispersion of a set of values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Stddev class calculates either the population standard deviation or the sample
|
||||
/// standard deviation based on the isPopulation parameter. It uses a circular buffer
|
||||
/// to efficiently manage the data points within the specified period.
|
||||
/// </remarks>
|
||||
public class Stddev : AbstractBase {
|
||||
private readonly int Period;
|
||||
private readonly bool IsPopulation;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
public Stddev(int period, bool isPopulation = false) : base()
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
||||
}
|
||||
Period = period;
|
||||
IsPopulation = isPopulation;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"Stddev(period={period}, population={isPopulation})";
|
||||
Init();
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Stddev class with the specified period and
|
||||
/// population flag.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the standard deviation.</param>
|
||||
/// <param name="isPopulation">
|
||||
/// A flag indicating whether to calculate population (true) or sample (false) standard deviation.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than 2.
|
||||
/// </exception>
|
||||
public Stddev(int period, bool isPopulation = false) : base() {
|
||||
if (period < 2) {
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
||||
}
|
||||
Period = period;
|
||||
IsPopulation = isPopulation;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"Stddev(period={period}, population={isPopulation})";
|
||||
Init();
|
||||
}
|
||||
|
||||
public Stddev(object source, int period, bool isPopulation = false) : this(period, isPopulation)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Stddev class with the specified source, period,
|
||||
/// and population flag.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object to subscribe to for value updates.</param>
|
||||
/// <param name="period">The period over which to calculate the standard deviation.</param>
|
||||
/// <param name="isPopulation">
|
||||
/// A flag indicating whether to calculate population (true) or sample (false) standard deviation.
|
||||
/// </param>
|
||||
public Stddev(object source, int period, bool isPopulation = false) : this(period, isPopulation) {
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes the Stddev instance by clearing the buffer.
|
||||
/// </summary>
|
||||
public override void Init() {
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double stddev = 0;
|
||||
if (_buffer.Count > 1)
|
||||
{
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
|
||||
|
||||
double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
|
||||
double variance = sumOfSquaredDifferences / divisor;
|
||||
stddev = Math.Sqrt(variance);
|
||||
}
|
||||
|
||||
IsHot = true; // StdDev calc is valid from bar 1
|
||||
return stddev;
|
||||
/// <summary>
|
||||
/// Manages the state of the Stddev instance based on whether a new value is being processed.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||
protected override void ManageState(bool isNew) {
|
||||
if (isNew) {
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the standard deviation calculation for the current period.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated standard deviation value for the current period.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method calculates the standard deviation using the formula:
|
||||
/// sqrt(sum((x - mean)^2) / n) for population, or
|
||||
/// sqrt(sum((x - mean)^2) / (n - 1)) for sample,
|
||||
/// where x is each value, mean is the average of all values, and n is the number of values.
|
||||
/// If there's only one value in the buffer, the method returns 0.
|
||||
/// </remarks>
|
||||
protected override double Calculation() {
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double stddev = 0;
|
||||
if (_buffer.Count > 1) {
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
|
||||
|
||||
double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
|
||||
double variance = sumOfSquaredDifferences / divisor;
|
||||
stddev = Math.Sqrt(variance);
|
||||
}
|
||||
|
||||
IsHot = true; // StdDev calc is valid from bar 1
|
||||
return stddev;
|
||||
}
|
||||
}
|
||||
|
||||
+96
-60
@@ -1,68 +1,104 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
namespace QuanTAlib;
|
||||
|
||||
namespace QuanTAlib
|
||||
{
|
||||
public class Variance : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly bool IsPopulation;
|
||||
private readonly CircularBuffer _buffer;
|
||||
/// <summary>
|
||||
/// Represents a variance calculator that measures the spread of a set of numbers
|
||||
/// from their average value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Variance class calculates either the population variance or the sample
|
||||
/// variance based on the isPopulation parameter. It uses a circular buffer
|
||||
/// to efficiently manage the data points within the specified period.
|
||||
/// </remarks>
|
||||
public class Variance : AbstractBase {
|
||||
private readonly int Period;
|
||||
private readonly bool IsPopulation;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
public Variance(int period, bool isPopulation = false) : base()
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
||||
}
|
||||
Period = period;
|
||||
IsPopulation = isPopulation;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"Variance(period={period}, population={isPopulation})";
|
||||
Init();
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Variance class with the specified period and
|
||||
/// population flag.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the variance.</param>
|
||||
/// <param name="isPopulation">
|
||||
/// A flag indicating whether to calculate population (true) or sample (false) variance.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than 2.
|
||||
/// </exception>
|
||||
public Variance(int period, bool isPopulation = false) : base() {
|
||||
if (period < 2) {
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2.");
|
||||
}
|
||||
Period = period;
|
||||
IsPopulation = isPopulation;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"Variance(period={period}, population={isPopulation})";
|
||||
Init();
|
||||
}
|
||||
|
||||
public Variance(object source, int period, bool isPopulation = false) : this(period, isPopulation)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Variance class with the specified source, period,
|
||||
/// and population flag.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object to subscribe to for value updates.</param>
|
||||
/// <param name="period">The period over which to calculate the variance.</param>
|
||||
/// <param name="isPopulation">
|
||||
/// A flag indicating whether to calculate population (true) or sample (false) variance.
|
||||
/// </param>
|
||||
public Variance(object source, int period, bool isPopulation = false) : this(period, isPopulation) {
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes the Variance instance by clearing the buffer.
|
||||
/// </summary>
|
||||
public override void Init() {
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double variance = 0;
|
||||
if (_buffer.Count > 1)
|
||||
{
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
|
||||
|
||||
double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
|
||||
variance = sumOfSquaredDifferences / divisor;
|
||||
}
|
||||
|
||||
IsHot = true;
|
||||
return variance;
|
||||
/// <summary>
|
||||
/// Manages the state of the Variance instance based on whether a new value is being processed.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||
protected override void ManageState(bool isNew) {
|
||||
if (isNew) {
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the variance calculation for the current period.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated variance value for the current period.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method calculates the variance using the formula:
|
||||
/// sum((x - mean)^2) / n for population, or
|
||||
/// sum((x - mean)^2) / (n - 1) for sample,
|
||||
/// where x is each value, mean is the average of all values, and n is the number of values.
|
||||
/// If there's only one value in the buffer, the method returns 0.
|
||||
/// </remarks>
|
||||
protected override double Calculation() {
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double variance = 0;
|
||||
if (_buffer.Count > 1) {
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
|
||||
|
||||
double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
|
||||
variance = sumOfSquaredDifferences / divisor;
|
||||
}
|
||||
|
||||
IsHot = true;
|
||||
return variance;
|
||||
}
|
||||
}
|
||||
|
||||
+50
-23
@@ -1,17 +1,27 @@
|
||||
namespace QuanTAlib;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
public class Zscore : AbstractBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Z-score calculator that measures how many standard deviations
|
||||
/// an element is from the mean of a set of values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Zscore class calculates the Z-score (also known as standard score) for
|
||||
/// the most recent value in a given period. It uses a circular buffer to
|
||||
/// efficiently manage the data points within the specified period.
|
||||
/// </remarks>
|
||||
public class Zscore : AbstractBase {
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
public Zscore(int period) : base()
|
||||
{
|
||||
if (period < 2)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Zscore class with the specified period.
|
||||
/// </summary>
|
||||
/// <param name="period">The period over which to calculate the Z-score.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than 2.
|
||||
/// </exception>
|
||||
public Zscore(int period) : base() {
|
||||
if (period < 2) {
|
||||
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2 for Z-score calculation.");
|
||||
}
|
||||
Period = period;
|
||||
@@ -21,36 +31,54 @@ public class Zscore : AbstractBase
|
||||
Init();
|
||||
}
|
||||
|
||||
public Zscore(object source, int period) : this(period)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Zscore class with the specified source and period.
|
||||
/// </summary>
|
||||
/// <param name="source">The source object to subscribe to for value updates.</param>
|
||||
/// <param name="period">The period over which to calculate the Z-score.</param>
|
||||
public Zscore(object source, int period) : this(period) {
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the Zscore instance by clearing the buffer.
|
||||
/// </summary>
|
||||
public override void Init() {
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the state of the Zscore instance based on whether a new value is being processed.
|
||||
/// </summary>
|
||||
/// <param name="isNew">Indicates whether the current input is a new value.</param>
|
||||
protected override void ManageState(bool isNew) {
|
||||
if (isNew) {
|
||||
_lastValidValue = Input.Value;
|
||||
_index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs the Z-score calculation for the current period.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The calculated Z-score value for the most recent input in the current period.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method calculates the Z-score using the formula:
|
||||
/// Z = (x - μ) / σ
|
||||
/// where x is the input value, μ is the mean of the period, and σ is the sample standard deviation.
|
||||
/// If there are fewer than 2 data points or if the standard deviation is 0, the method returns 0.
|
||||
/// </remarks>
|
||||
protected override double Calculation() {
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double zScore = 0;
|
||||
if (_buffer.Count >= 2) // We need at least 2 data points for Z-score
|
||||
{
|
||||
if (_buffer.Count >= 2) { // We need at least 2 data points for Z-score
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double n = values.Length;
|
||||
@@ -58,8 +86,7 @@ public class Zscore : AbstractBase
|
||||
double sumSquaredDeviations = values.Sum(x => Math.Pow(x - mean, 2));
|
||||
double standardDeviation = Math.Sqrt(sumSquaredDeviations / (n - 1)); // Sample standard deviation
|
||||
|
||||
if (standardDeviation != 0) // Avoid division by zero
|
||||
{
|
||||
if (standardDeviation != 0) { // Avoid division by zero
|
||||
zScore = (Input.Value - mean) / standardDeviation;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user