first iteration

This commit is contained in:
Miha Kralj
2025-11-25 20:40:46 -08:00
parent b5881b9bb4
commit 33ffd3a37a
594 changed files with 117007 additions and 80111 deletions
-159
View File
@@ -1,159 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// BETA: Beta Coefficient
/// A statistical measure that quantifies the volatility of an asset or portfolio
/// in relation to the overall market. Beta is used to assess the risk and return
/// characteristics of an investment.
/// </summary>
/// <remarks>
/// The Beta calculation process:
/// 1. Calculates covariance between asset and market returns
/// 2. Computes variance of market returns
/// 3. Divides covariance by market variance
///
/// Key characteristics:
/// - Measures relative volatility
/// - Beta > 1: More volatile than market
/// - Beta < 1: Less volatile than market
/// - Beta = 1: Same volatility as market
/// - Beta < 0: Inverse relationship with market
///
/// Formula:
/// β = Cov(Ra, Rm) / Var(Rm)
/// where:
/// Ra = asset returns
/// Rm = market returns
///
/// Market Applications:
/// - Risk assessment
/// - Portfolio management
/// - Asset allocation
/// - Performance analysis
/// - Hedging strategies
///
/// Sources:
/// https://en.wikipedia.org/wiki/Beta_(finance)
/// "Modern Portfolio Theory" - Harry Markowitz
///
/// Note: Assumes linear relationship between asset and market returns
/// </remarks>
[SkipLocalsInit]
public sealed class Beta : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _assetReturns;
private readonly CircularBuffer _marketReturns;
private const double Epsilon = 1e-10;
private const int MinimumPoints = 2;
/// <param name="period">The number of points to consider for beta calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Beta(int period)
{
if (period < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for beta calculation.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_assetReturns = new CircularBuffer(period);
_marketReturns = new CircularBuffer(period);
Name = $"Beta(period={period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of points to consider for beta calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Beta(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();
_assetReturns.Clear();
_marketReturns.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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 CalculateCovariance(ReadOnlySpan<double> assetReturns, ReadOnlySpan<double> marketReturns, double assetMean, double marketMean)
{
double covariance = 0;
for (int i = 0; i < assetReturns.Length; i++)
{
covariance += (assetReturns[i] - assetMean) * (marketReturns[i] - marketMean);
}
return covariance / assetReturns.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateVariance(ReadOnlySpan<double> values, double mean)
{
double variance = 0;
for (int i = 0; i < values.Length; i++)
{
double diff = values[i] - mean;
variance += diff * diff;
}
return variance / values.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
_assetReturns.Add(Input.Value, Input.IsNew);
_marketReturns.Add(Input2.Value, Input.IsNew);
double beta = 0;
if (_assetReturns.Count >= MinimumPoints && _marketReturns.Count >= MinimumPoints)
{
ReadOnlySpan<double> assetValues = _assetReturns.GetSpan();
ReadOnlySpan<double> marketValues = _marketReturns.GetSpan();
double assetMean = CalculateMean(assetValues);
double marketMean = CalculateMean(marketValues);
double covariance = CalculateCovariance(assetValues, marketValues, assetMean, marketMean);
double marketVariance = CalculateVariance(marketValues, marketMean);
if (marketVariance > Epsilon)
{
beta = covariance / marketVariance;
}
}
IsHot = _assetReturns.Count >= Period && _marketReturns.Count >= Period;
return beta;
}
}
-163
View File
@@ -1,163 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// CORR: Correlation Coefficient
/// A statistical measure that quantifies the strength and direction of the relationship
/// between two variables. The correlation coefficient ranges from -1 to 1, where 1 indicates
/// a perfect positive correlation, -1 indicates a perfect negative correlation, and 0 indicates
/// no correlation.
/// </summary>
/// <remarks>
/// The Correlation calculation process:
/// 1. Calculates mean of both variables
/// 2. Computes covariance between variables
/// 3. Calculates standard deviation of both variables
/// 4. Divides covariance by product of standard deviations
///
/// Key characteristics:
/// - Measures linear relationship strength
/// - Symmetric around zero
/// - Scale-independent measure
/// - Sensitive to outliers
/// - Useful for portfolio diversification
///
/// Formula:
/// ρ = Cov(X, Y) / (σX * σY)
/// where:
/// X, Y = variables
/// Cov = covariance
/// σ = standard deviation
///
/// Market Applications:
/// - Portfolio diversification
/// - Risk management
/// - Pairs trading
/// - Performance analysis
/// - Market sentiment analysis
///
/// Sources:
/// https://en.wikipedia.org/wiki/Correlation_coefficient
/// "Modern Portfolio Theory" - Harry Markowitz
///
/// Note: Assumes linear relationship between variables
/// </remarks>
[SkipLocalsInit]
public sealed class Corr : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _xValues;
private readonly CircularBuffer _yValues;
private const double Epsilon = 1e-10;
private const int MinimumPoints = 2;
/// <param name="period">The number of points to consider for correlation calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Corr(int period)
{
if (period < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for correlation calculation.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_xValues = new CircularBuffer(period);
_yValues = new CircularBuffer(period);
Name = $"Corr(period={period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of points to consider for correlation calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Corr(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();
_xValues.Clear();
_yValues.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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 CalculateCovariance(ReadOnlySpan<double> xValues, ReadOnlySpan<double> yValues, double xMean, double yMean)
{
double covariance = 0;
for (int i = 0; i < xValues.Length; i++)
{
covariance += (xValues[i] - xMean) * (yValues[i] - yMean);
}
return covariance / xValues.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);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
_xValues.Add(Input.Value, Input.IsNew);
_yValues.Add(Input2.Value, Input.IsNew);
double correlation = 0;
if (_xValues.Count >= MinimumPoints && _yValues.Count >= MinimumPoints)
{
ReadOnlySpan<double> xValues = _xValues.GetSpan();
ReadOnlySpan<double> yValues = _yValues.GetSpan();
double xMean = CalculateMean(xValues);
double yMean = CalculateMean(yValues);
double covariance = CalculateCovariance(xValues, yValues, xMean, yMean);
double xStdDev = CalculateStandardDeviation(xValues, xMean);
double yStdDev = CalculateStandardDeviation(yValues, yMean);
if (xStdDev > Epsilon && yStdDev > Epsilon)
{
correlation = covariance / (xStdDev * yStdDev);
}
}
IsHot = _xValues.Count >= Period && _yValues.Count >= Period;
return correlation;
}
}
-142
View File
@@ -1,142 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// COVAR: Covariance
/// A statistical measure that quantifies how two variables change together. Unlike correlation,
/// covariance is not normalized and therefore is scale-dependent. A positive covariance indicates
/// that variables tend to move in the same direction, while a negative covariance indicates
/// opposite movement.
/// </summary>
/// <remarks>
/// The Covariance calculation process:
/// 1. Calculates mean of both variables
/// 2. For each pair of points, multiply their deviations from their respective means
/// 3. Sum these products and divide by the number of observations
///
/// Key characteristics:
/// - Measures linear relationship
/// - Scale-dependent measure
/// - Sign indicates direction of relationship
/// - Magnitude depends on scale of variables
/// - Basis for correlation coefficient
///
/// Formula:
/// Cov(X,Y) = Σ((x - μx)(y - μy)) / n
/// where:
/// X, Y = variables
/// μx, μy = means of X and Y
/// n = number of observations
///
/// Market Applications:
/// - Portfolio risk analysis
/// - Pairs trading strategy development
/// - Asset relationship analysis
/// - Risk factor sensitivity analysis
/// - Multi-asset portfolio optimization
///
/// Sources:
/// https://en.wikipedia.org/wiki/Covariance
/// "Modern Portfolio Theory" - Harry Markowitz
///
/// Note: Scale-dependent nature means values should be interpreted in context of the data scales
/// </remarks>
[SkipLocalsInit]
public sealed class Covar : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _xValues;
private readonly CircularBuffer _yValues;
private const int MinimumPoints = 2;
/// <param name="period">The number of points to consider for covariance calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Covar(int period)
{
if (period < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for covariance calculation.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_xValues = new CircularBuffer(period);
_yValues = new CircularBuffer(period);
Name = $"Covar(period={period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of points to consider for covariance calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Covar(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();
_xValues.Clear();
_yValues.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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 CalculateCovariance(ReadOnlySpan<double> xValues, ReadOnlySpan<double> yValues, double xMean, double yMean)
{
double covariance = 0;
for (int i = 0; i < xValues.Length; i++)
{
covariance += (xValues[i] - xMean) * (yValues[i] - yMean);
}
return covariance / xValues.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
_xValues.Add(Input.Value, Input.IsNew);
_yValues.Add(Input2.Value, Input.IsNew);
double covariance = 0;
if (_xValues.Count >= MinimumPoints && _yValues.Count >= MinimumPoints)
{
ReadOnlySpan<double> xValues = _xValues.GetSpan();
ReadOnlySpan<double> yValues = _yValues.GetSpan();
double xMean = CalculateMean(xValues);
double yMean = CalculateMean(yValues);
covariance = CalculateCovariance(xValues, yValues, xMean, yMean);
}
IsHot = _xValues.Count >= Period && _yValues.Count >= Period;
return covariance;
}
}
-199
View File
@@ -1,199 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Curvature: Second Derivative Rate of Change
/// A statistical measure that calculates the rate of change of the slope over time.
/// Curvature provides insights into trend acceleration or deceleration by measuring
/// how quickly the slope (first derivative) is changing.
/// </summary>
/// <remarks>
/// The Curvature calculation process:
/// 1. Calculates slope values over the specified period
/// 2. Applies least squares regression to slope values
/// 3. Provides slope of slopes (curvature)
/// 4. Includes additional statistical measures (R², StdDev)
///
/// Key characteristics:
/// - Measures trend acceleration/deceleration
/// - Positive values indicate accelerating uptrends or decelerating downtrends
/// - Negative values indicate decelerating uptrends or accelerating downtrends
/// - Helps identify potential trend reversals
/// - Provides trend momentum information
///
/// Formula:
/// Curvature = Σ((x - x̄)(y - ȳ)) / Σ((x - x̄)²)
/// where:
/// x = time points
/// y = slope values
/// x̄, ȳ = respective means
///
/// Sources:
/// https://en.wikipedia.org/wiki/Curvature
/// https://www.sciencedirect.com/topics/mathematics/curve-fitting
///
/// Note: Second-order derivative providing acceleration insights
/// </remarks>
[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.
/// </summary>
public double? Intercept { get; private set; }
/// <summary>
/// Gets the standard deviation of the slope values used in the curvature calculation.
/// </summary>
public double? StdDev { get; private set; }
/// <summary>
/// Gets the R-squared value, indicating the goodness of fit of the curvature line.
/// </summary>
public double? RSquared { get; private set; }
/// <summary>
/// Gets the last calculated point on the curvature line.
/// </summary>
public double? Line { get; private set; }
/// <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)
{
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();
}
/// <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();
_slopeBuffer.Clear();
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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);
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);
ReadOnlySpan<double> slopes = _slopeBuffer.GetSpan();
// Calculate averages
var (sumX, sumY) = CalculateSums(slopes, count);
double avgX = sumX / count;
double avgY = sumY / count;
// Least squares method
var (sumSqX, sumSqY, sumSqXY) = CalculateSquaredSums(slopes, count, avgX, avgY);
if (sumSqX > Epsilon)
{
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;
double stdDevProduct = stdDevX * stdDevY;
if (stdDevProduct > Epsilon)
{
double r = sumSqXY / (stdDevProduct) / 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;
}
}
-145
View File
@@ -1,145 +0,0 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Entropy: Information Content Measure
/// A statistical measure that quantifies the unpredictability or randomness in
/// a time series using Shannon's Entropy. Higher entropy indicates more randomness
/// and uncertainty in the data.
/// </summary>
/// <remarks>
/// The Entropy calculation process:
/// 1. Groups values to calculate probabilities
/// 2. Applies Shannon's entropy formula
/// 3. Normalizes result to 0-1 range
/// 4. Adjusts for number of unique values
///
/// Key characteristics:
/// - Range from 0 (predictable) to 1 (random)
/// - Measures information content
/// - Detects regime changes
/// - Identifies market uncertainty
/// - Scale-independent measure
///
/// Formula:
/// H = -Σ(p(x) * log₂(p(x))) / log₂(n)
/// where:
/// p(x) = probability of value x
/// n = number of unique values
///
/// Applications:
/// - Detect market regime changes
/// - Assess price movement predictability
/// - Identify periods of high uncertainty
/// - Measure information flow in markets
///
/// Sources:
/// Claude Shannon - "A Mathematical Theory of Communication" (1948)
/// https://en.wikipedia.org/wiki/Entropy_(information_theory)
///
/// Note: Normalized to [0,1] for easier interpretation
/// </remarks>
[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 < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for entropy calculation.");
}
Period = period;
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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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);
if (_index <= 1) // Need at least two data points for entropy calculation
{
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;
}
}
-272
View File
@@ -1,272 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// GRANGER: Granger Causality Test
/// A statistical test to determine whether one time series is useful in forecasting another.
/// Tests if past values of X help predict future values of Y beyond Y's own past values.
/// Returns a value between 0 and 1 representing the probability that X does not Granger-cause Y.
/// </summary>
/// <remarks>
/// The Granger Causality calculation process:
/// 1. Fits two regression models:
/// - Restricted model: Y(t) = α₀ + Σ(β₁ᵢY(t-i)) + ε(t)
/// - Unrestricted model: Y(t) = α₀ + Σ(β₁ᵢY(t-i)) + Σ(β₂ᵢX(t-i)) + ε(t)
/// 2. Calculates F-statistic comparing the models
/// 3. Computes p-value from F-distribution
///
/// Key characteristics:
/// - Tests predictive causality, not true causation
/// - Sensitive to lag selection
/// - Assumes stationarity of time series
/// - Useful for lead/lag relationship analysis
///
/// Formula:
/// F = ((RSS₁ - RSS₂)/p) / (RSS₂/(n-2p-1))
/// where:
/// RSS₁ = residual sum of squares from restricted model
/// RSS₂ = residual sum of squares from unrestricted model
/// p = number of lags
/// n = number of observations
///
/// Market Applications:
/// - Lead/lag analysis between markets
/// - Price discovery analysis
/// - Market efficiency testing
/// - Intermarket analysis
/// - Risk spillover detection
///
/// Sources:
/// https://en.wikipedia.org/wiki/Granger_causality
/// "Investigating Causal Relations by Econometric Models and Cross-spectral Methods" - C.W.J. Granger
///
/// Note: Assumes linear relationships and stationarity
/// </remarks>
[SkipLocalsInit]
public sealed class Granger : AbstractBase
{
private readonly int Lags;
private readonly CircularBuffer _xValues;
private readonly CircularBuffer _yValues;
private const double Epsilon = 1e-10;
private const int MinimumLags = 1;
/// <param name="lags">The number of lags to use in the Granger causality test.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when lags is less than 1.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Granger(int lags)
{
if (lags < MinimumLags)
{
throw new ArgumentOutOfRangeException(nameof(lags),
"Number of lags must be at least 1 for Granger causality test.");
}
Lags = lags;
WarmupPeriod = lags + 1;
_xValues = new CircularBuffer(lags * 2); // Need extra space for lagged values
_yValues = new CircularBuffer(lags * 2);
Name = $"Granger(lags={lags})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="lags">The number of lags to use in the Granger causality test.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Granger(object source, int lags) : this(lags)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_xValues.Clear();
_yValues.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private double CalculateRSS(ReadOnlySpan<double> y, ReadOnlySpan<double> yhat)
{
double rss = 0;
for (int i = 0; i < y.Length; i++)
{
double residual = y[i] - yhat[i];
rss += residual * residual;
}
return rss;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void FitOLS(ReadOnlySpan<double> y, ReadOnlySpan<double> x, Span<double> beta)
{
// Simple OLS implementation for y = Xβ + ε
int n = y.Length;
int k = beta.Length;
// Create X matrix (including constant term)
var X = new double[n, k];
for (int i = 0; i < n; i++)
{
X[i, 0] = 1.0; // Constant term
for (int j = 1; j < k; j++)
{
X[i, j] = x[(i * (k - 1)) + (j - 1)];
}
}
// Calculate β = (X'X)⁻¹X'y
var XtX = new double[k, k];
var Xty = new double[k];
// Calculate X'X and X'y
for (int i = 0; i < k; i++)
{
for (int j = 0; j < k; j++)
{
double sum = 0;
for (int l = 0; l < n; l++)
{
sum += X[l, i] * X[l, j];
}
XtX[i, j] = sum;
}
double sum2 = 0;
for (int l = 0; l < n; l++)
{
sum2 += X[l, i] * y[l];
}
Xty[i] = sum2;
}
// Solve system of equations
for (int i = 0; i < k; i++)
{
double pivot = XtX[i, i];
if (Math.Abs(pivot) > Epsilon)
{
for (int j = 0; j < k; j++)
{
XtX[i, j] /= pivot;
}
Xty[i] /= pivot;
for (int j = 0; j < k; j++)
{
if (i != j)
{
double factor = XtX[j, i];
for (int l = 0; l < k; l++)
{
XtX[j, l] -= factor * XtX[i, l];
}
Xty[j] -= factor * Xty[i];
}
}
}
}
// Copy results to beta
for (int i = 0; i < k; i++)
{
beta[i] = Xty[i];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private double CalculateFStatistic(double rss1, double rss2, int n, int p)
{
// Calculate F-statistic
double numerator = (rss1 - rss2) / p;
double denominator = rss2 / (n - (2 * p) - 1);
return numerator / denominator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double FDistributionPValue(double f, int df1, int df2)
{
// Approximate p-value from F-distribution
// Using a simplified approximation for performance
double v = df2 / (df2 + (df1 * f));
return Math.Pow(v, df2 / 2.0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
_xValues.Add(Input.Value, Input.IsNew);
_yValues.Add(Input2.Value, Input.IsNew);
double pValue = 1.0; // Null hypothesis: X does not Granger-cause Y
if (_xValues.Count >= WarmupPeriod && _yValues.Count >= WarmupPeriod)
{
int n = _xValues.Count - Lags;
if (n > (2 * Lags) + 1)
{
ReadOnlySpan<double> x = _xValues.GetSpan();
ReadOnlySpan<double> y = _yValues.GetSpan();
// Prepare data for regression
var yData = y.Slice(Lags, n).ToArray();
var restricted = new double[Lags + 1];
var unrestricted = new double[(2 * Lags) + 1];
// Fit restricted model (only Y lags)
FitOLS(yData, y.Slice(0, n), restricted);
// Calculate RSS for restricted model
var yhatRestricted = new double[n];
for (int i = 0; i < n; i++)
{
yhatRestricted[i] = restricted[0];
for (int j = 0; j < Lags; j++)
{
yhatRestricted[i] += restricted[j + 1] * y[i + Lags - j - 1];
}
}
double rss1 = CalculateRSS(yData, yhatRestricted);
// Fit unrestricted model (Y and X lags)
FitOLS(yData, x.Slice(0, n), unrestricted);
// Calculate RSS for unrestricted model
var yhatUnrestricted = new double[n];
for (int i = 0; i < n; i++)
{
yhatUnrestricted[i] = unrestricted[0];
for (int j = 0; j < Lags; j++)
{
yhatUnrestricted[i] += unrestricted[j + 1] * y[i + Lags - j - 1];
yhatUnrestricted[i] += unrestricted[j + Lags + 1] * x[i + Lags - j - 1];
}
}
double rss2 = CalculateRSS(yData, yhatUnrestricted);
// Calculate F-statistic and p-value
if (rss2 > Epsilon)
{
double f = CalculateFStatistic(rss1, rss2, n, Lags);
pValue = FDistributionPValue(f, Lags, n - (2 * Lags) - 1);
}
}
}
IsHot = _xValues.Count >= WarmupPeriod && _yValues.Count >= WarmupPeriod;
return pValue;
}
}
-193
View File
@@ -1,193 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// HURST: Hurst Exponent
/// A measure of long-term memory of time series that relates to the
/// autocorrelations of the time series, and the rate at which these
/// decrease as the lag between pairs of values increases.
/// </summary>
/// <remarks>
/// The Hurst Exponent calculation process:
/// 1. Calculate log returns of the series
/// 2. Create subsequences of different lengths
/// 3. For each length:
/// - Calculate range (max-min) of cumulative deviations
/// - Calculate standard deviation
/// - Calculate R/S ratio
/// 4. Fit log(R/S) vs log(length) to find H
///
/// Key characteristics:
/// - H = 0.5: Random walk (Brownian motion)
/// - 0.5 < H ≤ 1.0: Trending (persistent) series
/// - 0 ≤ H < 0.5: Mean-reverting (anti-persistent) series
/// - Default minimum length is 10
/// - Default maximum length is period/2
///
/// Formula:
/// R(n)/S(n) = c * n^H
/// where:
/// R(n) = range of cumulative deviations
/// S(n) = standard deviation
/// n = subsequence length
/// H = Hurst exponent
///
/// Market Applications:
/// - Market efficiency analysis
/// - Trend strength measurement
/// - Trading strategy development
/// - Risk assessment
/// - Market regime identification
///
/// Sources:
/// H.E. Hurst (1951)
/// "Long-term Storage Capacity of Reservoirs"
/// Transactions of the American Society of Civil Engineers, 116, 770-799
///
/// Note: Returns a value between 0 and 1
/// </remarks>
[SkipLocalsInit]
public sealed class Hurst : AbstractBase
{
private readonly int _period;
private readonly int _minLength;
private readonly CircularBuffer _prices;
private readonly CircularBuffer _logReturns;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hurst(int period = 100, int minLength = 10)
{
if (minLength < 10)
{
throw new ArgumentOutOfRangeException(nameof(minLength), "Minimum length must be at least 10.");
}
if (period <= minLength * 2)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be at least twice the minimum length.");
}
_period = period;
_minLength = minLength;
WarmupPeriod = period + 1; // Need one extra period for returns
Name = $"HURST({_period})";
_prices = new CircularBuffer(period);
_logReturns = new CircularBuffer(period);
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hurst(object source, int period = 100, int minLength = 10) : this(period, minLength)
{
var pubEvent = source.GetType().GetEvent("Pub");
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Init()
{
base.Init();
_prices.Clear();
_logReturns.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static (double range, double stdDev) CalculateRangeAndStdDev(ReadOnlySpan<double> data)
{
int n = data.Length;
if (n == 0) return (0, 0);
// Calculate mean
double mean = 0;
for (int i = 0; i < n; i++)
{
mean += data[i];
}
mean /= n;
// Calculate cumulative deviations and std dev
double max = double.MinValue;
double min = double.MaxValue;
double sumSquaredDev = 0;
double cumDev = 0;
for (int i = 0; i < n; i++)
{
double dev = data[i] - mean;
cumDev += dev;
max = Math.Max(max, cumDev);
min = Math.Min(min, cumDev);
sumSquaredDev += dev * dev;
}
double range = max - min;
double stdDev = Math.Sqrt(sumSquaredDev / n);
return (range, stdDev);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(BarInput.IsNew);
// Add price and calculate log return
_prices.Add(BarInput.Close);
if (_index > 1)
{
double logReturn = Math.Log(BarInput.Close / _prices[1]);
_logReturns.Add(logReturn);
}
// Need enough values for calculation
if (_index <= _period)
{
return 0.5; // Return random walk value until we have enough data
}
// Calculate R/S values for different lengths
int maxLength = _period / 2;
int numPoints = 0;
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (int length = _minLength; length <= maxLength; length *= 2)
{
var (range, stdDev) = CalculateRangeAndStdDev(_logReturns.GetSpan()[..length]);
if (stdDev > 0)
{
double rs = range / stdDev;
if (rs > 0)
{
double x = Math.Log(length);
double y = Math.Log(rs);
sumX += x;
sumY += y;
sumXY += x * y;
sumX2 += x * x;
numPoints++;
}
}
}
// Calculate Hurst exponent using linear regression
double hurst = 0.5; // Default to random walk
if (numPoints > 1)
{
double slope = ((numPoints * sumXY) - (sumX * sumY)) / ((numPoints * sumX2) - (sumX * sumX));
hurst = Math.Max(0, Math.Min(1, slope)); // Clamp between 0 and 1
}
IsHot = _index >= WarmupPeriod;
return hurst;
}
}
-182
View File
@@ -1,182 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// KENDALL: Kendall's Rank Correlation Coefficient (Tau)
/// A nonparametric measure that evaluates the degree of similarity between two sets
/// of rankings by analyzing concordant and discordant pairs. Unlike Spearman correlation,
/// Kendall's tau measures the ordinal association between two variables.
/// </summary>
/// <remarks>
/// The Kendall calculation process:
/// 1. Compares each pair of observations
/// 2. Counts concordant and discordant pairs
/// 3. Handles ties in both variables
///
/// Key characteristics:
/// - Measures ordinal association
/// - Range: -1 to +1
/// - Robust to outliers
/// - More intuitive probabilistic interpretation
/// - Less sensitive to error than Spearman
///
/// Formula:
/// τ = (nc - nd) / sqrt((n0 - n1)(n0 - n2))
/// where:
/// nc = number of concordant pairs
/// nd = number of discordant pairs
/// n0 = n(n-1)/2
/// n1 = sum(u(u-1)/2) for ties in x
/// n2 = sum(v(v-1)/2) for ties in y
///
/// Market Applications:
/// - Rank correlation analysis
/// - Portfolio diversification
/// - Risk assessment
/// - Market trend analysis
/// - Pattern recognition
///
/// Sources:
/// https://en.wikipedia.org/wiki/Kendall_rank_correlation_coefficient
/// "Rank Correlation Methods" - Maurice G. Kendall
///
/// Note: More robust to outliers and errors than other correlation measures
/// </remarks>
[SkipLocalsInit]
public sealed class Kendall : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _xValues;
private readonly CircularBuffer _yValues;
private const double Epsilon = 1e-10;
private const int MinimumPoints = 2;
/// <param name="period">The number of points to consider for Kendall correlation calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Kendall(int period)
{
if (period < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for Kendall correlation calculation.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_xValues = new CircularBuffer(period);
_yValues = new CircularBuffer(period);
Name = $"Kendall(period={period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of points to consider for Kendall correlation calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Kendall(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();
_xValues.Clear();
_yValues.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static (int concordant, int discordant, int tiesX, int tiesY) CountPairs(ReadOnlySpan<double> x, ReadOnlySpan<double> y)
{
int n = x.Length;
int concordant = 0;
int discordant = 0;
int tiesX = 0;
int tiesY = 0;
for (int i = 0; i < n - 1; i++)
{
if (double.IsNaN(x[i]) || double.IsNaN(y[i])) continue;
for (int j = i + 1; j < n; j++)
{
if (double.IsNaN(x[j]) || double.IsNaN(y[j])) continue;
double xDiff = x[i] - x[j];
double yDiff = y[i] - y[j];
if (Math.Abs(xDiff) < Epsilon && Math.Abs(yDiff) < Epsilon)
{
tiesX++;
tiesY++;
}
else if (Math.Abs(xDiff) < Epsilon)
{
tiesX++;
}
else if (Math.Abs(yDiff) < Epsilon)
{
tiesY++;
}
else
{
int xSign = xDiff > 0 ? 1 : -1;
int ySign = yDiff > 0 ? 1 : -1;
if (xSign == ySign)
{
concordant++;
}
else
{
discordant++;
}
}
}
}
return (concordant, discordant, tiesX, tiesY);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
_xValues.Add(Input.Value, Input.IsNew);
_yValues.Add(Input2.Value, Input.IsNew);
double correlation = 0;
if (_xValues.Count >= MinimumPoints && _yValues.Count >= MinimumPoints)
{
ReadOnlySpan<double> xValues = _xValues.GetSpan();
ReadOnlySpan<double> yValues = _yValues.GetSpan();
var (concordant, discordant, tiesX, tiesY) = CountPairs(xValues, yValues);
int n = xValues.Length;
int n0 = (n * (n - 1)) / 2;
// Calculate denominator considering ties
double denominator = Math.Sqrt((n0 - tiesX) * (n0 - tiesY));
if (denominator > Epsilon)
{
correlation = (concordant - discordant) / denominator;
}
}
IsHot = _xValues.Count >= Period && _yValues.Count >= Period;
return correlation;
}
}
-154
View File
@@ -1,154 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Kurtosis: Distribution Tail Weight Measure
/// A statistical measure that quantifies the "tailedness" of a distribution using
/// the Sheskin Algorithm. Kurtosis indicates whether data has heavy tails (more
/// outliers) or light tails (fewer outliers) compared to a normal distribution.
/// </summary>
/// <remarks>
/// The Kurtosis calculation process:
/// 1. Calculates mean of the data
/// 2. Computes squared and fourth power deviations
/// 3. Applies Sheskin Algorithm for excess kurtosis
/// 4. Adjusts for sample size bias
///
/// Key characteristics:
/// - Measures tail weight relative to normal distribution
/// - Positive values indicate heavy tails
/// - Negative values indicate light tails
/// - Zero indicates normal distribution
/// - Sensitive to extreme values
///
/// Formula:
/// K = [n(n+1)Σ(x-μ)⁴] / [s⁴(n-1)(n-2)(n-3)] - [3(n-1)²]/[(n-2)(n-3)]
/// where:
/// n = sample size
/// μ = mean
/// s = standard deviation
///
/// Market Applications:
/// - Identify potential for extreme moves
/// - Assess risk of "black swan" events
/// - Compare return distributions
/// - Risk management tool
///
/// Sources:
/// David J. Sheskin - "Handbook of Parametric and Nonparametric Statistical Procedures"
/// https://en.wikipedia.org/wiki/Kurtosis
///
/// Note: Returns excess kurtosis (normal distribution = 0)
/// </remarks>
[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 < MinimumPoints)
{
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();
}
/// <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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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);
_buffer.Add(Input.Value, Input.IsNew);
double kurtosis = 0;
if (_buffer.Count > MinimumPoints - 1) // Need at least 4 points for valid calculation
{
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;
return kurtosis;
}
}
-160
View File
@@ -1,160 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// MAX: Maximum Value with Decay
/// A statistical measure that tracks the highest value over a specified period,
/// with an optional decay factor to gradually reduce the influence of older peaks.
/// This adaptive approach allows the indicator to respond to changing market conditions.
/// </summary>
/// <remarks>
/// The MAX calculation process:
/// 1. Tracks highest value in current period
/// 2. Applies exponential decay to old peaks
/// 3. Adjusts decay based on time since last peak
/// 4. Caps result at current period's maximum
///
/// Key characteristics:
/// - Tracks absolute highest values
/// - Optional decay for adaptivity
/// - Maintains historical context
/// - Smooth transitions with decay
/// - Period-based windowing
///
/// Formula:
/// decay = 1 - e^(-halfLife * timeSinceMax / period)
/// max = max - decay * (max - periodAverage)
/// max = min(max, periodMaximum)
///
/// Market Applications:
/// - Identify resistance levels
/// - Track price peaks
/// - Implement trailing stops
/// - Monitor price extremes
/// - Adaptive trend following
///
/// Sources:
/// Technical Analysis of Financial Markets
/// https://www.investopedia.com/terms/r/resistance.asp
///
/// Note: Decay factor allows for adaptive peak tracking
/// </remarks>
[SkipLocalsInit]
public sealed class Max : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _buffer;
private readonly double _halfLife;
private double _currentMax;
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Max(int period, double decay = DefaultDecay)
{
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 * DecayScaleFactor;
Name = $"Max(period={period}, halfLife={decay:F2})";
Init();
}
/// <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>
[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();
_currentMax = double.MinValue;
_timeSinceNewMax = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[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);
_buffer.Add(Input.Value, Input.IsNew);
// Update maximum if new value is higher
if (Input.Value >= _currentMax)
{
_currentMax = Input.Value;
_timeSinceNewMax = 0;
}
// Apply decay based on time since last maximum
double decayRate = CalculateDecayRate();
_currentMax -= decayRate * (_currentMax - _buffer.Average());
// Ensure maximum doesn't exceed current period's highest value
ReadOnlySpan<double> values = _buffer.GetSpan();
_currentMax = Math.Min(_currentMax, FindMaxValue(values));
IsHot = true;
return _currentMax;
}
}
-157
View File
@@ -1,157 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Median: Central Tendency Measure
/// A robust statistical measure that finds the middle value in a sorted dataset.
/// The median is less sensitive to outliers than the mean, making it particularly
/// useful for analyzing price data with extreme values.
/// </summary>
/// <remarks>
/// The Median calculation process:
/// 1. Collects values over specified period
/// 2. Sorts values in ascending order
/// 3. Finds middle value(s)
/// 4. Averages two middle values if even count
///
/// Key characteristics:
/// - Robust to outliers
/// - Always represents actual data point
/// - Splits dataset in half
/// - More stable than mean
/// - Maintains data scale
///
/// Formula:
/// For odd n: median = value at position (n+1)/2
/// For even n: median = (value at n/2 + value at (n/2)+1) / 2
///
/// Market Applications:
/// - Price distribution analysis
/// - Trend identification
/// - Outlier detection
/// - Support/resistance levels
/// - Filter extreme movements
///
/// Sources:
/// https://en.wikipedia.org/wiki/Median
/// "Statistics for Trading" - Technical Analysis of Financial Markets
///
/// Note: More robust than mean for non-normal distributions
/// </remarks>
[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)
{
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();
}
/// <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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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);
_buffer.Add(Input.Value, Input.IsNew);
double median;
if (_index >= Period)
{
// 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 = CalculateMedian(values);
}
else
{
// Not enough data, use average as temporary measure
median = _buffer.Average();
}
IsHot = _index >= WarmupPeriod;
return median;
}
}
-158
View File
@@ -1,158 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// MIN: Minimum Value with Decay
/// A statistical measure that tracks the lowest value over a specified period,
/// with an optional decay factor to gradually reduce the influence of older lows.
/// This adaptive approach allows the indicator to respond to changing market conditions.
/// </summary>
/// <remarks>
/// The MIN calculation process:
/// 1. Tracks lowest value in current period
/// 2. Applies exponential decay to old lows
/// 3. Adjusts decay based on time since last low
/// 4. Caps result at current period's minimum
///
/// Key characteristics:
/// - Tracks absolute lowest values
/// - Optional decay for adaptivity
/// - Maintains historical context
/// - Smooth transitions with decay
/// - Period-based windowing
///
/// Formula:
/// decay = 1 - e^(-halfLife * timeSinceMin / period)
/// min = min + decay * (periodAverage - min)
/// min = max(min, periodMinimum)
///
/// Market Applications:
/// - Identify support levels
/// - Track price troughs
/// - Implement trailing stops
/// - Monitor price extremes
/// - Adaptive trend following
///
/// Sources:
/// Technical Analysis of Financial Markets
/// https://www.investopedia.com/terms/s/support.asp
///
/// Note: Decay factor allows for adaptive low tracking
/// </remarks>
[SkipLocalsInit]
public sealed class Min : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _buffer;
private readonly double _halfLife;
private double _currentMin;
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Min(int period, double decay = DefaultDecay)
{
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 * DecayScaleFactor;
Name = $"Min(period={period}, halfLife={decay:F2})";
Init();
}
/// <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>
[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();
_currentMin = double.MaxValue;
_timeSinceNewMin = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
[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);
_buffer.Add(Input.Value, Input.IsNew);
// Update minimum if new value is lower
if (Input.Value <= _currentMin)
{
_currentMin = Input.Value;
_timeSinceNewMin = 0;
}
// Apply decay based on time since last minimum
double decayRate = CalculateDecayRate();
_currentMin += decayRate * (_buffer.Average() - _currentMin);
// Ensure minimum doesn't fall below current period's lowest value
ReadOnlySpan<double> values = _buffer.GetSpan();
_currentMin = Math.Max(_currentMin, FindMinValue(values));
IsHot = true;
return _currentMin;
}
}
-162
View File
@@ -1,162 +0,0 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// MODE: Most Frequent Value Measure
/// A statistical measure that identifies the most frequently occurring value(s)
/// in a dataset. When multiple values share the highest frequency, it returns
/// their average to provide a representative central value.
/// </summary>
/// <remarks>
/// The Mode calculation process:
/// 1. Groups values by frequency
/// 2. Identifies highest frequency group(s)
/// 3. Averages multiple modes if present
/// 4. Uses mean until period filled
///
/// Key characteristics:
/// - Identifies most common values
/// - Handles multiple modes
/// - Robust to distribution shape
/// - Useful for discrete data
/// - Returns actual data points
///
/// Formula:
/// mode = value with highest frequency count
/// if multiple modes: average of mode values
///
/// Market Applications:
/// - Identify common price levels
/// - Detect support/resistance zones
/// - Analyze volume clusters
/// - Find price congestion areas
/// - Pattern recognition
///
/// Sources:
/// https://en.wikipedia.org/wiki/Mode_(statistics)
/// "Statistical Analysis in Financial Markets"
///
/// Note: Particularly useful for price level analysis
/// </remarks>
[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)
{
throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
}
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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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);
_buffer.Add(Input.Value, Input.IsNew);
double mode;
if (_index >= Period)
{
ReadOnlySpan<double> values = _buffer.GetSpan();
CountFrequencies(values);
FindModes();
mode = CalculateAverageMode();
}
else
{
// Use average until we have enough data points
mode = _buffer.Average();
}
IsHot = _index >= WarmupPeriod;
return mode;
}
}
-171
View File
@@ -1,171 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Percentile: Distribution Position Measure
/// A statistical measure that indicates the value below which a given percentage
/// of observations falls. Percentiles provide insights into data distribution
/// and are particularly useful for risk assessment and outlier detection.
/// </summary>
/// <remarks>
/// The Percentile calculation process:
/// 1. Sorts values in ascending order
/// 2. Calculates position based on percentile
/// 3. Interpolates between adjacent values
/// 4. Uses mean until period filled
///
/// Key characteristics:
/// - Range specific value identification
/// - Linear interpolation for precision
/// - Distribution independent
/// - Robust to outliers
/// - Useful for risk metrics
///
/// Formula:
/// position = (percentile/100) * (n-1)
/// value = v[floor(pos)] + (v[ceil(pos)] - v[floor(pos)]) * (pos - floor(pos))
/// where n = number of observations, v = sorted values
///
/// Market Applications:
/// - Value at Risk (VaR) calculation
/// - Risk management metrics
/// - Performance analysis
/// - Volatility assessment
/// - Outlier detection
///
/// Sources:
/// https://en.wikipedia.org/wiki/Percentile
/// "Risk Management in Trading" - Davis Edwards
///
/// Note: Particularly useful for risk metrics like VaR
/// </remarks>
[SkipLocalsInit]
public sealed class Percentile : AbstractBase
{
private readonly int Period;
private readonly double Percent;
private readonly CircularBuffer _buffer;
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)
{
ArgumentOutOfRangeException.ThrowIfLessThan(period, MinimumPoints);
ArgumentOutOfRangeException.ThrowIfLessThan(percent, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThan(percent, 100);
Period = period;
Percent = percent;
WarmupPeriod = MinimumPoints; // Minimum number of points needed for percentile calculation
_buffer = new CircularBuffer(period);
Name = $"Percentile(period={period}, percent={percent})";
Init();
}
/// <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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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);
_buffer.Add(Input.Value, Input.IsNew);
double result;
if (_buffer.Count >= Period)
{
// 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);
result = CalculatePercentile(values);
}
else
{
// Use average until we have enough data points
result = _buffer.Average();
}
IsHot = _buffer.Count >= Period;
return result;
}
}
-153
View File
@@ -1,153 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// SKEW: Distribution Asymmetry Measure
/// A statistical measure that quantifies the asymmetry of a probability distribution
/// around its mean. Skewness indicates whether deviations from the mean are more
/// likely in one direction than the other.
/// </summary>
/// <remarks>
/// The Skew calculation process:
/// 1. Calculates mean of the data
/// 2. Computes deviations from mean
/// 3. Calculates third moment (cubed deviations)
/// 4. Normalizes by standard deviation cubed
///
/// Key characteristics:
/// - Measures distribution asymmetry
/// - Positive values indicate right skew
/// - Negative values indicate left skew
/// - Zero indicates symmetry
/// - Scale-independent measure
///
/// Formula:
/// skew = [√(n(n-1))/(n-2)] * [m₃/s³]
/// where:
/// m₃ = third moment about the mean
/// s = standard deviation
/// n = sample size
///
/// Market Applications:
/// - Risk assessment in returns
/// - Options pricing models
/// - Trading strategy development
/// - Portfolio risk management
/// - Market sentiment analysis
///
/// Sources:
/// Fisher-Pearson standardized moment coefficient
/// https://en.wikipedia.org/wiki/Skewness
/// "The Analysis of Financial Time Series" - Ruey S. Tsay
///
/// Note: Requires minimum of 3 data points for calculation
/// </remarks>
[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 < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 3 for skewness calculation.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_buffer = new CircularBuffer(period);
Name = $"Skew(period={period})";
Init();
}
/// <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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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 >= MinimumPoints) // Need at least 3 points for skewness
{
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;
return skew;
}
}
-199
View File
@@ -1,199 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// SLOPE: Linear Regression Trend Measure
/// A statistical measure that calculates the rate of change using linear regression.
/// Slope indicates the direction and steepness of a trend, providing insights into
/// momentum and potential trend changes.
/// </summary>
/// <remarks>
/// The Slope calculation process:
/// 1. Calculates means of x and y values
/// 2. Computes deviations from means
/// 3. Applies least squares method
/// 4. Provides additional regression statistics
///
/// Key characteristics:
/// - Measures trend direction and strength
/// - Provides rate of change
/// - Scale-dependent measure
/// - Includes regression statistics
/// - Time-weighted calculation
///
/// Formula:
/// slope = Σ((x - x̄)(y - ȳ)) / Σ((x - x̄)²)
/// where:
/// x = time points
/// y = price values
/// x̄, ȳ = respective means
///
/// Market Applications:
/// - Trend identification
/// - Momentum measurement
/// - Support/resistance angles
/// - Price target projection
/// - Trend strength analysis
///
/// Sources:
/// https://en.wikipedia.org/wiki/Simple_linear_regression
/// "Technical Analysis of Financial Markets" - John J. Murphy
///
/// Note: Provides additional regression statistics (R², intercept)
/// </remarks>
[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; }
/// <summary>Gets the standard deviation of the y-values.</summary>
public double? StdDev { get; private set; }
/// <summary>Gets the R-squared value, indicating regression fit quality.</summary>
public double? RSquared { get; private set; }
/// <summary>Gets the last point on the regression line.</summary>
public double? Line { get; private set; }
/// <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)
{
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})";
Init();
}
/// <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();
_buffer.Clear();
_timeBuffer.Clear();
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static (double sumX, double sumY) CalculateSums(ReadOnlySpan<double> values, int count)
{
double sumX = 0, sumY = 0;
for (int i = 0; i < count; i++)
{
sumX += i + 1;
sumY += values[i];
}
return (sumX, sumY);
}
[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++)
{
double devX = (i + 1) - avgX;
double devY = values[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);
_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;
Intercept = avgY - (slope * avgX);
// Calculate Standard Deviation and R-Squared
double stdDevX = Math.Sqrt(sumSqX / count);
double stdDevY = Math.Sqrt(sumSqY / count);
StdDev = stdDevY;
double stdDevProduct = stdDevX * stdDevY;
if (stdDevProduct > Epsilon)
{
double r = sumSqXY / stdDevProduct / count;
RSquared = r * r;
}
// Calculate regression line endpoint
Line = (slope * count) + Intercept;
}
else
{
Intercept = null;
StdDev = null;
RSquared = null;
Line = null;
}
IsHot = _buffer.Count == _period;
return slope;
}
}
-224
View File
@@ -1,224 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// SPEARMAN: Spearman's Rank Correlation Coefficient
/// A nonparametric measure of rank correlation that assesses the monotonic relationship
/// between two variables. Unlike Pearson correlation, Spearman correlation evaluates
/// the relationship based on ranked values rather than raw data.
/// </summary>
/// <remarks>
/// The Spearman calculation process:
/// 1. Ranks both sets of values
/// 2. Calculates correlation between ranks
/// 3. Handles ties by averaging ranks
///
/// Key characteristics:
/// - Resistant to outliers
/// - Detects monotonic relationships
/// - Range: -1 to +1
/// - Distribution-free measure
/// - Handles non-linear relationships
///
/// Formula:
/// ρ = Cov(rank(X), rank(Y)) / (σrank(X) * σrank(Y))
/// where:
/// X, Y = variables
/// rank() = ranking function
/// Cov = covariance
/// σ = standard deviation
///
/// Market Applications:
/// - Technical analysis
/// - Risk assessment
/// - Market correlation studies
/// - Trend analysis
/// - Pattern recognition
///
/// Sources:
/// https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient
/// "Nonparametric Statistics for Non-Statisticians" - Gregory W. Corder
///
/// Note: More robust to outliers than Pearson correlation
/// </remarks>
[SkipLocalsInit]
public sealed class Spearman : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _xValues;
private readonly CircularBuffer _yValues;
private readonly CircularBuffer _xRanks;
private readonly CircularBuffer _yRanks;
private const double Epsilon = 1e-10;
private const int MinimumPoints = 2;
/// <param name="period">The number of points to consider for Spearman correlation calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Spearman(int period)
{
if (period < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for Spearman correlation calculation.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_xValues = new CircularBuffer(period);
_yValues = new CircularBuffer(period);
_xRanks = new CircularBuffer(period);
_yRanks = new CircularBuffer(period);
Name = $"Spearman(period={period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of points to consider for Spearman correlation calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Spearman(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();
_xValues.Clear();
_yValues.Clear();
_xRanks.Clear();
_yRanks.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double[] CalculateRanks(ReadOnlySpan<double> values)
{
int n = values.Length;
var pairs = new (double value, int index)[n];
for (int i = 0; i < n; i++)
{
pairs[i] = double.IsNaN(values[i]) ? (double.NaN, i) : (values[i], i);
}
// Sort non-NaN values
var validPairs = pairs.Where(p => !double.IsNaN(p.value)).OrderBy(p => p.value).ToArray();
var ranks = new double[n];
Array.Fill(ranks, double.NaN);
for (int i = 0; i < validPairs.Length;)
{
int j = i;
// Find ties
while (j < validPairs.Length - 1 && Math.Abs(validPairs[j].value - validPairs[j + 1].value) < Epsilon)
{
j++;
}
// Average rank for ties
double rank = ((i + j) / 2.0) + 1;
for (int k = i; k <= j; k++)
{
ranks[validPairs[k].index] = rank;
}
i = j + 1;
}
return ranks;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateCovariance(CircularBuffer xBuffer, CircularBuffer yBuffer, double xMean, double yMean)
{
var xSpan = xBuffer.GetSpan();
var ySpan = yBuffer.GetSpan();
double covariance = 0;
int count = 0;
for (int i = 0; i < xSpan.Length; i++)
{
if (!double.IsNaN(xSpan[i]) && !double.IsNaN(ySpan[i]))
{
covariance += (xSpan[i] - xMean) * (ySpan[i] - yMean);
count++;
}
}
return count > 0 ? covariance / count : double.NaN;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateStandardDeviation(CircularBuffer buffer, double mean)
{
var span = buffer.GetSpan();
double sumSquaredDeviations = 0;
int count = 0;
for (int i = 0; i < span.Length; i++)
{
if (!double.IsNaN(span[i]))
{
double deviation = span[i] - mean;
sumSquaredDeviations += deviation * deviation;
count++;
}
}
return count > 0 ? Math.Sqrt(sumSquaredDeviations / count) : double.NaN;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
_xValues.Add(Input.Value, Input.IsNew);
_yValues.Add(Input2.Value, Input.IsNew);
double correlation = 0;
if (_xValues.Count >= MinimumPoints && _yValues.Count >= MinimumPoints)
{
// Convert values to ranks
var xRanks = CalculateRanks(_xValues.GetSpan());
var yRanks = CalculateRanks(_yValues.GetSpan());
// Store ranks in buffers for statistical calculations
_xRanks.Clear();
_yRanks.Clear();
for (int i = 0; i < xRanks.Length; i++)
{
_xRanks.Add(xRanks[i], true);
_yRanks.Add(yRanks[i], true);
}
// Use CircularBuffer's optimized Average() method
double xMean = _xRanks.Average();
double yMean = _yRanks.Average();
if (!double.IsNaN(xMean) && !double.IsNaN(yMean))
{
double covariance = CalculateCovariance(_xRanks, _yRanks, xMean, yMean);
double xStdDev = CalculateStandardDeviation(_xRanks, xMean);
double yStdDev = CalculateStandardDeviation(_yRanks, yMean);
if (!double.IsNaN(covariance) && xStdDev > Epsilon && yStdDev > Epsilon)
{
correlation = covariance / (xStdDev * yStdDev);
}
}
}
IsHot = _xValues.Count >= Period && _yValues.Count >= Period;
return correlation;
}
}
-143
View File
@@ -1,143 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// STDDEV: Standard Deviation Volatility Measure
/// A statistical measure that quantifies the amount of variation or dispersion
/// in a dataset. Standard deviation is widely used in finance as a measure of
/// volatility and risk assessment.
/// </summary>
/// <remarks>
/// The StdDev calculation process:
/// 1. Calculates mean of the data
/// 2. Computes squared deviations from mean
/// 3. Averages squared deviations
/// 4. Takes square root of average
///
/// Key characteristics:
/// - Measures data dispersion
/// - Same units as input data
/// - Sensitive to outliers
/// - Population or sample versions
/// - Key volatility indicator
///
/// Formula:
/// Population: σ = √(Σ(x - μ)² / N)
/// Sample: s = √(Σ(x - x̄)² / (n-1))
/// where:
/// x = values
/// μ, x̄ = mean
/// N, n = count
///
/// Market Applications:
/// - Volatility measurement
/// - Risk assessment
/// - Bollinger Bands
/// - Option pricing
/// - Portfolio management
///
/// Sources:
/// https://en.wikipedia.org/wiki/Standard_deviation
/// "Options, Futures, and Other Derivatives" - John C. Hull
///
/// Note: Foundation for many volatility-based indicators
/// </remarks>
[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 < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2.");
}
IsPopulation = isPopulation;
WarmupPeriod = 0;
_buffer = new CircularBuffer(period);
Name = $"Stddev(period={period}, population={isPopulation})";
Init();
}
/// <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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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);
_buffer.Add(Input.Value, Input.IsNew);
double stddev = 0;
if (_buffer.Count > 1)
{
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;
double variance = sumOfSquaredDifferences / divisor;
stddev = Math.Sqrt(variance);
}
IsHot = true; // StdDev calc is valid from bar 1
return stddev;
}
}
-167
View File
@@ -1,167 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// THEIL: Theil's U Statistics (U1, U2)
/// A statistical measure that quantifies the accuracy of forecasts compared to actual values
/// and naive forecasts.
/// </summary>
/// <remarks>
/// The Theil's U calculation process:
/// 1. Calculate U1 statistic (relative accuracy)
/// 2. Calculate U2 statistic (comparison with naive forecast)
///
/// Key characteristics:
/// - U1 ranges from 0 to 1, with 0 indicating perfect forecast
/// - U2 &lt; 1: forecast better than naive forecast
/// - U2 = 1: forecast equal to naive forecast
/// - U2 &gt; 1: forecast worse than naive forecast
///
/// Formula:
/// U1 = √[Σ(Ft - At)² / Σ(At)²]
/// U2 = √[Σ(Ft - At)² / Σ(At - At-1)²]
/// where:
/// Ft = forecasted value
/// At = actual value
/// At-1 = previous actual value
///
/// Market Applications:
/// - Evaluating forecast accuracy
/// - Comparing forecasting models
/// - Assessing forecasting methods
/// - Model selection
/// - Performance analysis
///
/// Sources:
/// https://en.wikipedia.org/wiki/Theil%27s_U
/// "Forecasting: Principles and Practice" - Rob J Hyndman
///
/// Note: Should be used alongside other accuracy measures
/// </remarks>
[SkipLocalsInit]
public sealed class Theil : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _actual;
private readonly CircularBuffer _forecast;
private const int MinimumPoints = 2;
/// <summary>
/// Gets the U2 statistic comparing forecast with naive forecast
/// </summary>
public double U2 { get; private set; }
/// <param name="period">The number of points to consider for Theil's U calculation.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Theil(int period)
{
if (period < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for Theil's U calculation.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_actual = new CircularBuffer(period);
_forecast = new CircularBuffer(period);
Name = $"Theil(period={period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of points to consider for Theil's U calculation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Theil(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();
_actual.Clear();
_forecast.Clear();
U2 = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateSquaredSum(ReadOnlySpan<double> values)
{
double sum = 0;
for (int i = 0; i < values.Length; i++)
{
sum += values[i] * values[i];
}
return sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateSquaredErrorSum(ReadOnlySpan<double> forecast, ReadOnlySpan<double> actual)
{
double sum = 0;
for (int i = 0; i < forecast.Length; i++)
{
double error = forecast[i] - actual[i];
sum += error * error;
}
return sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateNaiveSquaredErrorSum(ReadOnlySpan<double> actual)
{
double sum = 0;
for (int i = 1; i < actual.Length; i++)
{
double error = actual[i] - actual[i - 1];
sum += error * error;
}
return sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
_actual.Add(Input.Value, Input.IsNew);
_forecast.Add(Input2.Value, Input.IsNew);
double u1 = 0;
if (_actual.Count >= MinimumPoints && _forecast.Count >= MinimumPoints)
{
ReadOnlySpan<double> actualValues = _actual.GetSpan();
ReadOnlySpan<double> forecastValues = _forecast.GetSpan();
double squaredErrorSum = CalculateSquaredErrorSum(forecastValues, actualValues);
double squaredActualSum = CalculateSquaredSum(actualValues);
double naiveSquaredErrorSum = CalculateNaiveSquaredErrorSum(actualValues);
if (squaredActualSum > double.Epsilon)
{
u1 = Math.Sqrt(squaredErrorSum / squaredActualSum);
}
if (naiveSquaredErrorSum > double.Epsilon)
{
U2 = Math.Sqrt(squaredErrorSum / naiveSquaredErrorSum);
}
}
IsHot = _actual.Count >= Period && _forecast.Count >= Period;
return u1;
}
}
-185
View File
@@ -1,185 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// TSF: Time Series Forecast
/// A statistical indicator that provides a linear regression forecast of future values
/// based on historical data. It includes both the forecast value and a confidence interval.
/// </summary>
/// <remarks>
/// The Time Series Forecast calculation process:
/// 1. Calculates linear regression on the input data
/// 2. Extrapolates the regression line to forecast future values
/// 3. Computes confidence intervals based on the standard error of the forecast
///
/// Key characteristics:
/// - Provides point forecast and confidence interval
/// - Based on linear regression principles
/// - Assumes trend continuity
/// - Sensitive to recent data changes
/// - Useful for short-term predictions
///
/// Formula:
/// Forecast = a + b * (n + 1)
/// where:
/// a = y-intercept
/// b = slope
/// n = number of periods
///
/// Confidence Interval = Forecast ± (t * SE)
/// where:
/// t = t-value for desired confidence level
/// SE = Standard Error of the forecast
///
/// Market Applications:
/// - Price target estimation
/// - Trend analysis
/// - Risk assessment
/// - Trading strategy development
/// - Market behavior prediction
///
/// Sources:
/// https://en.wikipedia.org/wiki/Time_series
/// "Forecasting: Principles and Practice" - Rob J Hyndman and George Athanasopoulos
///
/// Note: Assumes linear trend in the data and may not capture non-linear patterns
/// </remarks>
[SkipLocalsInit]
public sealed class Tsf : AbstractBase
{
private readonly int Period;
private readonly CircularBuffer _values;
private const int MinimumPoints = 2;
/// <summary>
/// The forecasted value for the next period.
/// </summary>
public double Forecast { get; private set; }
/// <summary>
/// The lower bound of the confidence interval.
/// </summary>
public double LowerBound { get; private set; }
/// <summary>
/// The upper bound of the confidence interval.
/// </summary>
public double UpperBound { get; private set; }
/// <param name="period">The number of historical data points to consider for forecasting.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 2.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Tsf(int period)
{
if (period < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for time series forecasting.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_values = new CircularBuffer(period);
Name = $"TSF(period={period})";
Init();
}
/// <param name="source">The data source object that publishes updates.</param>
/// <param name="period">The number of historical data points to consider for forecasting.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Tsf(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();
_values.Clear();
Forecast = 0;
LowerBound = 0;
UpperBound = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void ManageState(bool isNew)
{
if (isNew)
{
_lastValidValue = Input.Value;
_index++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static (double slope, double intercept) CalculateLinearRegression(ReadOnlySpan<double> values)
{
int n = values.Length;
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (int i = 0; i < n; i++)
{
double x = i + 1;
double y = values[i];
sumX += x;
sumY += y;
sumXY += x * y;
sumX2 += x * x;
}
double slope = ((n * sumXY) - (sumX * sumY)) / ((n * sumX2) - (sumX * sumX));
double intercept = (sumY - (slope * sumX)) / n;
return (slope, intercept);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double CalculateStandardError(ReadOnlySpan<double> values, double slope, double intercept)
{
int n = values.Length;
double sumSquaredResiduals = 0;
for (int i = 0; i < n; i++)
{
double x = i + 1;
double y = values[i];
double predicted = (slope * x) + intercept;
double residual = y - predicted;
sumSquaredResiduals += residual * residual;
}
return Math.Sqrt(sumSquaredResiduals / (n - 2));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override double Calculation()
{
ManageState(Input.IsNew);
_values.Add(Input.Value, Input.IsNew);
if (_values.Count >= MinimumPoints)
{
ReadOnlySpan<double> values = _values.GetSpan();
var (slope, intercept) = CalculateLinearRegression(values);
// Calculate forecast for the next period
Forecast = (slope * (Period + 1)) + intercept;
// Calculate standard error
double standardError = CalculateStandardError(values, slope, intercept);
// Calculate confidence interval (using t-distribution with n-2 degrees of freedom)
double tValue = 1.96; // Approximation for 95% confidence interval
double marginOfError = tValue * standardError * Math.Sqrt(1 + (1.0 / Period));
LowerBound = Forecast - marginOfError;
UpperBound = Forecast + marginOfError;
}
IsHot = _values.Count >= Period;
return Forecast;
}
}
-142
View File
@@ -1,142 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// VARIANCE: Squared Deviation Risk Measure
/// A statistical measure that quantifies the spread of data points around their
/// mean value. Variance is fundamental to risk assessment and portfolio theory,
/// providing the basis for many financial models.
/// </summary>
/// <remarks>
/// The Variance calculation process:
/// 1. Calculates mean of the data
/// 2. Computes squared deviations from mean
/// 3. Sums squared deviations
/// 4. Divides by n or (n-1)
///
/// Key characteristics:
/// - Measures data dispersion
/// - Squared units of input data
/// - Always non-negative
/// - Population or sample versions
/// - Foundation for risk metrics
///
/// Formula:
/// Population: σ² = Σ(x - μ)² / N
/// Sample: s² = Σ(x - x̄)² / (n-1)
/// where:
/// x = values
/// μ, x̄ = mean
/// N, n = count
///
/// Market Applications:
/// - Portfolio optimization
/// - Risk measurement
/// - Modern Portfolio Theory
/// - Asset allocation
/// - Volatility analysis
///
/// Sources:
/// Harry Markowitz - "Portfolio Selection" (1952)
/// https://en.wikipedia.org/wiki/Variance
///
/// Note: Basis for Modern Portfolio Theory and risk models
/// </remarks>
[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 < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2.");
}
IsPopulation = isPopulation;
WarmupPeriod = 0;
_buffer = new CircularBuffer(period);
Name = $"Variance(period={period}, population={isPopulation})";
Init();
}
/// <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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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);
_buffer.Add(Input.Value, Input.IsNew);
double variance = 0;
if (_buffer.Count > 1)
{
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;
variance = sumOfSquaredDifferences / divisor;
}
IsHot = true;
return variance;
}
}
-140
View File
@@ -1,140 +0,0 @@
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// ZSCORE: Standardized Distance Measure
/// A statistical measure that indicates how many standard deviations an observation
/// is from the mean. Z-scores normalize data to a standard scale, making it useful
/// for comparing values across different distributions.
/// </summary>
/// <remarks>
/// The Zscore calculation process:
/// 1. Calculates mean of the period
/// 2. Computes standard deviation
/// 3. Measures distance from mean
/// 4. Normalizes by standard deviation
///
/// Key characteristics:
/// - Scale-independent measure
/// - Symmetric around zero
/// - Normal distribution context
/// - Outlier identification
/// - Comparative analysis tool
///
/// Formula:
/// Z = (x - μ) / σ
/// where:
/// x = current value
/// μ = mean
/// σ = standard deviation
///
/// Market Applications:
/// - Mean reversion strategies
/// - Overbought/oversold signals
/// - Volatility breakouts
/// - Cross-asset comparison
/// - Statistical arbitrage
///
/// Sources:
/// https://en.wikipedia.org/wiki/Standard_score
/// "Statistical Analysis in Trading" - Technical Analysis
///
/// Note: Assumes approximately normal distribution
/// </remarks>
[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 < MinimumPoints)
{
throw new ArgumentOutOfRangeException(nameof(period),
"Period must be greater than or equal to 2 for Z-score calculation.");
}
Period = period;
WarmupPeriod = MinimumPoints;
_buffer = new CircularBuffer(period);
Name = $"ZScore(period={period})";
Init();
}
/// <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)
{
_lastValidValue = Input.Value;
_index++;
}
}
[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 >= MinimumPoints) // Need at least 2 points for standard deviation
{
ReadOnlySpan<double> values = _buffer.GetSpan();
double mean = CalculateMean(values);
double standardDeviation = CalculateStandardDeviation(values, mean);
if (standardDeviation > Epsilon) // Avoid division by zero
{
zScore = (Input.Value - mean) / standardDeviation;
}
}
IsHot = _buffer.Count >= Period;
return zScore;
}
}
-26
View File
@@ -1,26 +0,0 @@
# Statistics indicators
Done: 22, Todo: 1
✔️ BETA - Beta coefficient measuring volatility relative to market
✔️ CORR - Correlation coefficient between two series
✔️ COVAR - Covariance between two series
✔️ CURVATURE - Curvature of a time series
✔️ ENTROPY - Information entropy of a series
✔️ GRANGER - Granger causality test
✔️ HURST - Hurst exponent for trend strength
✔️ KENDALL - Kendall rank correlation
✔️ KURTOSIS - Kurtosis measuring tail extremity
✔️ MAX - Maximum value over period
✔️ MEDIAN - Median value over period
✔️ MIN - Minimum value over period
✔️ MODE - Mode (most frequent value)
✔️ PERCENTILE - Percentile rank calculation
✔️ SKEW - Skewness measuring distribution asymmetry
✔️ SLOPE - Linear regression slope
✔️ SPEARMAN - Spearman rank correlation
✔️ STDDEV - Standard deviation
✔️ THEIL - Theil's U statistics for forecast accuracy
✔️ TSF - Time series forecast
✔️ VARIANCE - Statistical variance
✔️ ZSCORE - Z-score standardization
COINTEGRATION - Test for cointegrated series