mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-15 17:18:05 +00:00
first iteration
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 < 1: forecast better than naive forecast
|
||||
/// - U2 = 1: forecast equal to naive forecast
|
||||
/// - U2 > 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user