diff --git a/Tests/test_updates_oscillators.cs b/Tests/test_updates_oscillators.cs
index 12f55626..cbc20ec1 100644
--- a/Tests/test_updates_oscillators.cs
+++ b/Tests/test_updates_oscillators.cs
@@ -17,6 +17,15 @@ public class OscillatorsUpdateTests
return ((double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue * 200) - 100; // Range: -100 to 100
}
+ private TBar GetRandomBar(bool IsNew)
+ {
+ double open = GetRandomDouble();
+ double high = open + Math.Abs(GetRandomDouble());
+ double low = open - Math.Abs(GetRandomDouble());
+ double close = low + ((high - low) * GetRandomDouble());
+ return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew);
+ }
+
[Fact]
public void Rsi_Update()
{
@@ -66,12 +75,12 @@ public class OscillatorsUpdateTests
public void Ao_Update()
{
var indicator = new Ao();
- TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true);
+ TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
- indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, IsNew: false));
+ indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
@@ -82,12 +91,12 @@ public class OscillatorsUpdateTests
public void Ac_Update()
{
var indicator = new Ac();
- TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true);
+ TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
- indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, IsNew: false));
+ indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
@@ -98,12 +107,12 @@ public class OscillatorsUpdateTests
public void Aroon_Update()
{
var indicator = new Aroon(period: 25);
- TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true);
+ TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
- indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, IsNew: false));
+ indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
@@ -114,12 +123,12 @@ public class OscillatorsUpdateTests
public void Bop_Update()
{
var indicator = new Bop();
- TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true);
+ TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
- indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, IsNew: false));
+ indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
@@ -130,12 +139,12 @@ public class OscillatorsUpdateTests
public void Cci_Update()
{
var indicator = new Cci(period: 20);
- TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true);
+ TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
- indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, IsNew: false));
+ indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
@@ -161,12 +170,12 @@ public class OscillatorsUpdateTests
public void Chop_Update()
{
var indicator = new Chop(period: 14);
- TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true);
+ TBar r = GetRandomBar(true);
double initialValue = indicator.Calc(r);
for (int i = 0; i < RandomUpdates; i++)
{
- indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, IsNew: false));
+ indicator.Calc(GetRandomBar(IsNew: false));
}
double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
@@ -187,4 +196,113 @@ public class OscillatorsUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+
+ [Fact]
+ public void Smi_Update()
+ {
+ var indicator = new Smi(period: 10, smooth1: 3, smooth2: 3);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Srsi_Update()
+ {
+ var indicator = new Srsi(rsiPeriod: 14, stochPeriod: 14, smoothK: 3, smoothD: 3);
+ double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Stc_Update()
+ {
+ var indicator = new Stc(cyclePeriod: 10, fastPeriod: 23, slowPeriod: 50);
+ double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Stoch_Update()
+ {
+ var indicator = new Stoch(period: 14, smoothK: 3, smoothD: 3);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Tsi_Update()
+ {
+ var indicator = new Tsi(firstPeriod: 25, secondPeriod: 13);
+ double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Uo_Update()
+ {
+ var indicator = new Uo(period1: 7, period2: 14, period3: 28);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Willr_Update()
+ {
+ var indicator = new Willr(period: 14);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
}
diff --git a/Tests/test_updates_volatility.cs b/Tests/test_updates_volatility.cs
index 26b6a5f9..85a58ea7 100644
--- a/Tests/test_updates_volatility.cs
+++ b/Tests/test_updates_volatility.cs
@@ -170,6 +170,22 @@ public class VolatilityUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+ [Fact]
+ public void Dchn_Update()
+ {
+ var indicator = new Dchn(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Ewma_Update()
{
@@ -264,6 +280,54 @@ public class VolatilityUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+ [Fact]
+ public void Natr_Update()
+ {
+ var indicator = new Natr(period: 14);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Pch_Update()
+ {
+ var indicator = new Pch(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
+ [Fact]
+ public void Pv_Update()
+ {
+ var indicator = new Pv(period: 10);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Realized_Update()
{
@@ -279,6 +343,22 @@ public class VolatilityUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+ [Fact]
+ public void Rsv_Update()
+ {
+ var indicator = new Rsv(period: 10);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Rvi_Update()
{
@@ -294,6 +374,22 @@ public class VolatilityUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+ [Fact]
+ public void Sv_Update()
+ {
+ var indicator = new Sv(period: 20, lambda: 0.94);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
+
[Fact]
public void Tr_Update()
{
@@ -389,4 +485,20 @@ public class VolatilityUpdateTests
Assert.Equal(initialValue, finalValue, precision);
}
+
+ [Fact]
+ public void Yzv_Update()
+ {
+ var indicator = new Yzv(period: 20);
+ TBar r = GetRandomBar(true);
+ double initialValue = indicator.Calc(r);
+
+ for (int i = 0; i < RandomUpdates; i++)
+ {
+ indicator.Calc(GetRandomBar(IsNew: false));
+ }
+ double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
+
+ Assert.Equal(initialValue, finalValue, precision);
+ }
}
diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md
index 0aecedff..8c607e10 100644
--- a/docs/indicators/indicators.md
+++ b/docs/indicators/indicators.md
@@ -1,18 +1,16 @@
# Indicators in QuanTAlib
-
-
| **Category** | **Status** | **Completion** |
|--------------|:----------:|:--------------:|
| Basic Transforms | 6 of 6 | 100% |
| Averages & Trends | 33 of 33 | 100% |
-| Momentum | 17 of 17 | 100% |
-| Oscillators | 11 of 29 | 38% |
+| Momentum | 16 of 16 | 100% |
+| Oscillators | 20 of 29 | 69% |
| Volatility | 24 of 35 | 69% |
| Volume | 15 of 19 | 79% |
| Numerical Analysis | 13 of 19 | 68% |
| Errors | 16 of 16 | 100% |
-| **Total** | **135 of 174** | **78%** |
+| **Total** | **143 of 173** | **83%** |
|Technical Indicator Name| Class Name|
|-----------|:----------:|
@@ -71,7 +69,6 @@
|PPO - Percentage Price Oscillator|`Ppo`|
|PRS - Price Relative Strength|`Prs`|
|ROC - Rate of Change|`Roc`|
-|TSI - True Strength Index|`Tsi`|
|TRIX - 1-day ROC of TEMA|`Trix`|
|VEL - Jurik Signal Velocity|`Vel`|
|VORTEX* - Vortex Indicator (VI+, VI-)|`Vortex`|
@@ -85,8 +82,8 @@
|CMO - Chande Momentum Oscillator|`Cmo`|
|CHOP - Choppiness Index|`Chop`|
|COG - Ehler's Center of Gravity|`Cog`|
-|π§ COPPOCK - Coppock Curve|`Coppock`|
-|π§ CRSI - Connor RSI|`Crsi`|
+|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`|
@@ -98,13 +95,13 @@
|RSI - Relative Strength Index|`Rsi`|
|RSX - Jurik Trend Strength Index|`Rsx`|
|π§ RVGI* - Relative Vigor Index (RVGI, Signal)|`Rvgi`|
-|π§ SMI - Stochastic Momentum Index|`Smi`|
-|π§ SRSI* - Stochastic RSI (SRSI, Signal)|`Srsi`|
-|π§ STC - Schaff Trend Cycle|`Stc`|
-|π§ STOCH* - Stochastic Oscillator (%K, %D)|`Stoch`|
-|π§ TSI - True Strength Index|`Tsi`|
-|π§ UO - Ultimate Oscillator|`Uo`|
-|π§ WILLR - Larry Williams' %R|`Willr`|
+|SMI - Stochastic Momentum Index|`Smi`|
+|SRSI* - Stochastic RSI (SRSI, Signal)|`Srsi`|
+|STC - Schaff Trend Cycle|`Stc`|
+|STOCH* - Stochastic Oscillator (%K, %D)|`Stoch`|
+|TSI - True Strength Index|`Tsi`|
+|UO - Ultimate Oscillator|`Uo`|
+|WILLR - Larry Williams' %R|`Willr`|
|**VOLATILITY INDICATORS**||
|ADR - Average Daily Range|`Adr`|
|AP - Andrew's Pitchfork|`Ap`|
@@ -122,7 +119,7 @@
|GKV - Garman-Klass Volatility|`Gkv`|
|HLV - High-Low Volatility|`Hlv`|
|HV - Historical Volatility|`Hv`|
-|π§ ICH* - Ichimoku Cloud (Conversion, Base, Leading Span A, Leading Span B, Lagging Span)|`Ich`|
+|π§ ICH* - Ichimoku Cloud (Conversion, Base, Span A, Span B, Lagging Span)|`Ich`|
|JVOLTY - Jurik Volatility|`Jvolty`|
|π§ KC* - Keltner Channels (Upper, Middle, Lower)|`Kc`|
|π§ NATR - Normalized Average True Range|`Natr`|
diff --git a/lib/momentum/Tsi.cs b/lib/momentum/Tsi.cs
deleted file mode 100644
index e39458bc..00000000
--- a/lib/momentum/Tsi.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-using System.Runtime.CompilerServices;
-namespace QuanTAlib;
-
-///
-/// TSI: True Strength Index
-/// A momentum indicator that shows both trend direction and overbought/oversold conditions
-/// by using two smoothing steps on price changes.
-///
-///
-/// The TSI calculation process:
-/// 1. Calculate price change (PC):
-/// PC = Close - Previous Close
-/// 2. Calculate absolute price change (APC):
-/// APC = |PC|
-/// 3. Double smooth both PC and APC using EMA:
-/// First PC EMA = EMA(PC, firstPeriod)
-/// Second PC EMA = EMA(First PC EMA, secondPeriod)
-/// First APC EMA = EMA(APC, firstPeriod)
-/// Second APC EMA = EMA(First APC EMA, secondPeriod)
-/// 4. Calculate TSI:
-/// TSI = (Second PC EMA / Second APC EMA) * 100
-///
-/// Key characteristics:
-/// - Double smoothed momentum indicator
-/// - Oscillates between +100 and -100
-/// - Default periods are 25 and 13
-/// - Shows trend direction
-/// - Identifies overbought/oversold
-///
-/// Formula:
-/// TSI = (EMA(EMA(PC, r), s) / EMA(EMA(|PC|, r), s)) * 100
-/// where:
-/// PC = Close - Previous Close
-/// r = first period (default 25)
-/// s = second period (default 13)
-///
-/// Market Applications:
-/// - Trend direction
-/// - Overbought/Oversold levels
-/// - Centerline crossovers
-/// - Divergence analysis
-/// - Signal line crossovers
-///
-/// Sources:
-/// William Blau - Original development (1991)
-/// https://www.investopedia.com/terms/t/tsi.asp
-///
-/// Note: Values above +25 indicate overbought conditions, while values below -25 indicate oversold conditions
-///
-[SkipLocalsInit]
-public sealed class Tsi : AbstractBase
-{
- private readonly int _firstPeriod;
- private double _prevClose;
- private double _pcFirstEma;
- private double _pcSecondEma;
- private double _apcFirstEma;
- private double _apcSecondEma;
- private readonly double _firstAlpha;
- private readonly double _secondAlpha;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Tsi(int firstPeriod = 25, int secondPeriod = 13)
- {
- _firstPeriod = firstPeriod;
- WarmupPeriod = firstPeriod + secondPeriod;
- Name = $"TSI({_firstPeriod},{secondPeriod})";
- _firstAlpha = 2.0 / (firstPeriod + 1);
- _secondAlpha = 2.0 / (secondPeriod + 1);
- Init();
- }
-
- /// The data source object that publishes updates.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Tsi(object source, int firstPeriod = 25, int secondPeriod = 13) : this(firstPeriod, secondPeriod)
- {
- var pubEvent = source.GetType().GetEvent("Pub");
- pubEvent?.AddEventHandler(source, new BarSignal(Sub));
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override void Init()
- {
- base.Init();
- _prevClose = 0;
- _pcFirstEma = 0;
- _pcSecondEma = 0;
- _apcFirstEma = 0;
- _apcSecondEma = 0;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected override void ManageState(bool isNew)
- {
- if (isNew)
- {
- _lastValidValue = Value;
- _index++;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- protected override double Calculation()
- {
- ManageState(BarInput.IsNew);
-
- // Skip first period to establish previous close
- if (_index == 1)
- {
- _prevClose = BarInput.Close;
- return 0;
- }
-
- // Calculate price changes
- double pc = BarInput.Close - _prevClose;
- double apc = Math.Abs(pc);
-
- // Initialize or update EMAs
- if (_index <= _firstPeriod)
- {
- _pcFirstEma = pc;
- _apcFirstEma = apc;
- }
- else
- {
- _pcFirstEma = (_firstAlpha * pc) + ((1 - _firstAlpha) * _pcFirstEma);
- _apcFirstEma = (_firstAlpha * apc) + ((1 - _firstAlpha) * _apcFirstEma);
- }
-
- if (_index <= WarmupPeriod)
- {
- _pcSecondEma = _pcFirstEma;
- _apcSecondEma = _apcFirstEma;
- }
- else
- {
- _pcSecondEma = (_secondAlpha * _pcFirstEma) + ((1 - _secondAlpha) * _pcSecondEma);
- _apcSecondEma = (_secondAlpha * _apcFirstEma) + ((1 - _secondAlpha) * _apcSecondEma);
- }
-
- // Store current close for next calculation
- _prevClose = BarInput.Close;
-
- // Calculate TSI
- double tsi = Math.Abs(_apcSecondEma) > double.Epsilon ? (_pcSecondEma / _apcSecondEma) * 100 : 0;
-
- IsHot = _index >= WarmupPeriod;
- return tsi;
- }
-}
diff --git a/lib/oscillators/Coppock.cs b/lib/oscillators/Coppock.cs
new file mode 100644
index 00000000..9cd6231c
--- /dev/null
+++ b/lib/oscillators/Coppock.cs
@@ -0,0 +1,118 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// COPPOCK: Coppock Curve
+/// A long-term momentum oscillator used to identify major bottoms in the market.
+/// It is calculated using a weighted moving average of two different Rate of Change calculations.
+///
+///
+/// The Coppock Curve calculation process:
+/// 1. Calculate 14-period Rate of Change (ROC)
+/// 2. Calculate 11-period Rate of Change (ROC)
+/// 3. Sum the two ROC values
+/// 4. Apply 10-period Weighted Moving Average (WMA) to the sum
+///
+/// Key characteristics:
+/// - Long-term momentum indicator
+/// - Primarily used for monthly data
+/// - Buy signals when curve turns up from below zero
+/// - Rarely used for sell signals
+/// - Designed to identify major bottoms in stock market indices
+///
+/// Formula:
+/// COPPOCK = WMA(10) of (ROC(14) + ROC(11))
+/// where:
+/// ROC(n) = ((Price - Price[n]) / Price[n]) * 100
+/// WMA is weighted moving average
+///
+/// Sources:
+/// Edwin Coppock - Barron's Magazine (October 1962)
+/// https://www.investopedia.com/terms/c/coppockcurve.asp
+///
+/// Note: Originally designed for monthly data with parameters (14,11,10),
+/// but can be adapted for other timeframes
+///
+[SkipLocalsInit]
+public sealed class Coppock : AbstractBase
+{
+ private readonly CircularBuffer _values;
+ private readonly Wma _wma;
+ private readonly int _roc1Period;
+ private readonly int _roc2Period;
+ private const int DefaultRoc1Period = 14;
+ private const int DefaultRoc2Period = 11;
+ private const int DefaultWmaPeriod = 10;
+
+ /// The first ROC period (default 14).
+ /// The second ROC period (default 11).
+ /// The WMA smoothing period (default 10).
+ /// Thrown when any period is less than 1.
+ [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");
+
+ _roc1Period = roc1Period;
+ _roc2Period = roc2Period;
+ int maxPeriod = Math.Max(roc1Period, roc2Period);
+ _values = new(maxPeriod + 1);
+ _wma = new(wmaPeriod);
+ WarmupPeriod = maxPeriod + wmaPeriod;
+ Name = $"COPPOCK({roc1Period},{roc2Period},{wmaPeriod})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The first ROC period.
+ /// The second ROC period.
+ /// The WMA smoothing period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Coppock(object source, int roc1Period = DefaultRoc1Period, int roc2Period = DefaultRoc2Period, int wmaPeriod = DefaultWmaPeriod)
+ : this(roc1Period, roc2Period, wmaPeriod)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _values.Add(Input.Value);
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private double CalculateRoc(int period)
+ {
+ if (_index <= period) return 0;
+ double currentValue = _values[0];
+ double oldValue = _values[period];
+ return ((currentValue - oldValue) / oldValue) * 100.0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+
+ // Calculate ROC values and their sum
+ double roc1 = CalculateRoc(_roc1Period);
+ double roc2 = CalculateRoc(_roc2Period);
+ double rocSum = roc1 + roc2;
+
+ // Not enough data for WMA calculation
+ if (_index <= Math.Max(_roc1Period, _roc2Period))
+ return 0;
+
+ // Calculate WMA of ROC sums
+ return _wma.Calc(new TValue(Input.Time, rocSum, Input.IsNew));
+ }
+}
diff --git a/lib/oscillators/Crsi.cs b/lib/oscillators/Crsi.cs
new file mode 100644
index 00000000..1d65c9e8
--- /dev/null
+++ b/lib/oscillators/Crsi.cs
@@ -0,0 +1,97 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// CRSI: Connor RSI
+/// A momentum oscillator that combines three different RSI time periods to provide
+/// a more comprehensive view of price momentum. It helps identify overbought and
+/// oversold conditions with higher accuracy than traditional RSI.
+///
+///
+/// The CRSI calculation process:
+/// 1. Calculate three RSIs with different periods (3,2,1)
+/// 2. Sum the three RSI values
+/// 3. Divide by 3 to get the average
+///
+/// Key characteristics:
+/// - Oscillates between 0 and 100
+/// - More responsive than traditional RSI
+/// - Combines multiple timeframes
+/// - Traditional overbought level at 90
+/// - Traditional oversold level at 10
+///
+/// Formula:
+/// CRSI = (RSI(3) + RSI(2) + RSI(1)) / 3
+/// where each RSI is calculated using standard RSI formula:
+/// RSI = 100 - (100 / (1 + RS))
+/// RS = Average Gain / Average Loss
+///
+/// Sources:
+/// Larry Connors - "Short-term Trading Strategies That Work"
+/// https://www.tradingview.com/script/cYk1LVpw-Connors-RSI-LazyBear/
+///
+/// Note: Default periods are 3,2,1 as recommended by Connors
+///
+[SkipLocalsInit]
+public sealed class Crsi : AbstractBase
+{
+ private readonly Rsi _rsi3;
+ private readonly Rsi _rsi2;
+ private readonly Rsi _rsi1;
+ private const int DefaultPeriod1 = 3;
+ private const int DefaultPeriod2 = 2;
+ private const int DefaultPeriod3 = 1;
+
+ /// The first RSI period (default 3).
+ /// The second RSI period (default 2).
+ /// The third RSI period (default 1).
+ /// Thrown when any period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Crsi(int period1 = DefaultPeriod1, int period2 = DefaultPeriod2, int period3 = DefaultPeriod3)
+ {
+ 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");
+
+ _rsi3 = new(period1);
+ _rsi2 = new(period2);
+ _rsi1 = new(period3);
+ WarmupPeriod = Math.Max(Math.Max(period1, period2), period3) + 1;
+ Name = $"CRSI({period1},{period2},{period3})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The first RSI period.
+ /// The second RSI period.
+ /// The third RSI period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Crsi(object source, int period1 = DefaultPeriod1, int period2 = DefaultPeriod2, int period3 = DefaultPeriod3)
+ : this(period1, period2, period3)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew) _index++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+
+ // Calculate individual RSIs
+ double rsi3 = _rsi3.Calc(Input);
+ double rsi2 = _rsi2.Calc(Input);
+ double rsi1 = _rsi1.Calc(Input);
+
+ // Average the three RSIs
+ return (rsi3 + rsi2 + rsi1) / 3.0;
+ }
+}
diff --git a/lib/oscillators/Smi.cs b/lib/oscillators/Smi.cs
new file mode 100644
index 00000000..27e6e635
--- /dev/null
+++ b/lib/oscillators/Smi.cs
@@ -0,0 +1,121 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// SMI: Stochastic Momentum Index
+/// A double-smoothed momentum indicator that shows where the close is relative
+/// to the midpoint of the recent high/low range. It helps identify overbought
+/// and oversold conditions with higher accuracy than traditional stochastics.
+///
+///
+/// The SMI calculation process:
+/// 1. Calculate median price distance (Close - (High + Low)/2)
+/// 2. Calculate highest high and lowest low over period
+/// 3. First smoothing of median distance and range
+/// 4. Second smoothing of first smoothed values
+/// 5. Scale to percentage (-100 to +100)
+///
+/// Key characteristics:
+/// - Oscillates between -100 and +100
+/// - Double smoothing reduces noise
+/// - Traditional overbought level at +40
+/// - Traditional oversold level at -40
+/// - Centerline crossovers signal trend changes
+///
+/// Formula:
+/// D = Close - (High + Low)/2
+/// HL = Highest High - Lowest Low
+/// First smoothing:
+/// SD = EMA(EMA(D, period1), period2)
+/// SHL = EMA(EMA(HL, period1), period2)
+/// SMI = 100 * (SD / (SHL/2))
+///
+/// Sources:
+/// William Blau - "Momentum, Direction, and Divergence" (1995)
+/// https://www.tradingview.com/scripts/stochasticmomentumindex/
+///
+/// Note: Default periods (10,3,3) are commonly used values
+///
+[SkipLocalsInit]
+public sealed class Smi : AbstractBase
+{
+ private readonly CircularBuffer _highs;
+ private readonly CircularBuffer _lows;
+ private readonly Ema _dEma1;
+ private readonly Ema _dEma2;
+ private readonly Ema _hlEma1;
+ private readonly Ema _hlEma2;
+ private const int DefaultPeriod = 10;
+ private const int DefaultSmooth1 = 3;
+ private const int DefaultSmooth2 = 3;
+ private const double ScalingFactor = 100.0;
+
+ /// The lookback period (default 10).
+ /// First smoothing period (default 3).
+ /// Second smoothing period (default 3).
+ /// Thrown when any period is less than 1.
+ [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");
+
+ _highs = new(period);
+ _lows = new(period);
+ _dEma1 = new(smooth1);
+ _dEma2 = new(smooth2);
+ _hlEma1 = new(smooth1);
+ _hlEma2 = new(smooth2);
+ WarmupPeriod = period + smooth1 + smooth2;
+ Name = $"SMI({period},{smooth1},{smooth2})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The lookback period.
+ /// First smoothing period.
+ /// Second smoothing period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Smi(object source, int period = DefaultPeriod, int smooth1 = DefaultSmooth1, int smooth2 = DefaultSmooth2)
+ : this(period, smooth1, smooth2)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _highs.Add(BarInput.High);
+ _lows.Add(BarInput.Low);
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Calculate median price distance and range
+ double midpoint = (BarInput.High + BarInput.Low) / 2.0;
+ double distance = BarInput.Close - midpoint;
+ double range = _highs.Max() - _lows.Min();
+
+ // First smoothing
+ double smoothD1 = _dEma1.Calc(new TValue(BarInput.Time, distance, BarInput.IsNew));
+ double smoothHL1 = _hlEma1.Calc(new TValue(BarInput.Time, range, BarInput.IsNew));
+
+ // Second smoothing
+ double smoothD2 = _dEma2.Calc(new TValue(BarInput.Time, smoothD1, BarInput.IsNew));
+ double smoothHL2 = _hlEma2.Calc(new TValue(BarInput.Time, smoothHL1, BarInput.IsNew));
+
+ // Calculate SMI
+ return smoothHL2 >= double.Epsilon ? ScalingFactor * (smoothD2 / (smoothHL2 / 2.0)) : 0;
+ }
+}
diff --git a/lib/oscillators/Srsi.cs b/lib/oscillators/Srsi.cs
new file mode 100644
index 00000000..c58d3fe9
--- /dev/null
+++ b/lib/oscillators/Srsi.cs
@@ -0,0 +1,145 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// SRSI: Stochastic RSI
+/// A momentum oscillator that applies the stochastic formula to RSI values
+/// instead of price data. It provides a more sensitive indicator than standard
+/// RSI or Stochastic oscillators.
+///
+///
+/// The SRSI calculation process:
+/// 1. Calculate RSI
+/// 2. Apply Stochastic formula to RSI values:
+/// - Find highest high and lowest low of RSI over period
+/// - Calculate where current RSI is within this range
+/// 3. Smooth the result with SMA (signal line)
+///
+/// Key characteristics:
+/// - Oscillates between 0 and 100
+/// - More sensitive than standard RSI
+/// - Combines benefits of both RSI and Stochastic
+/// - Traditional overbought level at 80
+/// - Traditional oversold level at 20
+///
+/// Formula:
+/// SRSI = ((RSI - Lowest RSI) / (Highest RSI - Lowest RSI)) * 100
+/// Signal = SMA(SRSI, signalPeriod)
+///
+/// Sources:
+/// Tushar Chande and Stanley Kroll - "The New Technical Trader" (1994)
+/// https://www.investopedia.com/terms/s/stochrsi.asp
+///
+/// Note: Default periods (14,14,3,3) are commonly used values
+///
+[SkipLocalsInit]
+public sealed class Srsi : AbstractBase
+{
+ private readonly Rsi _rsi;
+ private readonly CircularBuffer _rsiValues;
+ 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;
+ private const int DefaultSmoothD = 3;
+ private const double ScalingFactor = 100.0;
+
+ /// The RSI period (default 14).
+ /// The Stochastic period (default 14).
+ /// K line smoothing period (default 3).
+ /// D line smoothing period (default 3).
+ /// Thrown when any period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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");
+ }
+
+ _rsiPeriod = rsiPeriod;
+ _stochPeriod = stochPeriod;
+ _rsi = new(rsiPeriod);
+ _rsiValues = new(stochPeriod);
+ _srsiValues = new(smoothK);
+ _signal = new(smoothD);
+ WarmupPeriod = rsiPeriod + stochPeriod + Math.Max(smoothK, smoothD);
+ Name = $"SRSI({rsiPeriod},{stochPeriod},{smoothK},{smoothD})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The RSI period.
+ /// The Stochastic period.
+ /// K line smoothing period.
+ /// D line smoothing period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Srsi(object source, int rsiPeriod = DefaultRsiPeriod, int stochPeriod = DefaultStochPeriod,
+ int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD)
+ : this(rsiPeriod, stochPeriod, smoothK, smoothD)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew) _index++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+
+ // Calculate RSI
+ double rsiValue = _rsi.Calc(Input);
+
+ if (Input.IsNew)
+ _rsiValues.Add(rsiValue);
+
+ // Not enough data
+ if (_index <= _rsiPeriod)
+ return 0;
+
+ // Calculate Stochastic RSI
+ double highest = _rsiValues.Max();
+ double lowest = _rsiValues.Min();
+ double range = highest - lowest;
+ double srsi = range >= double.Epsilon ? ((rsiValue - lowest) / range) * ScalingFactor : 0;
+
+ if (Input.IsNew)
+ _srsiValues.Add(srsi);
+
+ // Calculate signal line
+ return _signal.Calc(new TValue(Input.Time, srsi, Input.IsNew));
+ }
+
+ ///
+ /// Gets the K line value (raw Stochastic RSI)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double K() => _srsiValues[0];
+
+ ///
+ /// Gets the D line value (signal line)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double D() => Value;
+}
diff --git a/lib/oscillators/Stc.cs b/lib/oscillators/Stc.cs
new file mode 100644
index 00000000..feb544c5
--- /dev/null
+++ b/lib/oscillators/Stc.cs
@@ -0,0 +1,140 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// STC: Schaff Trend Cycle
+/// A trend-following indicator that combines MACD and stochastic concepts
+/// to create a smoother, more responsive indicator with less noise.
+///
+///
+/// The STC calculation process:
+/// 1. Calculate MACD-style momentum using EMAs
+/// 2. Apply double stochastic formula to smooth the momentum
+/// 3. Scale result to oscillator range
+///
+/// Key characteristics:
+/// - Oscillates between 0 and 100
+/// - Combines trend and momentum
+/// - Double smoothing reduces noise
+/// - Traditional overbought level at 75
+/// - Traditional oversold level at 25
+///
+/// Formula:
+/// Momentum = EMA1(Close) - EMA2(Close)
+/// First Stochastic:
+/// %K1 = 100 * (Momentum - Lowest Low) / (Highest High - Lowest Low)
+/// %D1 = EMA(%K1)
+/// Second Stochastic:
+/// %K2 = 100 * (%D1 - Lowest %D1) / (Highest %D1 - Lowest %D1)
+/// STC = EMA(%K2)
+///
+/// Sources:
+/// Doug Schaff - "The Schaff Trend Cycle" (1999)
+/// https://www.tradingview.com/script/o6tSS6Hn-Schaff-Trend-Cycle/
+///
+/// Note: Default periods (23,10,3) were recommended by Schaff
+///
+[SkipLocalsInit]
+public sealed class Stc : AbstractBase
+{
+ private readonly Ema _fastEma;
+ private readonly Ema _slowEma;
+ private readonly CircularBuffer _macdValues;
+ private readonly CircularBuffer _k1Values;
+ private readonly CircularBuffer _d1Values;
+ private readonly Ema _d1Ema;
+ private readonly Ema _stcEma;
+ private const int DefaultCyclePeriod = 10;
+ private const int DefaultFastPeriod = 23;
+ private const int DefaultSlowPeriod = 50;
+ private const int DefaultD1Period = 3;
+ private const int DefaultStcPeriod = 3;
+ private const double ScalingFactor = 100.0;
+
+ /// The lookback period for highs/lows (default 10).
+ /// Fast EMA period (default 23).
+ /// Slow EMA period (default 50).
+ /// First %D smoothing period (default 3).
+ /// Final STC smoothing period (default 3).
+ /// Thrown when any period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Stc(int cyclePeriod = DefaultCyclePeriod, int fastPeriod = DefaultFastPeriod,
+ int slowPeriod = DefaultSlowPeriod, int d1Period = DefaultD1Period,
+ int stcPeriod = DefaultStcPeriod)
+ {
+ if (cyclePeriod < 1 || fastPeriod < 1 || slowPeriod < 1 || d1Period < 1 || stcPeriod < 1)
+ throw new ArgumentOutOfRangeException(nameof(cyclePeriod), "All periods must be greater than 0");
+ 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);
+ _k1Values = new(cyclePeriod);
+ _d1Values = new(cyclePeriod);
+ _d1Ema = new(d1Period);
+ _stcEma = new(stcPeriod);
+
+ WarmupPeriod = slowPeriod + cyclePeriod + Math.Max(d1Period, stcPeriod);
+ Name = $"STC({cyclePeriod},{fastPeriod},{slowPeriod},{d1Period},{stcPeriod})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The lookback period for highs/lows.
+ /// Fast EMA period.
+ /// Slow EMA period.
+ /// First %D smoothing period.
+ /// Final STC smoothing period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Stc(object source, int cyclePeriod = DefaultCyclePeriod, int fastPeriod = DefaultFastPeriod,
+ int slowPeriod = DefaultSlowPeriod, int d1Period = DefaultD1Period,
+ int stcPeriod = DefaultStcPeriod)
+ : this(cyclePeriod, fastPeriod, slowPeriod, d1Period, stcPeriod)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew) _index++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private static double CalculateStochastic(double value, double highest, double lowest)
+ {
+ double range = highest - lowest;
+ return range >= double.Epsilon ? ((value - lowest) / range) * ScalingFactor : 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+
+ // Calculate MACD-style momentum
+ double fastEma = _fastEma.Calc(Input);
+ double slowEma = _slowEma.Calc(Input);
+ double macd = fastEma - slowEma;
+
+ if (Input.IsNew)
+ _macdValues.Add(macd);
+
+ // First stochastic
+ double k1 = CalculateStochastic(macd, _macdValues.Max(), _macdValues.Min());
+ if (Input.IsNew)
+ _k1Values.Add(k1);
+
+ double d1 = _d1Ema.Calc(new TValue(Input.Time, k1, Input.IsNew));
+ if (Input.IsNew)
+ _d1Values.Add(d1);
+
+ // Second stochastic
+ double k2 = CalculateStochastic(d1, _d1Values.Max(), _d1Values.Min());
+
+ // Final smoothing
+ return _stcEma.Calc(new TValue(Input.Time, k2, Input.IsNew));
+ }
+}
diff --git a/lib/oscillators/Stoch.cs b/lib/oscillators/Stoch.cs
new file mode 100644
index 00000000..ab10d0b6
--- /dev/null
+++ b/lib/oscillators/Stoch.cs
@@ -0,0 +1,126 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// STOCH: Stochastic Oscillator
+/// A momentum indicator that shows the location of the close relative to
+/// high-low range over a period. Consists of %K (fast) and %D (slow) lines.
+///
+///
+/// The Stochastic calculation process:
+/// 1. Calculate %K (raw stochastic):
+/// - Find highest high and lowest low over period
+/// - Calculate where current close is within this range
+/// 2. Smooth %K with SMA to get Fast %K
+/// 3. Smooth Fast %K with SMA to get %D (signal line)
+///
+/// Key characteristics:
+/// - Oscillates between 0 and 100
+/// - Traditional overbought level at 80
+/// - Traditional oversold level at 20
+/// - %K/%D crossovers signal momentum shifts
+/// - Divergence with price shows potential reversals
+///
+/// Formula:
+/// Raw %K = 100 * (Close - Lowest Low) / (Highest High - Lowest Low)
+/// Fast %K = SMA(Raw %K, smoothK)
+/// %D = SMA(Fast %K, smoothD)
+///
+/// Sources:
+/// George Lane - "Lane's Stochastics" (1950s)
+/// https://www.investopedia.com/terms/s/stochasticoscillator.asp
+///
+/// Note: Default periods (14,3,3) are commonly used values
+///
+[SkipLocalsInit]
+public sealed class Stoch : AbstractBase
+{
+ private readonly CircularBuffer _highs;
+ private readonly CircularBuffer _lows;
+ private readonly Sma _fastK;
+ private readonly Sma _slowD;
+ private readonly CircularBuffer _rawK;
+ private const int DefaultPeriod = 14;
+ private const int DefaultSmoothK = 3;
+ private const int DefaultSmoothD = 3;
+ private const double ScalingFactor = 100.0;
+
+ /// The lookback period (default 14).
+ /// %K smoothing period (default 3).
+ /// %D smoothing period (default 3).
+ /// Thrown when any period is less than 1.
+ [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");
+
+ _highs = new(period);
+ _lows = new(period);
+ _rawK = new(smoothK);
+ _fastK = new(smoothK);
+ _slowD = new(smoothD);
+ WarmupPeriod = period + Math.Max(smoothK, smoothD);
+ Name = $"STOCH({period},{smoothK},{smoothD})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The lookback period.
+ /// %K smoothing period.
+ /// %D smoothing period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Stoch(object source, int period = DefaultPeriod, int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD)
+ : this(period, smoothK, smoothD)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _highs.Add(BarInput.High);
+ _lows.Add(BarInput.Low);
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Calculate raw %K
+ double highest = _highs.Max();
+ double lowest = _lows.Min();
+ double range = highest - lowest;
+ double rawK = range >= double.Epsilon ? ((BarInput.Close - lowest) / range) * ScalingFactor : 0;
+
+ if (BarInput.IsNew)
+ _rawK.Add(rawK);
+
+ // Calculate Fast %K (first smoothing)
+ double fastK = _fastK.Calc(new TValue(BarInput.Time, rawK, BarInput.IsNew));
+
+ // Calculate %D (second smoothing)
+ return _slowD.Calc(new TValue(BarInput.Time, fastK, BarInput.IsNew));
+ }
+
+ ///
+ /// Gets the %K line value (Fast Stochastic)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double K() => _fastK.Value;
+
+ ///
+ /// Gets the %D line value (Slow Stochastic)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double D() => Value;
+}
diff --git a/lib/oscillators/Tsi.cs b/lib/oscillators/Tsi.cs
new file mode 100644
index 00000000..98f214ea
--- /dev/null
+++ b/lib/oscillators/Tsi.cs
@@ -0,0 +1,111 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// TSI: True Strength Index
+/// A momentum oscillator that shows both trend direction and overbought/oversold conditions.
+/// Uses two EMAs of price change momentum to help identify short-term trends and reversals.
+///
+///
+/// The TSI calculation process:
+/// 1. Calculate price change (PC): Current close - Previous close
+/// 2. Calculate absolute price change (APC): Absolute value of PC
+/// 3. First smoothing: EMA1 of PC and EMA1 of APC
+/// 4. Second smoothing: EMA2 of EMA1(PC) and EMA2 of EMA1(APC)
+/// 5. TSI = 100 * (Double smoothed PC / Double smoothed APC)
+///
+/// Key characteristics:
+/// - Oscillates around zero
+/// - Shows momentum and trend direction
+/// - Identifies overbought/oversold conditions
+/// - Generates signals through centerline/signal line crossovers
+/// - Shows momentum divergence with price
+///
+/// Formula:
+/// TSI = 100 * (EMA2(EMA1(PC)) / EMA2(EMA1(APC)))
+/// where:
+/// PC = Current Price - Previous Price
+/// APC = |PC|
+/// Default periods: First EMA = 25, Second EMA = 13
+///
+/// Sources:
+/// William Blau - "Momentum, Direction, and Divergence" (1995)
+/// https://www.investopedia.com/terms/t/tsi.asp
+///
+/// Note: Default periods (25,13) were recommended by Blau
+///
+[SkipLocalsInit]
+public sealed class Tsi : AbstractBase
+{
+ private readonly Ema _pcEma1;
+ private readonly Ema _pcEma2;
+ private readonly Ema _apcEma1;
+ private readonly Ema _apcEma2;
+ private double _prevPrice;
+ private const int DefaultFirstPeriod = 25;
+ private const int DefaultSecondPeriod = 13;
+ private const double ScalingFactor = 100.0;
+
+ /// The first EMA smoothing period (default 25).
+ /// The second EMA smoothing period (default 13).
+ /// Thrown when any period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Tsi(int firstPeriod = DefaultFirstPeriod, int secondPeriod = DefaultSecondPeriod)
+ {
+ if (firstPeriod < 1 || secondPeriod < 1)
+ throw new ArgumentOutOfRangeException(nameof(firstPeriod), "All periods must be greater than 0");
+
+ _pcEma1 = new(firstPeriod);
+ _pcEma2 = new(secondPeriod);
+ _apcEma1 = new(firstPeriod);
+ _apcEma2 = new(secondPeriod);
+ WarmupPeriod = firstPeriod + secondPeriod;
+ Name = $"TSI({firstPeriod},{secondPeriod})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The first EMA smoothing period.
+ /// The second EMA smoothing period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Tsi(object source, int firstPeriod = DefaultFirstPeriod, int secondPeriod = DefaultSecondPeriod)
+ : this(firstPeriod, secondPeriod)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new ValueSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ if (_index == 0)
+ _prevPrice = Input.Value;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+
+ // Calculate price changes
+ double priceChange = Input.Value - _prevPrice;
+ double absPriceChange = Math.Abs(priceChange);
+
+ if (Input.IsNew)
+ _prevPrice = Input.Value;
+
+ // First smoothing
+ double smoothPc = _pcEma1.Calc(new TValue(Input.Time, priceChange, Input.IsNew));
+ double smoothApc = _apcEma1.Calc(new TValue(Input.Time, absPriceChange, Input.IsNew));
+
+ // Second smoothing
+ double doubleSmoothedPc = _pcEma2.Calc(new TValue(Input.Time, smoothPc, Input.IsNew));
+ double doubleSmoothedApc = _apcEma2.Calc(new TValue(Input.Time, smoothApc, Input.IsNew));
+
+ // Calculate TSI
+ return doubleSmoothedApc >= double.Epsilon ? ScalingFactor * (doubleSmoothedPc / doubleSmoothedApc) : 0;
+ }
+}
diff --git a/lib/oscillators/Uo.cs b/lib/oscillators/Uo.cs
new file mode 100644
index 00000000..547d83d2
--- /dev/null
+++ b/lib/oscillators/Uo.cs
@@ -0,0 +1,179 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// UO: Ultimate Oscillator
+/// A momentum oscillator that uses three different time periods to reduce volatility
+/// and false signals. It incorporates a weighted average of three oscillator calculations
+/// using different periods.
+///
+///
+/// The UO calculation process:
+/// 1. Calculate buying pressure (BP): Close - Min(Low, Prior Close)
+/// 2. Calculate true range (TR): Max(High, Prior Close) - Min(Low, Prior Close)
+/// 3. Calculate average of BP/TR for each period
+/// 4. Apply weights to each period's average
+/// 5. Scale result to oscillator range
+///
+/// Key characteristics:
+/// - Oscillates between 0 and 100
+/// - Uses multiple timeframes to reduce false signals
+/// - Weighted sum of three periods
+/// - Traditional overbought level at 70
+/// - Traditional oversold level at 30
+///
+/// Formula:
+/// UO = 100 * ((4 * Average7) + (2 * Average14) + Average28) / (4 + 2 + 1)
+/// where:
+/// Average7 = 7-period average of BP/TR
+/// Average14 = 14-period average of BP/TR
+/// Average28 = 28-period average of BP/TR
+///
+/// Sources:
+/// Larry Williams - "New Trading Dimensions" (1998)
+/// https://www.investopedia.com/terms/u/ultimateoscillator.asp
+///
+/// Note: Default periods (7,14,28) and weights (4,2,1) were recommended by Williams
+///
+[SkipLocalsInit]
+public sealed class Uo : AbstractBase
+{
+ private readonly CircularBuffer _bp1;
+ private readonly CircularBuffer _tr1;
+ private readonly CircularBuffer _bp2;
+ private readonly CircularBuffer _tr2;
+ private readonly CircularBuffer _bp3;
+ private readonly CircularBuffer _tr3;
+ private readonly double _weight1;
+ private readonly double _weight2;
+ private readonly double _weight3;
+ private double _prevClose;
+ private const int DefaultPeriod1 = 7;
+ private const int DefaultPeriod2 = 14;
+ private const int DefaultPeriod3 = 28;
+ private const double DefaultWeight1 = 4.0;
+ private const double DefaultWeight2 = 2.0;
+ private const double DefaultWeight3 = 1.0;
+ private const double ScalingFactor = 100.0;
+
+ /// The first period (default 7).
+ /// The second period (default 14).
+ /// The third period (default 28).
+ /// Weight for first period (default 4).
+ /// Weight for second period (default 2).
+ /// Weight for third period (default 1).
+ /// Thrown when any period is less than 1 or any weight is less than or equal to 0.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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");
+ }
+
+ _weight1 = weight1;
+ _weight2 = weight2;
+ _weight3 = weight3;
+
+ _bp1 = new(period1);
+ _tr1 = new(period1);
+ _bp2 = new(period2);
+ _tr2 = new(period2);
+ _bp3 = new(period3);
+ _tr3 = new(period3);
+
+ WarmupPeriod = period3;
+ Name = $"UO({period1},{period2},{period3})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The first period.
+ /// The second period.
+ /// The third period.
+ /// Weight for first period.
+ /// Weight for second period.
+ /// Weight for third period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Uo(object source, int period1 = DefaultPeriod1, int period2 = DefaultPeriod2, int period3 = DefaultPeriod3,
+ double weight1 = DefaultWeight1, double weight2 = DefaultWeight2, double weight3 = DefaultWeight3)
+ : this(period1, period2, period3, weight1, weight2, weight3)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ if (_index == 0)
+ _prevClose = BarInput.Close;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private static double CalculateAverage(CircularBuffer bp, CircularBuffer tr)
+ {
+ double trSum = tr.Sum();
+ return trSum >= double.Epsilon ? bp.Sum() / trSum : 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Calculate buying pressure and true range
+ double minLowPrevClose = Math.Min(BarInput.Low, _prevClose);
+ double maxHighPrevClose = Math.Max(BarInput.High, _prevClose);
+ double bp = BarInput.Close - minLowPrevClose;
+ double tr = maxHighPrevClose - minLowPrevClose;
+
+ if (BarInput.IsNew)
+ {
+ // Add values to buffers
+ _bp1.Add(bp);
+ _tr1.Add(tr);
+ _bp2.Add(bp);
+ _tr2.Add(tr);
+ _bp3.Add(bp);
+ _tr3.Add(tr);
+ _prevClose = BarInput.Close;
+ }
+
+ // Not enough data
+ if (_index <= 1) return 0;
+
+ // Calculate averages for each period
+ double avg1 = CalculateAverage(_bp1, _tr1);
+ double avg2 = CalculateAverage(_bp2, _tr2);
+ double avg3 = CalculateAverage(_bp3, _tr3);
+
+ // Calculate weighted sum
+ double weightSum = _weight1 + _weight2 + _weight3;
+ return ScalingFactor * ((_weight1 * avg1 + _weight2 * avg2 + _weight3 * avg3) / weightSum);
+ }
+}
diff --git a/lib/oscillators/Willr.cs b/lib/oscillators/Willr.cs
new file mode 100644
index 00000000..cb0beb84
--- /dev/null
+++ b/lib/oscillators/Willr.cs
@@ -0,0 +1,86 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// WILLR: Williams %R
+/// A momentum oscillator that measures the level of the close relative to the
+/// highest high for a look-back period. Similar to Stochastic Oscillator but
+/// with a reversed scale and no smoothing.
+///
+///
+/// The Williams %R calculation process:
+/// 1. Find highest high and lowest low over period
+/// 2. Calculate where current close is within this range
+/// 3. Scale result to -100 to 0 range
+///
+/// Key characteristics:
+/// - Oscillates between -100 and 0
+/// - Similar to Stochastic but no smoothing
+/// - Traditional overbought level at -20
+/// - Traditional oversold level at -80
+/// - Leading indicator for market tops/bottoms
+///
+/// Formula:
+/// %R = -100 * (Highest High - Close) / (Highest High - Lowest Low)
+///
+/// Sources:
+/// Larry Williams - "How I Made One Million Dollars Last Year Trading Commodities" (1973)
+/// https://www.investopedia.com/terms/w/williamsr.asp
+///
+/// Note: Default period of 14 is commonly used
+///
+[SkipLocalsInit]
+public sealed class Willr : AbstractBase
+{
+ private readonly CircularBuffer _highs;
+ private readonly CircularBuffer _lows;
+ private const int DefaultPeriod = 14;
+ private const double ScalingFactor = -100.0;
+
+ /// The lookback period (default 14).
+ /// Thrown when period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Willr(int period = DefaultPeriod)
+ {
+ if (period < 1)
+ throw new ArgumentOutOfRangeException(nameof(period));
+
+ _highs = new(period);
+ _lows = new(period);
+ WarmupPeriod = period;
+ Name = $"WILLR({period})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The lookback period.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Willr(object source, int period = DefaultPeriod)
+ : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _highs.Add(BarInput.High);
+ _lows.Add(BarInput.Low);
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ double highest = _highs.Max();
+ double lowest = _lows.Min();
+ double range = highest - lowest;
+
+ return range >= double.Epsilon ? ScalingFactor * ((highest - BarInput.Close) / range) : 0;
+ }
+}
diff --git a/lib/oscillators/_list.md b/lib/oscillators/_list.md
index 718edbf5..0cd477ee 100644
--- a/lib/oscillators/_list.md
+++ b/lib/oscillators/_list.md
@@ -1,17 +1,17 @@
# Oscillators indicators
-Done: 11, Todo: 18
+Done: 20, Todo: 9
βοΈ AC - Acceleration Oscillator
βοΈ AO - Awesome Oscillator
-βοΈ *AROON - Aroon oscillator (Up, Down)
+βοΈ AROON - Aroon oscillator (Up, Down)
βοΈ BOP - Balance of Power
βοΈ CCI - Commodity Channel Index
βοΈ CFO - Chande Forcast Oscillator
-βοΈ CMO - Chande Momentum Oscillator
βοΈ CHOP - Choppiness Index
+βοΈ CMO - Chande Momentum Oscillator
βοΈ COG - Ehler's Center of Gravity
-COPPOCK - Coppock Curve
-CRSI - Connor RSI
+βοΈ COPPOCK - Coppock Curve
+βοΈ CRSI - Connor RSI
CTI - Ehler's Correlation Trend Indicator
DOSC - Derivative Oscillator
EFI - Elder Ray's Force Index
@@ -23,10 +23,10 @@ KRI - Kairi Relative Index
βοΈ RSI - Relative Strength Index
βοΈ RSX - Jurik Trend Strength Index
*RVGI - Relative Vigor Index (RVGI, Signal)
-SMI - Stochastic Momentum Index
-*SRSI - Stochastic RSI (SRSI, Signal)
-STC - Schaff Trend Cycle
-*STOCH - Stochastic Oscillator (%K, %D)
-TSI - True Strength Index
-UO - Ultimate Oscillator
-WILLR - Larry Williams' %R
+βοΈ SMI - Stochastic Momentum Index
+βοΈ SRSI - Stochastic RSI (SRSI, Signal)
+βοΈ STC - Schaff Trend Cycle
+βοΈ STOCH - Stochastic Oscillator (%K, %D)
+βοΈ TSI - True Strength Index
+βοΈ UO - Ultimate Oscillator
+βοΈ WILLR - Larry Williams' %R
diff --git a/lib/statistics/_list.md b/lib/statistics/_list.md
index fa7932fd..4e16b0de 100644
--- a/lib/statistics/_list.md
+++ b/lib/statistics/_list.md
@@ -5,8 +5,7 @@ Done: 13, Todo: 6
*CORR - Correlation Coefficient (Correlation, P-value)
βοΈ CURVATURE - Rate of Change in Direction or Slope
βοΈ ENTROPY - Measure of Uncertainty or Disorder
-HUBER - Huber Loss
-HURST - Hurst Exponent
+βοΈ HURST - Hurst Exponent
βοΈ KURTOSIS - Measure of Tails/Peakedness
βοΈ MAX - Maximum with exponential decay
βοΈ MEDIAN - Middle value
diff --git a/lib/volatility/Dchn.cs b/lib/volatility/Dchn.cs
new file mode 100644
index 00000000..2a5d5782
--- /dev/null
+++ b/lib/volatility/Dchn.cs
@@ -0,0 +1,100 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// DCHN: Donchian Channels
+/// A volatility indicator that identifies the highest high and lowest low
+/// over a specified period, creating a channel that contains price movement.
+///
+///
+/// The DCHN calculation process:
+/// 1. Track highest high over period
+/// 2. Track lowest low over period
+/// 3. Calculate midline as average of high and low
+/// 4. Updates with each new price bar
+///
+/// Key characteristics:
+/// - Trend following indicator
+/// - Support/resistance identification
+/// - Breakout detection
+/// - Volatility measurement
+/// - Range-based analysis
+///
+/// Formula:
+/// Upper = Highest High over period
+/// Lower = Lowest Low over period
+/// Middle = (Upper + Lower) / 2
+///
+/// Market Applications:
+/// - Trend identification
+/// - Support/resistance levels
+/// - Breakout trading
+/// - Volatility analysis
+/// - Range-bound trading
+///
+[SkipLocalsInit]
+public sealed class Dchn : AbstractBase
+{
+ private readonly CircularBuffer _highs;
+ private readonly CircularBuffer _lows;
+ private const int DefaultPeriod = 20;
+
+ /// The number of periods for DCHN calculation (default 20).
+ /// Thrown when period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Dchn(int period = DefaultPeriod)
+ {
+ if (period < 1)
+ throw new ArgumentOutOfRangeException(nameof(period));
+
+ _highs = new(period);
+ _lows = new(period);
+ WarmupPeriod = period;
+ Name = $"DCHN({period})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of periods for DCHN calculation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Dchn(object source, int period = DefaultPeriod) : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _highs.Add(BarInput.High);
+ _lows.Add(BarInput.Low);
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Calculate channel boundaries
+ double upper = _highs.Max();
+ double lower = _lows.Min();
+
+ // Return midline
+ return (upper + lower) / 2.0;
+ }
+
+ ///
+ /// Gets the upper channel value (highest high)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double Upper() => _highs.Max();
+
+ ///
+ /// Gets the lower channel value (lowest low)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double Lower() => _lows.Min();
+}
diff --git a/lib/volatility/Natr.cs b/lib/volatility/Natr.cs
new file mode 100644
index 00000000..d4a37d21
--- /dev/null
+++ b/lib/volatility/Natr.cs
@@ -0,0 +1,94 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// NATR: Normalized Average True Range
+/// A volatility indicator that expresses ATR as a percentage of closing price,
+/// making it more comparable across different price levels.
+///
+///
+/// The NATR calculation process:
+/// 1. Calculate True Range (TR):
+/// TR = max(high-low, abs(high-prevClose), abs(low-prevClose))
+/// 2. Calculate ATR using SMA of TR
+/// 3. Normalize by dividing ATR by close price and multiply by 100
+/// 4. Updates with each new price bar
+///
+/// Key characteristics:
+/// - Normalized volatility measure
+/// - Period-based average
+/// - Trend independent
+/// - Percentage-based measure
+/// - Comparable across instruments
+///
+/// Formula:
+/// TR = max(high-low, abs(high-prevClose), abs(low-prevClose))
+/// ATR = SMA(TR, period)
+/// NATR = (ATR / Close) * 100
+///
+/// Market Applications:
+/// - Cross-market comparison
+/// - Position sizing
+/// - Volatility analysis
+/// - Risk assessment
+/// - Market regime identification
+///
+/// Note: More suitable for comparing volatility across different instruments than ATR
+///
+[SkipLocalsInit]
+public sealed class Natr : AbstractBase
+{
+ private readonly Sma _ma;
+ private double _prevClose;
+ private const int DefaultPeriod = 14;
+
+ /// The number of periods for NATR calculation (default 14).
+ /// Thrown when period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Natr(int period = DefaultPeriod)
+ {
+ if (period < 1)
+ throw new ArgumentOutOfRangeException(nameof(period));
+
+ _ma = new(period);
+ WarmupPeriod = period;
+ Name = $"NATR({period})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of periods for NATR calculation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Natr(object source, int period = DefaultPeriod) : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _prevClose = BarInput.Close;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Calculate True Range
+ double hl = BarInput.High - BarInput.Low;
+ double hc = Math.Abs(BarInput.High - _prevClose);
+ double lc = Math.Abs(BarInput.Low - _prevClose);
+ double tr = Math.Max(hl, Math.Max(hc, lc));
+
+ // Calculate ATR
+ double atr = _ma.Calc(tr, BarInput.IsNew);
+
+ // Normalize ATR
+ return (atr / BarInput.Close) * 100.0;
+ }
+}
diff --git a/lib/volatility/Pch.cs b/lib/volatility/Pch.cs
new file mode 100644
index 00000000..476d3b15
--- /dev/null
+++ b/lib/volatility/Pch.cs
@@ -0,0 +1,102 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// PCH: Price Channel
+/// A volatility indicator that identifies the highest high and lowest low
+/// over a specified period, creating a channel that contains price movement.
+///
+///
+/// The PCH calculation process:
+/// 1. Track highest high over period
+/// 2. Track lowest low over period
+/// 3. Calculate midline as average of high and low
+/// 4. Updates with each new price bar
+///
+/// Key characteristics:
+/// - Trend following indicator
+/// - Support/resistance identification
+/// - Breakout detection
+/// - Volatility measurement
+/// - Range-based analysis
+///
+/// Formula:
+/// Upper = Highest High over period
+/// Lower = Lowest Low over period
+/// Middle = (Upper + Lower) / 2
+///
+/// Market Applications:
+/// - Trend identification
+/// - Support/resistance levels
+/// - Breakout trading
+/// - Volatility analysis
+/// - Range-bound trading
+///
+/// Note: Also known as Donchian Channels
+///
+[SkipLocalsInit]
+public sealed class Pch : AbstractBase
+{
+ private readonly CircularBuffer _highs;
+ private readonly CircularBuffer _lows;
+ private const int DefaultPeriod = 20;
+
+ /// The number of periods for PCH calculation (default 20).
+ /// Thrown when period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Pch(int period = DefaultPeriod)
+ {
+ if (period < 1)
+ throw new ArgumentOutOfRangeException(nameof(period));
+
+ _highs = new(period);
+ _lows = new(period);
+ WarmupPeriod = period;
+ Name = $"PCH({period})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of periods for PCH calculation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Pch(object source, int period = DefaultPeriod) : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _highs.Add(BarInput.High);
+ _lows.Add(BarInput.Low);
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(BarInput.IsNew);
+
+ // Calculate channel boundaries
+ double upper = _highs.Max();
+ double lower = _lows.Min();
+
+ // Return midline
+ return (upper + lower) / 2.0;
+ }
+
+ ///
+ /// Gets the upper channel value (highest high)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double Upper() => _highs.Max();
+
+ ///
+ /// Gets the lower channel value (lowest low)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double Lower() => _lows.Min();
+}
diff --git a/lib/volatility/Pv.cs b/lib/volatility/Pv.cs
new file mode 100644
index 00000000..97d5817b
--- /dev/null
+++ b/lib/volatility/Pv.cs
@@ -0,0 +1,93 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// PV: Parkinson Volatility
+/// A volatility measure that uses the high and low prices to estimate
+/// volatility, assuming continuous trading and log-normal price distribution.
+///
+///
+/// The PV calculation process:
+/// 1. Calculate squared log range for each period
+/// 2. Apply scaling factor (1/4ln2)
+/// 3. Average over specified period
+/// 4. Take square root for final volatility
+///
+/// Key characteristics:
+/// - Range-based volatility
+/// - More efficient than close-to-close
+/// - Assumes continuous trading
+/// - No gap consideration
+/// - Log-normal distribution
+///
+/// Formula:
+/// PV = sqrt(1/(4*ln(2)*n) * Ξ£(ln(High/Low))Β²)
+/// where n is the number of periods
+///
+/// Market Applications:
+/// - Volatility estimation
+/// - Risk assessment
+/// - Option pricing
+/// - Trading system development
+/// - Market regime identification
+///
+/// Note: More efficient than traditional volatility measures but sensitive to gaps
+///
+[SkipLocalsInit]
+public sealed class Pv : AbstractBase
+{
+ private readonly Sma _ma;
+ private readonly double _scaleFactor;
+ private const int DefaultPeriod = 10;
+ private double _prevValue;
+
+ /// The number of periods for PV calculation (default 10).
+ /// Thrown when period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Pv(int period = DefaultPeriod)
+ {
+ if (period < 1)
+ throw new ArgumentOutOfRangeException(nameof(period));
+
+ _ma = new(period);
+ _scaleFactor = 1.0 / (4.0 * Math.Log(2.0));
+ WarmupPeriod = period;
+ Name = $"PV({period})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of periods for PV calculation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Pv(object source, int period = DefaultPeriod) : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ _index++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ if (!BarInput.IsNew)
+ return _prevValue;
+
+ ManageState(true);
+
+ // Calculate log range squared
+ double logRange = Math.Log(BarInput.High / BarInput.Low);
+ double logRangeSquared = logRange * logRange;
+
+ // Apply moving average and scaling
+ double meanLogRangeSquared = _ma.Calc(logRangeSquared, true);
+
+ // Calculate final volatility
+ _prevValue = Math.Sqrt(_scaleFactor * meanLogRangeSquared);
+ return _prevValue;
+ }
+}
diff --git a/lib/volatility/Rsv.cs b/lib/volatility/Rsv.cs
new file mode 100644
index 00000000..5f12700b
--- /dev/null
+++ b/lib/volatility/Rsv.cs
@@ -0,0 +1,93 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// RSV: Rogers-Satchell Volatility
+/// A volatility measure that accounts for drift in the price process and
+/// is independent of the mean return level.
+///
+///
+/// The RSV calculation process:
+/// 1. Calculate log differences between prices
+/// 2. Combine log differences in specific way
+/// 3. Average over specified period
+/// 4. Take square root for final volatility
+///
+/// Key characteristics:
+/// - Drift-independent
+/// - Uses all price data (HLOC)
+/// - More efficient estimator
+/// - Handles trending markets
+/// - Non-zero mean returns
+///
+/// Formula:
+/// RSV = sqrt(mean(ln(H/C) * ln(H/O) + ln(L/C) * ln(L/O)))
+/// where H=High, L=Low, O=Open, C=Close
+///
+/// Market Applications:
+/// - Volatility estimation
+/// - Risk measurement
+/// - Option pricing
+/// - Trading system development
+/// - Market regime identification
+///
+/// Note: More robust than simple volatility measures in trending markets
+///
+[SkipLocalsInit]
+public sealed class Rsv : AbstractBase
+{
+ private readonly Sma _ma;
+ private const int DefaultPeriod = 10;
+ private double _prevValue;
+
+ /// The number of periods for RSV calculation (default 10).
+ /// Thrown when period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rsv(int period = DefaultPeriod)
+ {
+ if (period < 1)
+ throw new ArgumentOutOfRangeException(nameof(period));
+
+ _ma = new(period);
+ WarmupPeriod = period;
+ Name = $"RSV({period})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of periods for RSV calculation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rsv(object source, int period = DefaultPeriod) : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ _index++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ if (!BarInput.IsNew)
+ return _prevValue;
+
+ ManageState(true);
+
+ // Calculate log ratios
+ double lnHC = Math.Log(BarInput.High / BarInput.Close);
+ double lnHO = Math.Log(BarInput.High / BarInput.Open);
+ double lnLC = Math.Log(BarInput.Low / BarInput.Close);
+ double lnLO = Math.Log(BarInput.Low / BarInput.Open);
+
+ // Calculate Rogers-Satchell term
+ double rs = lnHC * lnHO + lnLC * lnLO;
+
+ // Apply moving average and take square root
+ _prevValue = Math.Sqrt(_ma.Calc(rs, true));
+ return _prevValue;
+ }
+}
diff --git a/lib/volatility/Sv.cs b/lib/volatility/Sv.cs
new file mode 100644
index 00000000..de0e95fc
--- /dev/null
+++ b/lib/volatility/Sv.cs
@@ -0,0 +1,105 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// SV: Stochastic Volatility
+/// A volatility measure that models price volatility as a random process,
+/// capturing both the magnitude and the rate of change in price movements.
+///
+///
+/// The SV calculation process:
+/// 1. Calculate log returns
+/// 2. Compute exponentially weighted variance
+/// 3. Apply smoothing to variance estimate
+/// 4. Take square root for volatility
+///
+/// Key characteristics:
+/// - Time-varying volatility
+/// - Mean-reverting process
+/// - Captures volatility clustering
+/// - Handles leverage effects
+/// - Accounts for fat tails
+///
+/// Formula:
+/// Returns = ln(Close/PrevClose)
+/// Variance = Ξ» * PrevVariance + (1-Ξ») * ReturnsΒ²
+/// SV = sqrt(Variance)
+/// where Ξ» is the decay factor
+///
+/// Market Applications:
+/// - Option pricing
+/// - Risk management
+/// - Trading strategies
+/// - Portfolio optimization
+/// - Market regime detection
+///
+/// Note: More sophisticated than simple volatility measures, better captures market dynamics
+///
+[SkipLocalsInit]
+public sealed class Sv : AbstractBase
+{
+ private readonly double _lambda;
+ private readonly Sma _ma;
+ private double _prevClose;
+ private double _prevVariance;
+ private double _prevValue;
+ private const int DefaultPeriod = 20;
+ private const double DefaultLambda = 0.94;
+
+ /// The number of periods for smoothing (default 20).
+ /// The decay factor for variance calculation (default 0.94).
+ /// Thrown when period is less than 1 or lambda is not between 0 and 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Sv(int period = DefaultPeriod, double lambda = DefaultLambda)
+ {
+ if (period < 1)
+ throw new ArgumentOutOfRangeException(nameof(period));
+ if (lambda <= 0 || lambda >= 1)
+ throw new ArgumentOutOfRangeException(nameof(lambda));
+
+ _lambda = lambda;
+ _ma = new(period);
+ WarmupPeriod = period;
+ Name = $"SV({period},{lambda:F2})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of periods for smoothing.
+ /// The decay factor for variance calculation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Sv(object source, int period = DefaultPeriod, double lambda = DefaultLambda) : this(period, lambda)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _prevClose = BarInput.Close;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ if (!BarInput.IsNew)
+ return _prevValue;
+
+ ManageState(true);
+
+ // Calculate log return
+ double logReturn = Math.Log(BarInput.Close / _prevClose);
+ double squaredReturn = logReturn * logReturn;
+
+ // Update variance estimate
+ _prevVariance = _lambda * _prevVariance + (1.0 - _lambda) * squaredReturn;
+
+ // Apply smoothing and take square root
+ _prevValue = Math.Sqrt(_ma.Calc(_prevVariance, true));
+ return _prevValue;
+ }
+}
diff --git a/lib/volatility/Yzv.cs b/lib/volatility/Yzv.cs
new file mode 100644
index 00000000..6341ee52
--- /dev/null
+++ b/lib/volatility/Yzv.cs
@@ -0,0 +1,113 @@
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+///
+/// YZV: Yang-Zhang Volatility
+/// A volatility estimator that combines overnight and trading volatilities,
+/// providing a more complete picture of price variation while being drift-independent.
+///
+///
+/// The YZV calculation process:
+/// 1. Calculate overnight (close-to-open) volatility
+/// 2. Calculate open-to-close volatility
+/// 3. Calculate Rogers-Satchell volatility
+/// 4. Combine components with optimal weights
+///
+/// Key characteristics:
+/// - Drift independence
+/// - Minimum variance
+/// - Handles overnight gaps
+/// - Uses all HLOC prices
+/// - Optimal weighting
+///
+/// Formula:
+/// YZV = sqrt(Vo + k*Vc + (1-k)*Vrs)
+/// where:
+/// Vo = overnight volatility
+/// Vc = open-to-close volatility
+/// Vrs = Rogers-Satchell volatility
+/// k β 0.34 (optimal weight)
+///
+/// Market Applications:
+/// - Option pricing
+/// - Risk measurement
+/// - Trading systems
+/// - Portfolio management
+/// - Market analysis
+///
+/// Note: Most efficient unbiased estimator among drift-independent estimators
+///
+[SkipLocalsInit]
+public sealed class Yzv : AbstractBase
+{
+ private readonly Sma _maCo; // Close-to-Open
+ private readonly Sma _maOc; // Open-to-Close
+ private readonly Sma _maRs; // Rogers-Satchell
+ private double _prevClose;
+ private double _prevValue;
+ private const double K = 0.34; // Optimal weight
+ private const int DefaultPeriod = 20;
+
+ /// The number of periods for volatility calculation (default 20).
+ /// Thrown when period is less than 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Yzv(int period = DefaultPeriod)
+ {
+ if (period < 1)
+ throw new ArgumentOutOfRangeException(nameof(period));
+
+ _maCo = new(period);
+ _maOc = new(period);
+ _maRs = new(period);
+ WarmupPeriod = period;
+ Name = $"YZV({period})";
+ }
+
+ /// The data source object that publishes updates.
+ /// The number of periods for volatility calculation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Yzv(object source, int period = DefaultPeriod) : this(period)
+ {
+ var pubEvent = source.GetType().GetEvent("Pub");
+ pubEvent?.AddEventHandler(source, new BarSignal(Sub));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew)
+ {
+ _prevClose = BarInput.Close;
+ _index++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ if (!BarInput.IsNew)
+ return _prevValue;
+
+ ManageState(true);
+
+ // Calculate overnight volatility (close-to-open)
+ double co = Math.Log(BarInput.Open / _prevClose);
+ double vo = _maCo.Calc(co * co, true);
+
+ // Calculate open-to-close volatility
+ double oc = Math.Log(BarInput.Close / BarInput.Open);
+ double vc = _maOc.Calc(oc * oc, true);
+
+ // Calculate Rogers-Satchell volatility component
+ double lnHC = Math.Log(BarInput.High / BarInput.Close);
+ double lnHO = Math.Log(BarInput.High / BarInput.Open);
+ double lnLC = Math.Log(BarInput.Low / BarInput.Close);
+ double lnLO = Math.Log(BarInput.Low / BarInput.Open);
+ double rs = lnHC * lnHO + lnLC * lnLO;
+ double vrs = _maRs.Calc(rs, true);
+
+ // Combine components with optimal weights
+ _prevValue = Math.Sqrt(vo + K * vc + (1.0 - K) * vrs);
+ return _prevValue;
+ }
+}
diff --git a/lib/volatility/_list.md b/lib/volatility/_list.md
index 9e0f4094..59fa8825 100644
--- a/lib/volatility/_list.md
+++ b/lib/volatility/_list.md
@@ -1,5 +1,5 @@
# Volatility indicators
-Done: 24, Todo: 11
+Done: 25, Todo: 10
βοΈ ADR - Average Daily Range
βοΈ AP - Andrew's Pitchfork
@@ -11,28 +11,28 @@ Done: 24, Todo: 11
βοΈ CE - Chandelier Exit
βοΈ CV - Conditional Volatility (ARCH/GARCH)
βοΈ CVI - Chaikin's Volatility
-*DC - Donchian Channels (Upper, Middle, Lower)
+βοΈ DCHN - Donchian Channels (Upper, Middle, Lower)
βοΈ EWMA - Exponential Weighted Moving Average Volatility
βοΈ FCB - Fractal Chaos Bands
βοΈ GKV - Garman-Klass Volatility
βοΈ HLV - High-Low Volatility
βοΈ HV - Historical Volatility
*ICH - Ichimoku Cloud (Conversion, Base, Leading Span A, Leading Span B, Lagging Span)
-βοΈ JVOLTY - Jurik Volatility
+βοΈ *JVOLTY - Jurik Volatility (Jvolty, Upper band, Lower band)
*KC - Keltner Channels (Upper, Middle, Lower)
-NATR - Normalized Average True Range
-PCH - Price Channel Indicator
+βοΈ NATR - Normalized Average True Range
+βοΈ PCH - Price Channel Indicator
*PSAR - Parabolic Stop and Reverse (Value, Trend)
-PV - Parkinson Volatility
-RSV - Rogers-Satchell Volatility
+βοΈ PV - Parkinson Volatility
+βοΈ RSV - Rogers-Satchell Volatility
βοΈ RV - Realized Volatility
βοΈ RVI - Relative Volatility Index
*STARC - Starc Bands (Upper, Middle, Lower)
-SV - Stochastic Volatility
+βοΈ SV - Stochastic Volatility
βοΈ TR - True Range
βοΈ UI - Ulcer Index
βοΈ *VC - Volatility Cone (Mean, Upper Bound, Lower Bound)
βοΈ VOV - Volatility of Volatility
βοΈ VR - Volatility Ratio
βοΈ *VS - Volatility Stop (Long Stop, Short Stop)
-YZV - Yang-Zhang Volatility
+βοΈ YZV - Yang-Zhang Volatility