New version merge

This commit is contained in:
Miha Kralj
2024-09-22 17:31:24 -07:00
parent 1b719fa94e
commit d475bcd19a
405 changed files with 56573 additions and 12440 deletions
+80
View File
@@ -0,0 +1,80 @@
namespace QuanTAlib;
using System;
using System.Linq;
// Shannon's Entropy calculation
public class Entropy : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
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.");
}
Period = period;
WarmupPeriod = 2;
_buffer = new CircularBuffer(period);
Name = $"Entropy(period={period})";
Init();
}
public Entropy(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();
}
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 entropy = 0;
if (_index > 1) // We need at least two data points for entropy calculation
{
var values = _buffer.GetSpan().ToArray();
int n = values.Length;
// Calculate probabilities
var groupedValues = values.GroupBy(x => x).Select(g => new { Value = g.Key, Count = g.Count() });
// Use the actual count of values for probability calculation
foreach (var group in groupedValues)
{
double probability = (double)group.Count / n;
entropy -= probability * Math.Log2(probability);
}
// Normalize the entropy based on the current number of unique values
int uniqueValueCount = groupedValues.Count();
double maxEntropy = Math.Log2(uniqueValueCount);
entropy = entropy == 0 ? 1 : entropy / maxEntropy;
}
else { entropy = 1; }
IsHot = _buffer.Count >= Period;
return entropy;
}
}
+76
View File
@@ -0,0 +1,76 @@
namespace QuanTAlib;
// Excess kurtosis calculated with Sheskin Algorithm
public class Kurtosis : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
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.");
}
Period = period;
WarmupPeriod = Period - 1;
_buffer = new CircularBuffer(period);
Name = $"Kurtosis(period={period})";
Init();
}
public Kurtosis(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();
}
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 kurtosis = 0;
if (_buffer.Count > 3)
{
var values = _buffer.GetSpan().ToArray();
double mean = values.Average();
double n = values.Length;
double s2 = 0;
double s4 = 0;
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);
// Using the Sheskin Algorithm for kurtosis
kurtosis = (n * (n + 1) * s4) / (variance * variance * (n - 3) * (n - 1) * (n - 2))
- (3 * (n - 1) * (n - 1) / ((n - 2) * (n - 3)));
}
IsHot = _buffer.Count >= Period;
return kurtosis;
}
}
+80
View File
@@ -0,0 +1,80 @@
using System;
namespace QuanTAlib
{
public class Max : AbstractBase
{
public readonly int Period;
private 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)
{
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();
}
public Max(object source, int period, double decay = 0) : this(period, decay)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_currentMax = double.MinValue;
_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;
}
}
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;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
using System;
using System.Linq;
namespace QuanTAlib
{
public class Median : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
public Median(int period) : base()
{
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();
}
public Median(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
}
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 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];
}
}
else
{
median = _buffer.Average(); // Use average until we have enough data points
}
IsHot = _index >= WarmupPeriod;
return median;
}
}
}
+80
View File
@@ -0,0 +1,80 @@
using System;
namespace QuanTAlib
{
public class Min : AbstractBase
{
public readonly int Period;
private 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();
}
public Min(object source, int period, double decay = 0) : this(period, decay)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
_currentMin = double.MaxValue;
_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;
}
}
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;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
namespace QuanTAlib;
public class Mode : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
public Mode(int period) : base()
{
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 = $"Mode(period={period})";
Init();
}
public Mode(object source, int period) : this(period)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
public override void Init()
{
base.Init();
}
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 mode;
if (_index >= Period)
{
var values = _buffer.GetSpan().ToArray();
var groupedValues = values.GroupBy(v => v)
.OrderByDescending(g => g.Count())
.ThenBy(g => g.Key)
.ToList();
int maxCount = groupedValues.First().Count();
var modes = groupedValues.TakeWhile(g => g.Count() == maxCount)
.Select(g => g.Key)
.ToList();
mode = modes.Average(); // If there are multiple modes, we return their average
}
else
{
mode = _buffer.Average(); // Use average until we have enough data points
}
IsHot = _index >= WarmupPeriod;
return mode;
}
}
+88
View File
@@ -0,0 +1,88 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Percentile : AbstractBase
{
public readonly int Period;
public readonly double Percent;
private CircularBuffer _buffer;
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)
{
throw new ArgumentOutOfRangeException(nameof(percent), "Percent must be between 0 and 100.");
}
Period = period;
Percent = percent;
WarmupPeriod = 2;
_buffer = new CircularBuffer(period);
Name = $"Percentile(period={period}, percent={percent})";
Init();
}
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()
{
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 result;
if (_buffer.Count >= Period)
{
var values = _buffer.GetSpan().ToArray();
Array.Sort(values);
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
{
// Interpolate between the two nearest values
double lowerValue = values[lowerIndex];
double upperValue = values[upperIndex];
double fraction = position - lowerIndex;
result = lowerValue + (upperValue - lowerValue) * fraction;
}
}
else
{
// Use average for insufficient data, like the Median class
result = _buffer.Average();
}
IsHot = _buffer.Count >= Period;
return result;
}
}
+82
View File
@@ -0,0 +1,82 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Skew : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
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;
WarmupPeriod = 3;
_buffer = new CircularBuffer(period);
Name = $"Skew(period={period})";
Init();
}
public Skew(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();
}
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 skew = 0;
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;
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);
}
// Calculate sample skewness using the adjusted 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);
}
}
IsHot = _buffer.Count >= Period;
return skew;
}
}
+69
View File
@@ -0,0 +1,69 @@
using System;
using System.Linq;
namespace QuanTAlib
{
public class Stddev : AbstractBase
{
public readonly int Period;
public readonly bool IsPopulation;
private 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();
}
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();
}
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;
}
}
}
+68
View File
@@ -0,0 +1,68 @@
using System;
using System.Linq;
namespace QuanTAlib
{
public class Variance : AbstractBase
{
public readonly int Period;
public readonly bool IsPopulation;
private 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();
}
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();
}
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;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
namespace QuanTAlib;
using System;
using System.Linq;
public class Zscore : AbstractBase
{
public readonly int Period;
private CircularBuffer _buffer;
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;
WarmupPeriod = 2;
_buffer = new CircularBuffer(period);
Name = $"ZScore(period={period})";
Init();
}
public Zscore(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();
}
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 zScore = 0;
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;
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
{
zScore = (Input.Value - mean) / standardDeviation;
}
}
IsHot = _buffer.Count >= Period;
return zScore;
}
}