diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs
index 5406876e..b0689b4d 100644
--- a/Tests/test_eventing.cs
+++ b/Tests/test_eventing.cs
@@ -76,6 +76,8 @@ public class EventingTests
("Stddev", new Stddev(p), new Stddev(input, p)),
("Variance", new Variance(p), new Variance(input, p)),
("Zscore", new Zscore(p), new Zscore(input, p)),
+ ("Beta", new Beta(p), new Beta(input, p)),
+ ("Corr", new Corr(p), new Corr(input, p)),
// Volatility indicators (value-based)
("Hv", new Hv(p), new Hv(input, p)),
("Jvolty", new Jvolty(p), new Jvolty(input, p)),
diff --git a/Tests/test_updates_statistics.cs b/Tests/test_updates_statistics.cs
index e1c902aa..ff6ed756 100644
--- a/Tests/test_updates_statistics.cs
+++ b/Tests/test_updates_statistics.cs
@@ -26,6 +26,39 @@ public class StatisticsUpdateTests
return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew);
}
+ [Fact]
+ public void Beta_Update()
+ {
+ var indicator = new Beta(period: 14);
+ TBar marketBar = GetRandomBar(true);
+ TBar assetBar = GetRandomBar(true);
+ double initialValue = indicator.Calc(marketBar, assetBar);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(false), GetRandomBar(false));
+ }
+ double finalValue = indicator.Calc(new TBar(marketBar.Time, marketBar.Open, marketBar.High, marketBar.Low, marketBar.Close, marketBar.Volume, false),
+ new TBar(assetBar.Time, assetBar.Open, assetBar.High, assetBar.Low, assetBar.Close, assetBar.Volume, false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Corr_Update()
+ {
+ var indicator = new Corr(period: 14);
+ double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true), new TValue(DateTime.Now, ReferenceValue, IsNew: true));
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false), new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false), new TValue(DateTime.Now, ReferenceValue, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Curvature_Update()
{
diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md
index a88d07f9..4fdc43aa 100644
--- a/docs/indicators/indicators.md
+++ b/docs/indicators/indicators.md
@@ -6,11 +6,12 @@
| Averages & Trends | 33 of 33 | 100% |
| Momentum | 16 of 16 | 100% |
| Oscillators | 22 of 29 | 76% |
-| Volatility | 24 of 35 | 69% |
-| Volume | 15 of 19 | 79% |
-| Numerical Analysis | 13 of 19 | 68% |
+| Volatility | 29 of 35 | 83% |
+| Volume | 19 of 19 | 100% |
+| Numerical Analysis | 15 of 19 | 79% |
| Errors | 16 of 16 | 100% |
-| **Total** | **145 of 173** | **84%** |
+| Patterns | 0 of 8 | 0% |
+| **Total** | **156 of 181** | **86%** |
|Technical Indicator Name| Class Name|
|-----------|:----------:|
@@ -85,9 +86,10 @@
|COPPOCK - Coppock Curve|`Coppock`|
|CRSI - Connor RSI|`Crsi`|
|🚧 CTI - Ehler's Correlation Trend Indicator|`Cti`|
+|DOSC - Derivative Oscillator|`Dosc`|
+|EFI - Elder Ray's Force Index|`Efi`|
|🚧 FISHER - Fisher Transform|`Fisher`|
|🚧 FOSC - Forecast Oscillator|`Fosc`|
-|EFI - Elder Ray's Force Index|`Efi`|
|🚧 GATOR* - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)|`Gator`|
|🚧 KDJ* - KDJ Indicator (K, D, J lines)|`Kdj`|
|🚧 KRI - Kairi Relative Index|`Kri`|
@@ -101,7 +103,15 @@
|TSI - True Strength Index|`Tsi`|
|UO - Ultimate Oscillator|`Uo`|
|WILLR - Larry Williams' %R|`Willr`|
-|DOSC - Derivative Oscillator|`Dosc`|
+|**PATTERNS**||
+|🚧 DOJI - Doji Candlestick Pattern|`Doji`|
+|🚧 ER* - Elder Ray Pattern (Bull Power, Bear Power)|`Er`|
+|🚧 MARU - Marubozu Candlestick Pattern|`Maru`|
+|🚧 PIV* - Pivot Points (Support 1-3, Pivot, Resistance 1-3)|`Piv`|
+|🚧 PP* - Price Pivots (Support 1-3, Pivot, Resistance 1-3)|`Pp`|
+|🚧 RPP* - Rolling Pivot Points (Support 1-3, Pivot, Resistance 1-3)|`Rpp`|
+|🚧 WF - Williams Fractal|`Wf`|
+|🚧 ZZ - Zig Zag Pattern|`Zz`|
|**VOLATILITY INDICATORS**||
|ADR - Average Daily Range|`Adr`|
|AP - Andrew's Pitchfork|`Ap`|
@@ -159,12 +169,12 @@
|VWAP - Volume Weighted Average Price|`Vwap`|
|VWMA - Volume Weighted Moving Average|`Vwma`|
|**NUMERICAL ANALYSIS**||
-|🚧 BETA* - Beta coefficient (Beta, R-squared)|`Beta`|
-|🚧 CORR* - Correlation Coefficient (Correlation, P-value)|`Corr`|
+|BETA* - Beta coefficient (Beta, R-squared)|`Beta`|
+|CORR* - Correlation Coefficient (Correlation, P-value)|`Corr`|
|CURVATURE - Rate of Change in Direction or Slope|`Curvature`|
|ENTROPY - Measure of Uncertainty or Disorder|`Entropy`|
-|🚧 HUBER - Huber Loss|`Huber`|
-|🚧 HURST - Hurst Exponent|`Hurst`|
+|HUBER - Huber Loss|`Huber`|
+|HURST - Hurst Exponent|`Hurst`|
|KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`|
|MAX - Maximum with exponential decay|`Max`|
|MEDIAN - Middle value|`Median`|
diff --git a/lib/errors/Huber.cs b/lib/errors/Huber.cs
index 12c5779b..ffe7a0a1 100644
--- a/lib/errors/Huber.cs
+++ b/lib/errors/Huber.cs
@@ -43,14 +43,9 @@ public sealed class Huber : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Huber(int period, double delta = 1.0)
{
- if (period < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1.");
- }
- if (delta <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(delta), "Delta must be greater than 0.");
- }
+ ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(delta, 0);
+
WarmupPeriod = period;
_actualBuffer = new CircularBuffer(period);
_predictedBuffer = new CircularBuffer(period);
diff --git a/lib/momentum/Macd.cs b/lib/momentum/Macd.cs
index 67c4d589..263c80ec 100644
--- a/lib/momentum/Macd.cs
+++ b/lib/momentum/Macd.cs
@@ -60,14 +60,14 @@ public sealed class Macd : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Macd(int fastPeriod = DefaultFastPeriod, int slowPeriod = DefaultSlowPeriod, int signalPeriod = DefaultSignalPeriod)
{
- if (fastPeriod < 1)
- throw new ArgumentOutOfRangeException(nameof(fastPeriod));
- if (slowPeriod < 1)
- throw new ArgumentOutOfRangeException(nameof(slowPeriod));
- if (signalPeriod < 1)
- throw new ArgumentOutOfRangeException(nameof(signalPeriod));
+ ArgumentOutOfRangeException.ThrowIfLessThan(fastPeriod, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(slowPeriod, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(signalPeriod, 1);
+
if (fastPeriod >= slowPeriod)
- throw new ArgumentException("Fast period must be less than slow period");
+ {
+ throw new ArgumentOutOfRangeException(nameof(fastPeriod), "Fast period must be less than slow period");
+ }
_fastEma = new(fastPeriod);
_slowEma = new(slowPeriod);
diff --git a/lib/momentum/_list.md b/lib/momentum/_list.md
index 1d01f144..85cc2072 100644
--- a/lib/momentum/_list.md
+++ b/lib/momentum/_list.md
@@ -4,7 +4,7 @@ Done: 15, Todo: 2
✔️ ADX - Average Directional Movement Index
✔️ ADXR - Average Directional Movement Index Rating
✔️ APO - Absolute Price Oscillator
-✔️ *DMI - Directional Movement Index (DI+, DI-)
+✔️ DMI - Directional Movement Index (DI+, DI-)
✔️ DMX - Jurik Directional Movement Index
✔️ DPO - Detrended Price Oscillator
✔️ *MACD - Moving Average Convergence/Divergence (MACD, Signal, Histogram)
diff --git a/lib/oscillators/Coppock.cs b/lib/oscillators/Coppock.cs
index 9cd6231c..ed905b4c 100644
--- a/lib/oscillators/Coppock.cs
+++ b/lib/oscillators/Coppock.cs
@@ -51,12 +51,9 @@ public sealed class Coppock : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Coppock(int roc1Period = DefaultRoc1Period, int roc2Period = DefaultRoc2Period, int wmaPeriod = DefaultWmaPeriod)
{
- if (roc1Period < 1)
- throw new ArgumentOutOfRangeException(nameof(roc1Period), "ROC1 period must be greater than 0");
- if (roc2Period < 1)
- throw new ArgumentOutOfRangeException(nameof(roc2Period), "ROC2 period must be greater than 0");
- if (wmaPeriod < 1)
- throw new ArgumentOutOfRangeException(nameof(wmaPeriod), "WMA period must be greater than 0");
+ ArgumentOutOfRangeException.ThrowIfLessThan(roc1Period, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(roc2Period, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(wmaPeriod, 1);
_roc1Period = roc1Period;
_roc2Period = roc2Period;
diff --git a/lib/oscillators/Rsi.cs b/lib/oscillators/Rsi.cs
index c18f70aa..3db9f953 100644
--- a/lib/oscillators/Rsi.cs
+++ b/lib/oscillators/Rsi.cs
@@ -48,8 +48,7 @@ public sealed class Rsi : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rsi(int period = DefaultPeriod)
{
- if (period < 1)
- throw new ArgumentOutOfRangeException(nameof(period));
+ ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
_avgGain = new(period, useSma: true);
_avgLoss = new(period, useSma: true);
_index = 0;
diff --git a/lib/oscillators/Smi.cs b/lib/oscillators/Smi.cs
index 27e6e635..989218d1 100644
--- a/lib/oscillators/Smi.cs
+++ b/lib/oscillators/Smi.cs
@@ -57,12 +57,9 @@ public sealed class Smi : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Smi(int period = DefaultPeriod, int smooth1 = DefaultSmooth1, int smooth2 = DefaultSmooth2)
{
- if (period < 1)
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0");
- if (smooth1 < 1)
- throw new ArgumentOutOfRangeException(nameof(smooth1), "Smooth1 must be greater than 0");
- if (smooth2 < 1)
- throw new ArgumentOutOfRangeException(nameof(smooth2), "Smooth2 must be greater than 0");
+ ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(smooth1, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(smooth2, 1);
_highs = new(period);
_lows = new(period);
diff --git a/lib/oscillators/Srsi.cs b/lib/oscillators/Srsi.cs
index c58d3fe9..bdb2c3df 100644
--- a/lib/oscillators/Srsi.cs
+++ b/lib/oscillators/Srsi.cs
@@ -40,7 +40,6 @@ public sealed class Srsi : AbstractBase
private readonly CircularBuffer _srsiValues;
private readonly Sma _signal;
private readonly int _rsiPeriod;
- private readonly int _stochPeriod;
private const int DefaultRsiPeriod = 14;
private const int DefaultStochPeriod = 14;
private const int DefaultSmoothK = 3;
@@ -56,25 +55,12 @@ public sealed class Srsi : AbstractBase
public Srsi(int rsiPeriod = DefaultRsiPeriod, int stochPeriod = DefaultStochPeriod,
int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD)
{
- if (rsiPeriod < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(rsiPeriod), "Period must be greater than 0");
- }
- if (stochPeriod < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(stochPeriod), "Period must be greater than 0");
- }
- if (smoothK < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(smoothK), "Period must be greater than 0");
- }
- if (smoothD < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(smoothD), "Period must be greater than 0");
- }
+ ArgumentOutOfRangeException.ThrowIfLessThan(rsiPeriod, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(stochPeriod, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(smoothK, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(smoothD, 1);
_rsiPeriod = rsiPeriod;
- _stochPeriod = stochPeriod;
_rsi = new(rsiPeriod);
_rsiValues = new(stochPeriod);
_srsiValues = new(smoothK);
diff --git a/lib/oscillators/Stc.cs b/lib/oscillators/Stc.cs
index b1ab2560..8b755952 100644
--- a/lib/oscillators/Stc.cs
+++ b/lib/oscillators/Stc.cs
@@ -62,32 +62,17 @@ public sealed class Stc : AbstractBase
int slowPeriod = DefaultSlowPeriod, int d1Period = DefaultD1Period,
int stcPeriod = DefaultStcPeriod)
{
- string err = "All periods must be greater than 0";
+ ArgumentOutOfRangeException.ThrowIfLessThan(cyclePeriod, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(fastPeriod, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(slowPeriod, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(d1Period, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(stcPeriod, 1);
- if (cyclePeriod < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(cyclePeriod), err);
- }
- if (fastPeriod < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(fastPeriod), err);
- }
- if (slowPeriod < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(slowPeriod), err);
- }
- if (d1Period < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(d1Period), err);
- }
- if (stcPeriod < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(stcPeriod), err);
- }
if (fastPeriod >= slowPeriod)
{
throw new ArgumentOutOfRangeException(nameof(fastPeriod), "Fast period must be less than slow period");
}
+
_fastEma = new(fastPeriod);
_slowEma = new(slowPeriod);
_macdValues = new(cyclePeriod);
diff --git a/lib/oscillators/Stoch.cs b/lib/oscillators/Stoch.cs
index ab10d0b6..1f5d30a0 100644
--- a/lib/oscillators/Stoch.cs
+++ b/lib/oscillators/Stoch.cs
@@ -52,12 +52,9 @@ public sealed class Stoch : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Stoch(int period = DefaultPeriod, int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD)
{
- if (period < 1)
- throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0");
- if (smoothK < 1)
- throw new ArgumentOutOfRangeException(nameof(smoothK), "%K smoothing period must be greater than 0");
- if (smoothD < 1)
- throw new ArgumentOutOfRangeException(nameof(smoothD), "%D smoothing period must be greater than 0");
+ ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(smoothK, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(smoothD, 1);
_highs = new(period);
_lows = new(period);
diff --git a/lib/oscillators/Uo.cs b/lib/oscillators/Uo.cs
index ec247ebd..38a98129 100644
--- a/lib/oscillators/Uo.cs
+++ b/lib/oscillators/Uo.cs
@@ -67,30 +67,12 @@ public sealed class Uo : AbstractBase
public Uo(int period1 = DefaultPeriod1, int period2 = DefaultPeriod2, int period3 = DefaultPeriod3,
double weight1 = DefaultWeight1, double weight2 = DefaultWeight2, double weight3 = DefaultWeight3)
{
- if (period1 < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period1), "Period1 must be greater than 0");
- }
- if (period2 < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period2), "Period2 must be greater than 0");
- }
- if (period3 < 1)
- {
- throw new ArgumentOutOfRangeException(nameof(period3), "Period3 must be greater than 0");
- }
- if (weight1 <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(weight1), "Weight1 must be greater than 0");
- }
- if (weight2 <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(weight2), "Weight2 must be greater than 0");
- }
- if (weight3 <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(weight3), "Weight3 must be greater than 0");
- }
+ ArgumentOutOfRangeException.ThrowIfLessThan(period1, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(period2, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThan(period3, 1);
+ ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight1, 0);
+ ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight2, 0);
+ ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight3, 0);
_weight1 = weight1;
_weight2 = weight2;
diff --git a/lib/statistics/Beta.cs b/lib/statistics/Beta.cs
new file mode 100644
index 00000000..97e065f2
--- /dev/null
+++ b/lib/statistics/Beta.cs
@@ -0,0 +1,159 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// 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.
+///
+///
+/// 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
+///
+[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;
+
+ /// The number of points to consider for beta calculation.
+ /// Thrown when period is less than 2.
+ [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();
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of points to consider for beta calculation.
+ [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 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 assetReturns, ReadOnlySpan 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 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 assetValues = _assetReturns.GetSpan();
+ ReadOnlySpan 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;
+ }
+}
diff --git a/lib/statistics/Corr.cs b/lib/statistics/Corr.cs
new file mode 100644
index 00000000..0eb18c50
--- /dev/null
+++ b/lib/statistics/Corr.cs
@@ -0,0 +1,163 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// 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.
+///
+///
+/// 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
+///
+[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;
+
+ /// The number of points to consider for correlation calculation.
+ /// Thrown when period is less than 2.
+ [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();
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of points to consider for correlation calculation.
+ [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 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 xValues, ReadOnlySpan 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 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 xValues = _xValues.GetSpan();
+ ReadOnlySpan 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;
+ }
+}
diff --git a/lib/statistics/Percentile.cs b/lib/statistics/Percentile.cs
index 635f6680..c8ef7be9 100644
--- a/lib/statistics/Percentile.cs
+++ b/lib/statistics/Percentile.cs
@@ -45,7 +45,6 @@ public sealed class Percentile : AbstractBase
private readonly int Period;
private readonly double Percent;
private readonly CircularBuffer _buffer;
- private const double Epsilon = 1e-10;
private const int MinimumPoints = 2;
/// The number of points to consider for percentile calculation.
@@ -56,16 +55,10 @@ public sealed class Percentile : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Percentile(int period, double percent)
{
- if (period < MinimumPoints)
- {
- throw new ArgumentOutOfRangeException(nameof(period),
- "Period must be greater than or equal to 2 for percentile calculation.");
- }
- if (percent < 0 || percent > 100)
- {
- throw new ArgumentOutOfRangeException(nameof(percent),
- "Percent must be between 0 and 100.");
- }
+ 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
diff --git a/lib/statistics/Theil.cs b/lib/statistics/Theil.cs
new file mode 100644
index 00000000..3ebcb7b5
--- /dev/null
+++ b/lib/statistics/Theil.cs
@@ -0,0 +1,167 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// THEIL: Theil's U Statistics (U1, U2)
+/// A statistical measure that quantifies the accuracy of forecasts compared to actual values
+/// and naive forecasts.
+///
+///
+/// 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
+///
+[SkipLocalsInit]
+public sealed class Theil : AbstractBase
+{
+ private readonly int Period;
+ private readonly CircularBuffer _actual;
+ private readonly CircularBuffer _forecast;
+ private const int MinimumPoints = 2;
+
+ ///
+ /// Gets the U2 statistic comparing forecast with naive forecast
+ ///
+ public double U2 { get; private set; }
+
+ /// The number of points to consider for Theil's U calculation.
+ /// Thrown when period is less than 2.
+ [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();
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of points to consider for Theil's U calculation.
+ [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 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 forecast, ReadOnlySpan 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 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 actualValues = _actual.GetSpan();
+ ReadOnlySpan 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;
+ }
+}
diff --git a/lib/statistics/Tsf.cs b/lib/statistics/Tsf.cs
new file mode 100644
index 00000000..7001e1e0
--- /dev/null
+++ b/lib/statistics/Tsf.cs
@@ -0,0 +1,185 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// 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.
+///
+///
+/// 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
+///
+[SkipLocalsInit]
+public sealed class Tsf : AbstractBase
+{
+ private readonly int Period;
+ private readonly CircularBuffer _values;
+ private const int MinimumPoints = 2;
+
+ ///
+ /// The forecasted value for the next period.
+ ///
+ public double Forecast { get; private set; }
+
+ ///
+ /// The lower bound of the confidence interval.
+ ///
+ public double LowerBound { get; private set; }
+
+ ///
+ /// The upper bound of the confidence interval.
+ ///
+ public double UpperBound { get; private set; }
+
+ /// The number of historical data points to consider for forecasting.
+ /// Thrown when period is less than 2.
+ [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();
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of historical data points to consider for forecasting.
+ [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 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 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 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;
+ }
+}
diff --git a/lib/statistics/_list.md b/lib/statistics/_list.md
index 4e16b0de..3ca1af8c 100644
--- a/lib/statistics/_list.md
+++ b/lib/statistics/_list.md
@@ -1,22 +1,32 @@
-# Statistics indicators
-Done: 13, Todo: 6
+# Statistics
-*BETA - Beta coefficient (Beta, R-squared)
-*CORR - Correlation Coefficient (Correlation, P-value)
-✔️ CURVATURE - Rate of Change in Direction or Slope
-✔️ ENTROPY - Measure of Uncertainty or Disorder
-✔️ HURST - Hurst Exponent
-✔️ KURTOSIS - Measure of Tails/Peakedness
-✔️ MAX - Maximum with exponential decay
-✔️ MEDIAN - Middle value
-✔️ MIN - Minimum with exponential decay
-✔️ MODE - Most Frequent Value
-✔️ PERCENTILE - Rank Order
-*RSQUARED - Coefficient of Determination (R-squared, Adjusted R-squared)
-✔️ SKEW - Skewness, asymmetry of distribution
-✔️ SLOPE - Rate of Change, Linear Regression
-✔️ STDDEV - Standard Deviation, Measure of Spread
-*THEIL - Theil's U Statistics (U1, U2)
-*TSF - Time Series Forecast (Forecast, Confidence Interval)
-✔️ VARIANCE - Average of Squared Deviations
-✔️ ZSCORE - Standardized Score
+Statistical functions and indicators for financial analysis.
+
+## Implemented
+
+- [Beta](Beta.cs) - Beta coefficient measuring volatility relative to market
+- [Corr](Corr.cs) - Correlation coefficient between two series
+- [Curvature](Curvature.cs) - Curvature of a time series
+- [Entropy](Entropy.cs) - Information entropy of a series
+- [Hurst](Hurst.cs) - Hurst exponent for trend strength
+- [Kurtosis](Kurtosis.cs) - Kurtosis measuring tail extremity
+- [Max](Max.cs) - Maximum value over period
+- [Median](Median.cs) - Median value over period
+- [Min](Min.cs) - Minimum value over period
+- [Mode](Mode.cs) - Mode (most frequent value)
+- [Percentile](Percentile.cs) - Percentile rank calculation
+- [Skew](Skew.cs) - Skewness measuring distribution asymmetry
+- [Slope](Slope.cs) - Linear regression slope
+- [Stddev](Stddev.cs) - Standard deviation
+- [Theil](Theil.cs) - Theil's U statistics for forecast accuracy
+- [Tsf](Tsf.cs) - Time series forecast
+- [Variance](Variance.cs) - Statistical variance
+- [Zscore](Zscore.cs) - Z-score standardization
+
+## Planned
+
+- Cointegration - Test for cointegrated series
+- Granger - Granger causality test
+- Jarque-Bera - Normality test
+- Kendall - Kendall rank correlation
+- Spearman - Spearman rank correlation