Files
QuanTAlib/lib/statistics/Slope.cs
T

163 lines
5.2 KiB
C#
Raw Normal View History

2024-10-05 15:20:13 -07:00
namespace QuanTAlib;
/// <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>
2024-10-06 06:59:26 +00:00
public class Slope : AbstractBase
{
2024-10-05 15:20:13 -07:00
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; }
/// <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>
2024-10-06 06:59:26 +00:00
public Slope(int period)
{
if (period <= 1)
{
2024-10-05 15:20:13 -07:00
throw new ArgumentOutOfRangeException(nameof(period), period,
"Period must be greater than 1 for Slope/Linear Regression.");
2024-09-30 18:59:05 -07:00
}
2024-10-05 15:20:13 -07:00
_period = period;
WarmupPeriod = period;
_buffer = new CircularBuffer(period);
_timeBuffer = new CircularBuffer(period);
Name = $"Slope(period={period})";
2024-09-30 18:59:05 -07:00
2024-10-05 15:20:13 -07:00
Init();
}
2024-09-30 18:59:05 -07:00
2024-10-05 15:20:13 -07:00
/// <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>
2024-10-06 06:59:26 +00:00
public Slope(object source, int period) : this(period)
{
2024-10-05 15:20:13 -07:00
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
2024-09-30 18:59:05 -07:00
2024-10-05 15:20:13 -07:00
/// <summary>
/// Initializes the Slope instance by clearing buffers and resetting calculated values.
/// </summary>
2024-10-06 06:59:26 +00:00
public override void Init()
{
2024-10-05 15:20:13 -07:00
base.Init();
_buffer.Clear();
_timeBuffer.Clear();
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
}
2024-09-30 18:59:05 -07:00
2024-10-05 15:20:13 -07:00
/// <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>
2024-10-06 06:59:26 +00:00
protected override void ManageState(bool isNew)
{
if (isNew)
{
2024-10-05 15:20:13 -07:00
_lastValidValue = Input.Value;
_index++;
}
}
2024-09-30 18:59:05 -07:00
2024-10-05 15:20:13 -07:00
/// <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>
2024-10-06 06:59:26 +00:00
protected override double Calculation()
{
2024-10-05 15:20:13 -07:00
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
_timeBuffer.Add(Input.Time.Ticks, Input.IsNew);
double slope = 0;
2024-10-06 06:59:26 +00:00
if (_buffer.Count < 2)
{
2024-10-05 15:20:13 -07:00
return slope; // Return 0 when there are fewer than 2 points
}
2024-09-30 18:59:05 -07:00
2024-10-05 15:20:13 -07:00
int count = Math.Min(_buffer.Count, _period);
var values = _buffer.GetSpan().ToArray();
2024-09-30 18:59:05 -07:00
2024-10-05 15:20:13 -07:00
// Calculate averages
double sumX = 0, sumY = 0;
2024-10-06 06:59:26 +00:00
for (int i = 0; i < count; i++)
{
2024-10-05 15:20:13 -07:00
sumX += i + 1;
sumY += values[i];
}
double avgX = sumX / count;
double avgY = sumY / count;
// Least squares method
double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
2024-10-06 06:59:26 +00:00
for (int i = 0; i < count; i++)
{
2024-10-05 15:20:13 -07:00
double devX = (i + 1) - avgX;
double devY = values[i] - avgY;
sumSqX += devX * devX;
sumSqY += devY * devY;
sumSqXY += devX * devY;
}
2024-09-30 18:59:05 -07:00
2024-10-06 06:59:26 +00:00
if (sumSqX > 0)
{
2024-10-05 15:20:13 -07:00
slope = sumSqXY / sumSqX;
Intercept = avgY - (slope * avgX);
2024-09-30 18:59:05 -07:00
2024-10-05 15:20:13 -07:00
// Calculate Standard Deviation and R-Squared
double stdDevX = Math.Sqrt(sumSqX / count);
double stdDevY = Math.Sqrt(sumSqY / count);
StdDev = stdDevY;
2024-09-30 18:59:05 -07:00
2024-10-06 06:59:26 +00:00
if (stdDevX * stdDevY != 0)
{
2024-10-05 15:20:13 -07:00
double r = sumSqXY / (stdDevX * stdDevY) / count;
RSquared = r * r;
2024-09-30 18:59:05 -07:00
}
2024-10-05 15:20:13 -07:00
// Calculate last Line value (y = mx + b)
Line = (slope * count) + Intercept;
2024-10-06 06:59:26 +00:00
}
else
{
2024-10-05 15:20:13 -07:00
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
2024-09-30 18:59:05 -07:00
}
2024-10-05 15:20:13 -07:00
IsHot = _buffer.Count == _period;
return slope;
2024-09-30 18:59:05 -07:00
}
2024-10-05 15:20:13 -07:00
}