mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-13 16:18:05 +00:00
Class optimization
This commit is contained in:
+44
-22
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -36,11 +35,13 @@ namespace QuanTAlib;
|
||||
/// Note: Second-order derivative providing acceleration insights
|
||||
/// </remarks>
|
||||
|
||||
public class Curvature : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Curvature : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly Slope _slopeCalculator;
|
||||
private readonly CircularBuffer _slopeBuffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the y-intercept of the curvature line.
|
||||
@@ -64,6 +65,7 @@ public class Curvature : AbstractBase
|
||||
|
||||
/// <param name="period">The number of points to consider for calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is 2 or less.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Curvature(int period)
|
||||
{
|
||||
if (period <= 2)
|
||||
@@ -82,12 +84,14 @@ public class Curvature : AbstractBase
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Curvature(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();
|
||||
@@ -98,6 +102,7 @@ public class Curvature : AbstractBase
|
||||
Line = null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -107,6 +112,35 @@ public class Curvature : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double sumX, double sumY) CalculateSums(ReadOnlySpan<double> slopes, int count)
|
||||
{
|
||||
double sumX = 0, sumY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sumX += i + 1;
|
||||
sumY += slopes[i];
|
||||
}
|
||||
return (sumX, sumY);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double sumSqX, double sumSqY, double sumSqXY) CalculateSquaredSums(
|
||||
ReadOnlySpan<double> slopes, int count, double avgX, double avgY)
|
||||
{
|
||||
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;
|
||||
}
|
||||
return (sumSqX, sumSqY, sumSqXY);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -122,30 +156,17 @@ public class Curvature : AbstractBase
|
||||
}
|
||||
|
||||
int count = Math.Min(_slopeBuffer.Count, _period);
|
||||
var slopes = _slopeBuffer.GetSpan().ToArray();
|
||||
ReadOnlySpan<double> slopes = _slopeBuffer.GetSpan();
|
||||
|
||||
// Calculate averages
|
||||
double sumX = 0, sumY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sumX += i + 1;
|
||||
sumY += slopes[i];
|
||||
}
|
||||
var (sumX, sumY) = CalculateSums(slopes, count);
|
||||
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;
|
||||
}
|
||||
var (sumSqX, sumSqY, sumSqXY) = CalculateSquaredSums(slopes, count, avgX, avgY);
|
||||
|
||||
if (sumSqX > 0)
|
||||
if (sumSqX > Epsilon)
|
||||
{
|
||||
curvature = sumSqXY / sumSqX;
|
||||
Intercept = avgY - (curvature * avgX);
|
||||
@@ -155,9 +176,10 @@ public class Curvature : AbstractBase
|
||||
double stdDevY = Math.Sqrt(sumSqY / count);
|
||||
StdDev = stdDevY;
|
||||
|
||||
if (stdDevX * stdDevY != 0)
|
||||
double stdDevProduct = stdDevX * stdDevY;
|
||||
if (stdDevProduct > Epsilon)
|
||||
{
|
||||
double r = sumSqXY / (stdDevX * stdDevY) / count;
|
||||
double r = sumSqXY / (stdDevProduct) / count;
|
||||
RSquared = r * r;
|
||||
}
|
||||
|
||||
|
||||
+51
-29
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,41 +41,52 @@ namespace QuanTAlib;
|
||||
/// Note: Normalized to [0,1] for easier interpretation
|
||||
/// </remarks>
|
||||
|
||||
public class Entropy : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Entropy : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly Dictionary<double, int> _valueCounts;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const double DefaultEntropy = 1.0;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <param name="period">The number of points to consider for entropy calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Entropy(int period)
|
||||
{
|
||||
if (period < 2)
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for entropy calculation.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = 2; // Minimum number of points needed for entropy calculation
|
||||
WarmupPeriod = MinimumPoints; // Minimum number of points needed for entropy calculation
|
||||
_buffer = new CircularBuffer(period);
|
||||
_valueCounts = new Dictionary<double, int>();
|
||||
Name = $"Entropy(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for entropy calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Entropy(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();
|
||||
_buffer.Clear();
|
||||
_valueCounts.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -85,39 +96,50 @@ public class Entropy : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void CountValues(ReadOnlySpan<double> values, Dictionary<double, int> counts)
|
||||
{
|
||||
counts.Clear();
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
counts[values[i]] = counts.TryGetValue(values[i], out int count) ? count + 1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateShannonsEntropy(Dictionary<double, int> counts, int totalCount)
|
||||
{
|
||||
double entropy = 0;
|
||||
foreach (var count in counts.Values)
|
||||
{
|
||||
double probability = (double)count / totalCount;
|
||||
entropy -= probability * Math.Log2(probability);
|
||||
}
|
||||
return entropy;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double entropy = 0;
|
||||
if (_index > 1) // Need at least two data points for entropy calculation
|
||||
if (_index <= 1) // Need at least two data points for entropy calculation
|
||||
{
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
int n = values.Length;
|
||||
|
||||
// Calculate probabilities for each unique value
|
||||
var groupedValues = values.GroupBy(x => x).Select(g => new { Value = g.Key, Count = g.Count() });
|
||||
|
||||
// Calculate Shannon's entropy
|
||||
foreach (var group in groupedValues)
|
||||
{
|
||||
double probability = (double)group.Count / n;
|
||||
entropy -= probability * Math.Log2(probability);
|
||||
}
|
||||
|
||||
// Normalize by maximum possible entropy for current unique values
|
||||
int uniqueValueCount = groupedValues.Count();
|
||||
double maxEntropy = Math.Log2(uniqueValueCount);
|
||||
|
||||
entropy = entropy == 0 ? 1 : entropy / maxEntropy;
|
||||
}
|
||||
else
|
||||
{
|
||||
entropy = 1; // Maximum entropy when insufficient data
|
||||
return DefaultEntropy;
|
||||
}
|
||||
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
CountValues(values, _valueCounts);
|
||||
|
||||
// Calculate Shannon's entropy
|
||||
double entropy = CalculateShannonsEntropy(_valueCounts, values.Length);
|
||||
|
||||
// Normalize by maximum possible entropy for current unique values
|
||||
double maxEntropy = Math.Log2(_valueCounts.Count);
|
||||
entropy = maxEntropy < Epsilon ? DefaultEntropy : entropy / maxEntropy;
|
||||
|
||||
IsHot = _buffer.Count >= Period;
|
||||
return entropy;
|
||||
}
|
||||
|
||||
+57
-25
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -42,16 +41,20 @@ namespace QuanTAlib;
|
||||
/// Note: Returns excess kurtosis (normal distribution = 0)
|
||||
/// </remarks>
|
||||
|
||||
public class Kurtosis : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Kurtosis : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 4;
|
||||
|
||||
/// <param name="period">The number of points to consider for kurtosis calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 4.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Kurtosis(int period)
|
||||
{
|
||||
if (period < 4)
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 4 for kurtosis calculation.");
|
||||
@@ -65,18 +68,21 @@ public class Kurtosis : AbstractBase
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for kurtosis calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Kurtosis(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();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -86,6 +92,48 @@ public class Kurtosis : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMean(ReadOnlySpan<double> values)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
return sum / values.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double s2, double s4) CalculateDeviations(ReadOnlySpan<double> values, double mean)
|
||||
{
|
||||
double s2 = 0; // Sum of squared deviations
|
||||
double s4 = 0; // Sum of fourth power deviations
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double diff = values[i] - mean;
|
||||
double diff2 = diff * diff;
|
||||
s2 += diff2;
|
||||
s4 += diff2 * diff2;
|
||||
}
|
||||
|
||||
return (s2, s4);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSheskinKurtosis(double s2, double s4, int n)
|
||||
{
|
||||
double variance = s2 / (n - 1);
|
||||
double variance2 = variance * variance;
|
||||
|
||||
if (variance2 < Epsilon)
|
||||
return 0;
|
||||
|
||||
return (n * (n + 1) * s4) / (variance2 * (n - 3) * (n - 1) * (n - 2))
|
||||
- (3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -93,28 +141,12 @@ public class Kurtosis : AbstractBase
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double kurtosis = 0;
|
||||
if (_buffer.Count > 3) // Need at least 4 points for valid calculation
|
||||
if (_buffer.Count > MinimumPoints - 1) // Need at least 4 points for valid calculation
|
||||
{
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double n = values.Length;
|
||||
|
||||
// Calculate squared and fourth power deviations
|
||||
double s2 = 0; // Sum of squared deviations
|
||||
double s4 = 0; // Sum of fourth power deviations
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double diff = values[i] - mean;
|
||||
s2 += diff * diff;
|
||||
s4 += diff * diff * diff * diff;
|
||||
}
|
||||
|
||||
double variance = s2 / (n - 1);
|
||||
|
||||
// Sheskin Algorithm for excess kurtosis
|
||||
kurtosis = (n * (n + 1) * s4) / (variance * variance * (n - 3) * (n - 1) * (n - 2))
|
||||
- (3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)));
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
double mean = CalculateMean(values);
|
||||
var (s2, s4) = CalculateDeviations(values, mean);
|
||||
kurtosis = CalculateSheskinKurtosis(s2, s4, values.Length);
|
||||
}
|
||||
|
||||
IsHot = _buffer.Count >= Period;
|
||||
|
||||
+37
-7
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -40,7 +40,8 @@ namespace QuanTAlib;
|
||||
/// Note: Decay factor allows for adaptive peak tracking
|
||||
/// </remarks>
|
||||
|
||||
public class Max : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Max : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
@@ -49,11 +50,15 @@ public class Max : AbstractBase
|
||||
private double _p_currentMax;
|
||||
private int _timeSinceNewMax;
|
||||
private int _p_timeSinceNewMax;
|
||||
private const double DefaultDecay = 0.0;
|
||||
private const double DecayScaleFactor = 0.1;
|
||||
private const double Epsilon = 1e-10;
|
||||
|
||||
/// <param name="period">The number of points to consider for maximum calculation.</param>
|
||||
/// <param name="decay">Half-life decay factor (0 for no decay, higher for faster forgetting).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1 or decay is negative.</exception>
|
||||
public Max(int period, double decay = 0)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Max(int period, double decay = DefaultDecay)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
@@ -68,7 +73,7 @@ public class Max : AbstractBase
|
||||
Period = period;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_halfLife = decay * 0.1;
|
||||
_halfLife = decay * DecayScaleFactor;
|
||||
Name = $"Max(period={period}, halfLife={decay:F2})";
|
||||
Init();
|
||||
}
|
||||
@@ -76,12 +81,14 @@ public class Max : AbstractBase
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for maximum calculation.</param>
|
||||
/// <param name="decay">Half-life decay factor (default 0).</param>
|
||||
public Max(object source, int period, double decay = 0) : this(period, decay)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Max(object source, int period, double decay = DefaultDecay) : this(period, decay)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -89,6 +96,7 @@ public class Max : AbstractBase
|
||||
_timeSinceNewMax = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -106,6 +114,27 @@ public class Max : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private double CalculateDecayRate()
|
||||
{
|
||||
return 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double FindMaxValue(ReadOnlySpan<double> values)
|
||||
{
|
||||
double max = double.MinValue;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (values[i] > max)
|
||||
{
|
||||
max = values[i];
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -119,11 +148,12 @@ public class Max : AbstractBase
|
||||
}
|
||||
|
||||
// Apply decay based on time since last maximum
|
||||
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMax / Period);
|
||||
double decayRate = CalculateDecayRate();
|
||||
_currentMax -= decayRate * (_currentMax - _buffer.Average());
|
||||
|
||||
// Ensure maximum doesn't exceed current period's highest value
|
||||
_currentMax = Math.Min(_currentMax, _buffer.Max());
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
_currentMax = Math.Min(_currentMax, FindMaxValue(values));
|
||||
|
||||
IsHot = true;
|
||||
return _currentMax;
|
||||
|
||||
+54
-10
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -40,13 +39,15 @@ namespace QuanTAlib;
|
||||
/// Note: More robust than mean for non-normal distributions
|
||||
/// </remarks>
|
||||
|
||||
public class Median : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Median : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
|
||||
/// <param name="period">The number of points to consider for median calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Median(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
@@ -63,18 +64,21 @@ public class Median : AbstractBase
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for median calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Median(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();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -84,6 +88,46 @@ public class Median : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void QuickSort(Span<double> arr, int left, int right)
|
||||
{
|
||||
if (left < right)
|
||||
{
|
||||
int pivotIndex = Partition(arr, left, right);
|
||||
QuickSort(arr, left, pivotIndex - 1);
|
||||
QuickSort(arr, pivotIndex + 1, right);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static int Partition(Span<double> arr, int left, int right)
|
||||
{
|
||||
double pivot = arr[right];
|
||||
int i = left - 1;
|
||||
|
||||
for (int j = left; j < right; j++)
|
||||
{
|
||||
if (arr[j] <= pivot)
|
||||
{
|
||||
i++;
|
||||
(arr[i], arr[j]) = (arr[j], arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
(arr[i + 1], arr[right]) = (arr[right], arr[i + 1]);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMedian(Span<double> sortedValues)
|
||||
{
|
||||
int middleIndex = sortedValues.Length / 2;
|
||||
return (sortedValues.Length % 2 == 0)
|
||||
? (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0
|
||||
: sortedValues[middleIndex];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -92,15 +136,15 @@ public class Median : AbstractBase
|
||||
double median;
|
||||
if (_index >= Period)
|
||||
{
|
||||
// Get sorted copy of values
|
||||
var sortedValues = _buffer.GetSpan().ToArray();
|
||||
Array.Sort(sortedValues);
|
||||
int middleIndex = sortedValues.Length / 2;
|
||||
// Create a temporary buffer on the stack
|
||||
Span<double> values = stackalloc double[Period];
|
||||
_buffer.GetSpan().CopyTo(values);
|
||||
|
||||
// Sort values in-place
|
||||
QuickSort(values, 0, values.Length - 1);
|
||||
|
||||
// Calculate median based on odd/even count
|
||||
median = (sortedValues.Length % 2 == 0)
|
||||
? (sortedValues[middleIndex - 1] + sortedValues[middleIndex]) / 2.0
|
||||
: sortedValues[middleIndex];
|
||||
median = CalculateMedian(values);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+37
-7
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -40,7 +40,8 @@ namespace QuanTAlib;
|
||||
/// Note: Decay factor allows for adaptive low tracking
|
||||
/// </remarks>
|
||||
|
||||
public class Min : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Min : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
@@ -49,11 +50,15 @@ public class Min : AbstractBase
|
||||
private double _p_currentMin;
|
||||
private int _timeSinceNewMin;
|
||||
private int _p_timeSinceNewMin;
|
||||
private const double DefaultDecay = 0.0;
|
||||
private const double DecayScaleFactor = 0.1;
|
||||
private const double Epsilon = 1e-10;
|
||||
|
||||
/// <param name="period">The number of points to consider for minimum calculation.</param>
|
||||
/// <param name="decay">Half-life decay factor (0 for no decay, higher for faster forgetting).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1 or decay is negative.</exception>
|
||||
public Min(int period, double decay = 0)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Min(int period, double decay = DefaultDecay)
|
||||
{
|
||||
if (period < 1)
|
||||
{
|
||||
@@ -66,7 +71,7 @@ public class Min : AbstractBase
|
||||
Period = period;
|
||||
WarmupPeriod = 0;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_halfLife = decay * 0.1;
|
||||
_halfLife = decay * DecayScaleFactor;
|
||||
Name = $"Min(period={period}, halfLife={decay:F2})";
|
||||
Init();
|
||||
}
|
||||
@@ -74,12 +79,14 @@ public class Min : AbstractBase
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for minimum calculation.</param>
|
||||
/// <param name="decay">Half-life decay factor (default 0).</param>
|
||||
public Min(object source, int period, double decay = 0) : this(period, decay)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Min(object source, int period, double decay = DefaultDecay) : this(period, decay)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
@@ -87,6 +94,7 @@ public class Min : AbstractBase
|
||||
_timeSinceNewMin = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -104,6 +112,27 @@ public class Min : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private double CalculateDecayRate()
|
||||
{
|
||||
return 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double FindMinValue(ReadOnlySpan<double> values)
|
||||
{
|
||||
double min = double.MaxValue;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (values[i] < min)
|
||||
{
|
||||
min = values[i];
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -117,11 +146,12 @@ public class Min : AbstractBase
|
||||
}
|
||||
|
||||
// Apply decay based on time since last minimum
|
||||
double decayRate = 1 - Math.Exp(-_halfLife * _timeSinceNewMin / Period);
|
||||
double decayRate = CalculateDecayRate();
|
||||
_currentMin += decayRate * (_buffer.Average() - _currentMin);
|
||||
|
||||
// Ensure minimum doesn't fall below current period's lowest value
|
||||
_currentMin = Math.Max(_currentMin, _buffer.Min());
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
_currentMin = Math.Max(_currentMin, FindMinValue(values));
|
||||
|
||||
IsHot = true;
|
||||
return _currentMin;
|
||||
|
||||
+62
-18
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -40,13 +40,18 @@ namespace QuanTAlib;
|
||||
/// Note: Particularly useful for price level analysis
|
||||
/// </remarks>
|
||||
|
||||
public class Mode : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Mode : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly Dictionary<double, int> _frequencies;
|
||||
private readonly List<double> _modes;
|
||||
private const double Epsilon = 1e-10;
|
||||
|
||||
/// <param name="period">The number of points to consider for mode calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mode(int period)
|
||||
{
|
||||
if (period < 1)
|
||||
@@ -56,24 +61,31 @@ public class Mode : AbstractBase
|
||||
Period = period;
|
||||
WarmupPeriod = period;
|
||||
_buffer = new CircularBuffer(period);
|
||||
_frequencies = new Dictionary<double, int>();
|
||||
_modes = new List<double>();
|
||||
Name = $"Mode(period={period})";
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for mode calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Mode(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();
|
||||
_buffer.Clear();
|
||||
_frequencies.Clear();
|
||||
_modes.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -83,6 +95,49 @@ public class Mode : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void CountFrequencies(ReadOnlySpan<double> values)
|
||||
{
|
||||
_frequencies.Clear();
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
_frequencies[values[i]] = _frequencies.TryGetValue(values[i], out int count) ? count + 1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void FindModes()
|
||||
{
|
||||
_modes.Clear();
|
||||
int maxCount = 0;
|
||||
|
||||
foreach (var kvp in _frequencies)
|
||||
{
|
||||
if (kvp.Value > maxCount)
|
||||
{
|
||||
maxCount = kvp.Value;
|
||||
_modes.Clear();
|
||||
_modes.Add(kvp.Key);
|
||||
}
|
||||
else if (kvp.Value == maxCount)
|
||||
{
|
||||
_modes.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private double CalculateAverageMode()
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < _modes.Count; i++)
|
||||
{
|
||||
sum += _modes[i];
|
||||
}
|
||||
return sum / _modes.Count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -91,21 +146,10 @@ public class Mode : AbstractBase
|
||||
double mode;
|
||||
if (_index >= Period)
|
||||
{
|
||||
// Group values by frequency and order by count
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
var groupedValues = values.GroupBy(v => v)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.ThenBy(g => g.Key)
|
||||
.ToList();
|
||||
|
||||
// Find all values with highest frequency
|
||||
int maxCount = groupedValues.First().Count();
|
||||
var modes = groupedValues.TakeWhile(g => g.Count() == maxCount)
|
||||
.Select(g => g.Key)
|
||||
.ToList();
|
||||
|
||||
// Average multiple modes if present
|
||||
mode = modes.Average();
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
CountFrequencies(values);
|
||||
FindModes();
|
||||
mode = CalculateAverageMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,20 +40,24 @@ namespace QuanTAlib;
|
||||
/// Note: Particularly useful for risk metrics like VaR
|
||||
/// </remarks>
|
||||
|
||||
public class Percentile : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Percentile : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly double Percent;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <param name="period">The number of points to consider for percentile calculation.</param>
|
||||
/// <param name="percent">The percentile to calculate (0-100).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when period is less than 2 or percent is not between 0 and 100.
|
||||
/// </exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Percentile(int period, double percent)
|
||||
{
|
||||
if (period < 2)
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for percentile calculation.");
|
||||
@@ -66,7 +69,7 @@ public class Percentile : AbstractBase
|
||||
}
|
||||
Period = period;
|
||||
Percent = percent;
|
||||
WarmupPeriod = 2; // Minimum number of points needed for percentile calculation
|
||||
WarmupPeriod = MinimumPoints; // Minimum number of points needed for percentile calculation
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"Percentile(period={period}, percent={percent})";
|
||||
Init();
|
||||
@@ -75,18 +78,21 @@ public class Percentile : AbstractBase
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for percentile calculation.</param>
|
||||
/// <param name="percent">The percentile to calculate (0-100).</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Percentile(object source, int period, double percent) : this(period, percent)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -96,6 +102,56 @@ public class Percentile : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void QuickSort(Span<double> arr, int left, int right)
|
||||
{
|
||||
if (left < right)
|
||||
{
|
||||
int pivotIndex = Partition(arr, left, right);
|
||||
QuickSort(arr, left, pivotIndex - 1);
|
||||
QuickSort(arr, pivotIndex + 1, right);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static int Partition(Span<double> arr, int left, int right)
|
||||
{
|
||||
double pivot = arr[right];
|
||||
int i = left - 1;
|
||||
|
||||
for (int j = left; j < right; j++)
|
||||
{
|
||||
if (arr[j] <= pivot)
|
||||
{
|
||||
i++;
|
||||
(arr[i], arr[j]) = (arr[j], arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
(arr[i + 1], arr[right]) = (arr[right], arr[i + 1]);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private double CalculatePercentile(Span<double> sortedValues)
|
||||
{
|
||||
double position = (Percent / 100.0) * (sortedValues.Length - 1);
|
||||
int lowerIndex = (int)Math.Floor(position);
|
||||
int upperIndex = (int)Math.Ceiling(position);
|
||||
|
||||
if (lowerIndex == upperIndex)
|
||||
{
|
||||
return sortedValues[lowerIndex];
|
||||
}
|
||||
|
||||
// Linear interpolation between adjacent values
|
||||
double lowerValue = sortedValues[lowerIndex];
|
||||
double upperValue = sortedValues[upperIndex];
|
||||
double fraction = position - lowerIndex;
|
||||
return lowerValue + (upperValue - lowerValue) * fraction;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -104,26 +160,12 @@ public class Percentile : AbstractBase
|
||||
double result;
|
||||
if (_buffer.Count >= Period)
|
||||
{
|
||||
// Sort values and calculate percentile position
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
Array.Sort(values);
|
||||
// Create a temporary buffer on the stack and sort values
|
||||
Span<double> values = stackalloc double[Period];
|
||||
_buffer.GetSpan().CopyTo(values);
|
||||
QuickSort(values, 0, values.Length - 1);
|
||||
|
||||
double position = (Percent / 100.0) * (values.Length - 1);
|
||||
int lowerIndex = (int)Math.Floor(position);
|
||||
int upperIndex = (int)Math.Ceiling(position);
|
||||
|
||||
if (lowerIndex == upperIndex)
|
||||
{
|
||||
result = values[lowerIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Linear interpolation between adjacent values
|
||||
double lowerValue = values[lowerIndex];
|
||||
double upperValue = values[upperIndex];
|
||||
double fraction = position - lowerIndex;
|
||||
result = lowerValue + (upperValue - lowerValue) * fraction;
|
||||
}
|
||||
result = CalculatePercentile(values);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+56
-30
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -44,22 +43,26 @@ namespace QuanTAlib;
|
||||
/// Note: Requires minimum of 3 data points for calculation
|
||||
/// </remarks>
|
||||
|
||||
public class Skew : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Skew : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 3;
|
||||
|
||||
/// <param name="period">The number of points to consider for skewness calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 3.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Skew(int period)
|
||||
{
|
||||
if (period < 3)
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 3 for skewness calculation.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = 3;
|
||||
WarmupPeriod = MinimumPoints;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"Skew(period={period})";
|
||||
Init();
|
||||
@@ -67,18 +70,21 @@ public class Skew : AbstractBase
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for skewness calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Skew(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();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -88,38 +94,58 @@ public class Skew : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMean(ReadOnlySpan<double> values)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
return sum / values.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double m3, double m2) CalculateMoments(ReadOnlySpan<double> values, double mean)
|
||||
{
|
||||
double sumCubedDeviations = 0;
|
||||
double sumSquaredDeviations = 0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double deviation = values[i] - mean;
|
||||
double squared = deviation * deviation;
|
||||
sumSquaredDeviations += squared;
|
||||
sumCubedDeviations += squared * deviation;
|
||||
}
|
||||
|
||||
double n = values.Length;
|
||||
return (sumCubedDeviations / n, sumSquaredDeviations / n);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSkewness(double m3, double m2, int n)
|
||||
{
|
||||
double s3 = Math.Pow(m2, 1.5);
|
||||
if (s3 < Epsilon)
|
||||
return 0;
|
||||
|
||||
return (Math.Sqrt(n * (n - 1)) / (n - 2)) * (m3 / s3);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double skew = 0;
|
||||
if (_buffer.Count >= 3) // Need at least 3 points for skewness
|
||||
if (_buffer.Count >= MinimumPoints) // Need at least 3 points for skewness
|
||||
{
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double n = values.Length;
|
||||
|
||||
// Calculate third and second moments
|
||||
double sumCubedDeviations = 0;
|
||||
double sumSquaredDeviations = 0;
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
double deviation = value - mean;
|
||||
sumCubedDeviations += Math.Pow(deviation, 3);
|
||||
sumSquaredDeviations += Math.Pow(deviation, 2);
|
||||
}
|
||||
|
||||
// Fisher-Pearson standardized moment coefficient
|
||||
double m3 = sumCubedDeviations / n;
|
||||
double m2 = sumSquaredDeviations / n;
|
||||
double s3 = Math.Pow(m2, 1.5);
|
||||
|
||||
if (s3 != 0) // Avoid division by zero
|
||||
{
|
||||
skew = (Math.Sqrt(n * (n - 1)) / (n - 2)) * (m3 / s3);
|
||||
}
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
double mean = CalculateMean(values);
|
||||
var (m3, m2) = CalculateMoments(values, mean);
|
||||
skew = CalculateSkewness(m3, m2, values.Length);
|
||||
}
|
||||
|
||||
IsHot = _buffer.Count >= Period;
|
||||
|
||||
+48
-25
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -43,11 +42,14 @@ namespace QuanTAlib;
|
||||
/// Note: Provides additional regression statistics (R², intercept)
|
||||
/// </remarks>
|
||||
|
||||
public class Slope : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Slope : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private readonly CircularBuffer _timeBuffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <summary>Gets the y-intercept of the regression line.</summary>
|
||||
public double? Intercept { get; private set; }
|
||||
@@ -63,6 +65,7 @@ public class Slope : AbstractBase
|
||||
|
||||
/// <param name="period">The number of points to consider for slope calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than or equal to 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Slope(int period)
|
||||
{
|
||||
if (period <= 1)
|
||||
@@ -80,12 +83,14 @@ public class Slope : AbstractBase
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for slope calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Slope(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();
|
||||
@@ -97,6 +102,7 @@ public class Slope : AbstractBase
|
||||
Line = null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -106,33 +112,22 @@ public class Slope : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
protected override double Calculation()
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double sumX, double sumY) CalculateSums(ReadOnlySpan<double> values, int count)
|
||||
{
|
||||
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; // Need at least 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;
|
||||
return (sumX, sumY);
|
||||
}
|
||||
|
||||
// Least squares regression
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static (double sumSqX, double sumSqY, double sumSqXY) CalculateSquaredSums(
|
||||
ReadOnlySpan<double> values, int count, double avgX, double avgY)
|
||||
{
|
||||
double sumSqX = 0, sumSqY = 0, sumSqXY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
@@ -142,8 +137,35 @@ public class Slope : AbstractBase
|
||||
sumSqY += devY * devY;
|
||||
sumSqXY += devX * devY;
|
||||
}
|
||||
return (sumSqX, sumSqY, sumSqXY);
|
||||
}
|
||||
|
||||
if (sumSqX > 0)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
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 < MinimumPoints)
|
||||
{
|
||||
return slope; // Need at least 2 points
|
||||
}
|
||||
|
||||
int count = Math.Min(_buffer.Count, _period);
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
|
||||
// Calculate averages
|
||||
var (sumX, sumY) = CalculateSums(values, count);
|
||||
double avgX = sumX / count;
|
||||
double avgY = sumY / count;
|
||||
|
||||
// Least squares regression
|
||||
var (sumSqX, sumSqY, sumSqXY) = CalculateSquaredSums(values, count, avgX, avgY);
|
||||
|
||||
if (sumSqX > Epsilon)
|
||||
{
|
||||
// Calculate slope and related statistics
|
||||
slope = sumSqXY / sumSqX;
|
||||
@@ -154,9 +176,10 @@ public class Slope : AbstractBase
|
||||
double stdDevY = Math.Sqrt(sumSqY / count);
|
||||
StdDev = stdDevY;
|
||||
|
||||
if (stdDevX * stdDevY != 0)
|
||||
double stdDevProduct = stdDevX * stdDevY;
|
||||
if (stdDevProduct > Epsilon)
|
||||
{
|
||||
double r = sumSqXY / (stdDevX * stdDevY) / count;
|
||||
double r = sumSqXY / stdDevProduct / count;
|
||||
RSquared = r * r;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -44,17 +43,21 @@ namespace QuanTAlib;
|
||||
/// Note: Foundation for many volatility-based indicators
|
||||
/// </remarks>
|
||||
|
||||
public class Stddev : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Stddev : AbstractBase
|
||||
{
|
||||
private readonly bool IsPopulation;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <param name="period">The number of points to consider for standard deviation calculation.</param>
|
||||
/// <param name="isPopulation">True for population stddev, false for sample stddev (default).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Stddev(int period, bool isPopulation = false)
|
||||
{
|
||||
if (period < 2)
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2.");
|
||||
@@ -69,18 +72,21 @@ public class Stddev : AbstractBase
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for standard deviation calculation.</param>
|
||||
/// <param name="isPopulation">True for population stddev, false for sample stddev (default).</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Stddev(object source, int period, bool isPopulation = false) : this(period, isPopulation)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -90,6 +96,30 @@ public class Stddev : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMean(ReadOnlySpan<double> values)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
return sum / values.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSumSquaredDeviations(ReadOnlySpan<double> values, double mean)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double diff = values[i] - mean;
|
||||
sum += diff * diff;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -98,11 +128,9 @@ public class Stddev : AbstractBase
|
||||
double stddev = 0;
|
||||
if (_buffer.Count > 1)
|
||||
{
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
|
||||
// Calculate sum of squared deviations
|
||||
double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
double mean = CalculateMean(values);
|
||||
double sumOfSquaredDifferences = CalculateSumSquaredDeviations(values, mean);
|
||||
|
||||
// Use appropriate divisor based on population/sample calculation
|
||||
double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -44,17 +43,21 @@ namespace QuanTAlib;
|
||||
/// Note: Basis for Modern Portfolio Theory and risk models
|
||||
/// </remarks>
|
||||
|
||||
public class Variance : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Variance : AbstractBase
|
||||
{
|
||||
private readonly bool IsPopulation;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <param name="period">The number of points to consider for variance calculation.</param>
|
||||
/// <param name="isPopulation">True for population variance, false for sample variance (default).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Variance(int period, bool isPopulation = false)
|
||||
{
|
||||
if (period < 2)
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2.");
|
||||
@@ -69,18 +72,21 @@ public class Variance : AbstractBase
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for variance calculation.</param>
|
||||
/// <param name="isPopulation">True for population variance, false for sample variance (default).</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Variance(object source, int period, bool isPopulation = false) : this(period, isPopulation)
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -90,6 +96,30 @@ public class Variance : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMean(ReadOnlySpan<double> values)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
return sum / values.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateSumSquaredDeviations(ReadOnlySpan<double> values, double mean)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double diff = values[i] - mean;
|
||||
sum += diff * diff;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
@@ -98,11 +128,9 @@ public class Variance : AbstractBase
|
||||
double variance = 0;
|
||||
if (_buffer.Count > 1)
|
||||
{
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
|
||||
// Calculate sum of squared deviations
|
||||
double sumOfSquaredDifferences = values.Sum(x => Math.Pow(x - mean, 2));
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
double mean = CalculateMean(values);
|
||||
double sumOfSquaredDifferences = CalculateSumSquaredDeviations(values, mean);
|
||||
|
||||
// Use appropriate divisor based on population/sample calculation
|
||||
double divisor = IsPopulation ? _buffer.Count : _buffer.Count - 1;
|
||||
|
||||
+40
-14
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
@@ -43,22 +42,26 @@ namespace QuanTAlib;
|
||||
/// Note: Assumes approximately normal distribution
|
||||
/// </remarks>
|
||||
|
||||
public class Zscore : AbstractBase
|
||||
[SkipLocalsInit]
|
||||
public sealed class Zscore : AbstractBase
|
||||
{
|
||||
private readonly int Period;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private const double Epsilon = 1e-10;
|
||||
private const int MinimumPoints = 2;
|
||||
|
||||
/// <param name="period">The number of points to consider for Z-score calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Zscore(int period)
|
||||
{
|
||||
if (period < 2)
|
||||
if (period < MinimumPoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(period),
|
||||
"Period must be greater than or equal to 2 for Z-score calculation.");
|
||||
}
|
||||
Period = period;
|
||||
WarmupPeriod = 2;
|
||||
WarmupPeriod = MinimumPoints;
|
||||
_buffer = new CircularBuffer(period);
|
||||
Name = $"ZScore(period={period})";
|
||||
Init();
|
||||
@@ -66,18 +69,21 @@ public class Zscore : AbstractBase
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of points to consider for Z-score calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Zscore(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();
|
||||
_buffer.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void ManageState(bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
@@ -87,23 +93,43 @@ public class Zscore : AbstractBase
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateMean(ReadOnlySpan<double> values)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
return sum / values.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static double CalculateStandardDeviation(ReadOnlySpan<double> values, double mean)
|
||||
{
|
||||
double sumSquaredDeviations = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double deviation = values[i] - mean;
|
||||
sumSquaredDeviations += deviation * deviation;
|
||||
}
|
||||
return Math.Sqrt(sumSquaredDeviations / (values.Length - 1));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
protected override double Calculation()
|
||||
{
|
||||
ManageState(Input.IsNew);
|
||||
_buffer.Add(Input.Value, Input.IsNew);
|
||||
|
||||
double zScore = 0;
|
||||
if (_buffer.Count >= 2) // Need at least 2 points for standard deviation
|
||||
if (_buffer.Count >= MinimumPoints) // Need at least 2 points for standard deviation
|
||||
{
|
||||
var values = _buffer.GetSpan().ToArray();
|
||||
double mean = values.Average();
|
||||
double n = values.Length;
|
||||
ReadOnlySpan<double> values = _buffer.GetSpan();
|
||||
double mean = CalculateMean(values);
|
||||
double standardDeviation = CalculateStandardDeviation(values, mean);
|
||||
|
||||
// Calculate sample standard deviation
|
||||
double sumSquaredDeviations = values.Sum(x => Math.Pow(x - mean, 2));
|
||||
double standardDeviation = Math.Sqrt(sumSquaredDeviations / (n - 1));
|
||||
|
||||
if (standardDeviation != 0) // Avoid division by zero
|
||||
if (standardDeviation > Epsilon) // Avoid division by zero
|
||||
{
|
||||
zScore = (Input.Value - mean) / standardDeviation;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user