namespace QuanTAlib; /// /// Calculates the rate of change of the slope over a specified period. /// Provides insights into trend acceleration or deceleration. /// 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; } /// /// Initializes a new instance of the Curvature class. /// /// The number of data points to consider for calculation. /// /// Thrown when the period is 2 or less. /// public Curvature(int period) { 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(); } /// /// Initializes a new instance of the Curvature class with a data source. /// /// The source object that publishes data. /// The number of data points to consider. public Curvature(object source, int period) : this(period) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } /// /// Resets the Curvature indicator to its initial state. /// public override void Init() { base.Init(); _slopeBuffer.Clear(); Intercept = null; StdDev = null; RSquared = null; Line = null; } /// /// Manages the state of the indicator. /// /// Indicates if the current data point is new. protected override void ManageState(bool isNew) { if (isNew) { _lastValidValue = Input.Value; _index++; } } /// /// Performs the curvature calculation. /// /// /// The calculated curvature value. Positive for increasing slope, negative for decreasing. /// /// /// Uses least squares method for optimal calculation. Also computes additional statistics /// such as Intercept, Standard Deviation, R-Squared, and Line value. /// 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) { 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; } }