mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
Merge branch 'dev'
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,16 +22,7 @@ public class StatisticsUpdateTests
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
double close = low + ((high - low) * GetRandomDouble());
|
||||
return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`|
|
||||
|
||||
@@ -59,110 +59,3 @@ public readonly record struct TBar(DateTime Time, double Open, double High, doub
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]";
|
||||
}
|
||||
|
||||
public delegate void BarSignal(object source, in TBarEventArgs args);
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class TBarEventArgs : EventArgs
|
||||
{
|
||||
public readonly TBar Bar;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TBarEventArgs(TBar bar) => Bar = bar;
|
||||
}
|
||||
|
||||
[SkipLocalsInit]
|
||||
public class TBarSeries : List<TBar>
|
||||
{
|
||||
private static readonly TBar Default = new(DateTime.MinValue, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN);
|
||||
|
||||
public readonly TSeries Open;
|
||||
public readonly TSeries High;
|
||||
public readonly TSeries Low;
|
||||
public readonly TSeries Close;
|
||||
public readonly TSeries Volume;
|
||||
|
||||
public TBar Last => Count > 0 ? this[^1] : Default;
|
||||
public TBar First => Count > 0 ? this[0] : Default;
|
||||
public int Length => Count;
|
||||
public string Name { get; set; }
|
||||
public event BarSignal Pub = delegate { };
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TBarSeries()
|
||||
{
|
||||
Name = "Bar";
|
||||
Open = new TSeries();
|
||||
High = new TSeries();
|
||||
Low = new TSeries();
|
||||
Close = new TSeries();
|
||||
Volume = new TSeries();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TBarSeries(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new virtual void Add(TBar bar)
|
||||
{
|
||||
if (bar.IsNew || base.Count == 0)
|
||||
{
|
||||
base.Add(bar);
|
||||
}
|
||||
else
|
||||
{
|
||||
this[^1] = bar;
|
||||
}
|
||||
|
||||
Pub?.Invoke(this, new TBarEventArgs(bar));
|
||||
|
||||
Open.Add(bar.Time, bar.Open, IsNew: bar.IsNew, IsHot: true);
|
||||
High.Add(bar.Time, bar.High, IsNew: bar.IsNew, IsHot: true);
|
||||
Low.Add(bar.Time, bar.Low, IsNew: bar.IsNew, IsHot: true);
|
||||
Close.Add(bar.Time, bar.Close, IsNew: bar.IsNew, IsHot: true);
|
||||
Volume.Add(bar.Time, bar.Volume, IsNew: bar.IsNew, IsHot: true);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) =>
|
||||
Add(new TBar(Time, Open, High, Low, Close, Volume, IsNew));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) =>
|
||||
Add(new TBar(DateTime.Now, Open, High, Low, Close, Volume, IsNew));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(TBarSeries series)
|
||||
{
|
||||
if (series == this)
|
||||
{
|
||||
// If adding itself, create a copy to avoid modification during enumeration
|
||||
var copy = new TBarSeries { Name = Name };
|
||||
copy.AddRange(this);
|
||||
AddRange(copy);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddRange(series);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new virtual void AddRange(IEnumerable<TBar> collection)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Sub(object source, in TBarEventArgs args)
|
||||
{
|
||||
Add(args.Bar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
public delegate void BarSignal(object source, in TBarEventArgs args);
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class TBarEventArgs : EventArgs
|
||||
{
|
||||
public readonly TBar Bar;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TBarEventArgs(TBar bar) => Bar = bar;
|
||||
}
|
||||
|
||||
[SkipLocalsInit]
|
||||
public class TBarSeries : List<TBar>
|
||||
{
|
||||
private static readonly TBar Default = new(DateTime.MinValue, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN);
|
||||
|
||||
public TSeries Open { get; init; }
|
||||
public TSeries High { get; init; }
|
||||
public TSeries Low { get; init; }
|
||||
public TSeries Close { get; init; }
|
||||
public TSeries Volume { get; init; }
|
||||
|
||||
public TBar Last => Count > 0 ? this[^1] : Default;
|
||||
public TBar First => Count > 0 ? this[0] : Default;
|
||||
public int Length => Count;
|
||||
public string Name { get; set; }
|
||||
public event BarSignal Pub = delegate { };
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TBarSeries()
|
||||
{
|
||||
Name = "Bar";
|
||||
Open = new TSeries();
|
||||
High = new TSeries();
|
||||
Low = new TSeries();
|
||||
Close = new TSeries();
|
||||
Volume = new TSeries();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TBarSeries(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
pubEvent?.AddEventHandler(source, new BarSignal(Sub));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new virtual void Add(TBar bar)
|
||||
{
|
||||
if (bar.IsNew || base.Count == 0)
|
||||
{
|
||||
base.Add(bar);
|
||||
}
|
||||
else
|
||||
{
|
||||
this[^1] = bar;
|
||||
}
|
||||
|
||||
Pub?.Invoke(this, new TBarEventArgs(bar));
|
||||
|
||||
Open.Add(bar.Time, bar.Open, IsNew: bar.IsNew, IsHot: true);
|
||||
High.Add(bar.Time, bar.High, IsNew: bar.IsNew, IsHot: true);
|
||||
Low.Add(bar.Time, bar.Low, IsNew: bar.IsNew, IsHot: true);
|
||||
Close.Add(bar.Time, bar.Close, IsNew: bar.IsNew, IsHot: true);
|
||||
Volume.Add(bar.Time, bar.Volume, IsNew: bar.IsNew, IsHot: true);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) =>
|
||||
Add(new TBar(Time, Open, High, Low, Close, Volume, IsNew));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) =>
|
||||
Add(new TBar(DateTime.Now, Open, High, Low, Close, Volume, IsNew));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(TBarSeries series)
|
||||
{
|
||||
if (series == this)
|
||||
{
|
||||
// If adding itself, create a copy to avoid modification during enumeration
|
||||
var copy = new TBarSeries { Name = Name };
|
||||
copy.AddRange(this);
|
||||
AddRange(copy);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddRange(series);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new virtual void AddRange(IEnumerable<TBar> collection)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Sub(object source, in TBarEventArgs args)
|
||||
{
|
||||
Add(args.Bar);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace QuanTAlib;
|
||||
|
||||
public delegate void ValueSignal(object source, in ValueEventArgs args);
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class ValueEventArgs : EventArgs
|
||||
{
|
||||
public readonly TValue Tick;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueEventArgs(TValue value) => Tick = value;
|
||||
}
|
||||
|
||||
[SkipLocalsInit]
|
||||
public class TSeries : List<TValue>
|
||||
{
|
||||
private static readonly TValue Default = new(DateTime.MinValue, double.NaN);
|
||||
|
||||
public IEnumerable<DateTime> t => this.Select(item => item.t);
|
||||
public IEnumerable<double> v => this.Select(item => item.v);
|
||||
public TValue Last => Count > 0 ? this[^1] : Default;
|
||||
public TValue First => Count > 0 ? this[0] : Default;
|
||||
public int Length => Count;
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event that publishes value updates to subscribers. This event is used in the pub/sub pattern
|
||||
/// where TSeries instances can subscribe to updates from other data sources through the Sub method,
|
||||
/// and publish their own updates to downstream subscribers.
|
||||
/// </summary>
|
||||
[SuppressMessage("Minor Code Smell", "S3264:Events should be invoked", Justification = "Event is invoked through delegate")]
|
||||
public event ValueSignal Pub = delegate { };
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TSeries()
|
||||
{
|
||||
Name = "Data";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TSeries(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
if (pubEvent != null)
|
||||
{
|
||||
pubEvent.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator List<double>(TSeries series) => series.Select(item => item.Value).ToList();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator double[](TSeries series) => series.Select(item => item.Value).ToArray();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new virtual void Add(TValue tick)
|
||||
{
|
||||
if (tick.IsNew || base.Count == 0)
|
||||
{
|
||||
base.Add(tick);
|
||||
}
|
||||
else
|
||||
{
|
||||
this[^1] = tick;
|
||||
}
|
||||
Pub?.Invoke(this, new ValueEventArgs(tick));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void Add(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) =>
|
||||
Add(new TValue(Time, Value, IsNew, IsHot));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void Add(double Value, bool IsNew = true, bool IsHot = true) =>
|
||||
Add(new TValue(DateTime.UtcNow, Value, IsNew, IsHot));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(IEnumerable<double> values)
|
||||
{
|
||||
var valueList = values.ToList();
|
||||
int count = valueList.Count;
|
||||
DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Add(startTime, valueList[i]);
|
||||
startTime = startTime.AddHours(1);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(TSeries series)
|
||||
{
|
||||
if (series == this)
|
||||
{
|
||||
// If adding itself, create a copy to avoid modification during enumeration
|
||||
var copy = new TSeries { Name = Name };
|
||||
copy.AddRange(this);
|
||||
AddRange(copy);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddRange(series);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new virtual void AddRange(IEnumerable<TValue> collection)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Sub(object source, in ValueEventArgs args) => Add(args.Tick);
|
||||
}
|
||||
@@ -39,114 +39,3 @@ public readonly record struct TValue(DateTime Time, double Value, bool IsNew = t
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}, {Value:F2}, IsNew: {IsNew}, IsHot: {IsHot}]";
|
||||
}
|
||||
|
||||
public delegate void ValueSignal(object source, in ValueEventArgs args);
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class ValueEventArgs : EventArgs
|
||||
{
|
||||
public readonly TValue Tick;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueEventArgs(TValue value) => Tick = value;
|
||||
}
|
||||
|
||||
[SkipLocalsInit]
|
||||
public class TSeries : List<TValue>
|
||||
{
|
||||
private static readonly TValue Default = new(DateTime.MinValue, double.NaN);
|
||||
|
||||
public IEnumerable<DateTime> t => this.Select(item => item.t);
|
||||
public IEnumerable<double> v => this.Select(item => item.v);
|
||||
public TValue Last => Count > 0 ? this[^1] : Default;
|
||||
public TValue First => Count > 0 ? this[0] : Default;
|
||||
public int Length => Count;
|
||||
public string Name { get; set; }
|
||||
public event ValueSignal Pub = delegate { };
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TSeries()
|
||||
{
|
||||
Name = "Data";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TSeries(object source) : this()
|
||||
{
|
||||
var pubEvent = source.GetType().GetEvent("Pub");
|
||||
if (pubEvent != null)
|
||||
{
|
||||
pubEvent.AddEventHandler(source, new ValueSignal(Sub));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator List<double>(TSeries series) => series.Select(item => item.Value).ToList();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator double[](TSeries series) => series.Select(item => item.Value).ToArray();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new virtual void Add(TValue tick)
|
||||
{
|
||||
if (tick.IsNew || base.Count == 0)
|
||||
{
|
||||
base.Add(tick);
|
||||
}
|
||||
else
|
||||
{
|
||||
this[^1] = tick;
|
||||
}
|
||||
Pub?.Invoke(this, new ValueEventArgs(tick));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void Add(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) =>
|
||||
Add(new TValue(Time, Value, IsNew, IsHot));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void Add(double Value, bool IsNew = true, bool IsHot = true) =>
|
||||
Add(new TValue(DateTime.UtcNow, Value, IsNew, IsHot));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(IEnumerable<double> values)
|
||||
{
|
||||
var valueList = values.ToList();
|
||||
int count = valueList.Count;
|
||||
DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Add(startTime, valueList[i]);
|
||||
startTime = startTime.AddHours(1);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(TSeries series)
|
||||
{
|
||||
if (series == this)
|
||||
{
|
||||
// If adding itself, create a copy to avoid modification during enumeration
|
||||
var copy = new TSeries { Name = Name };
|
||||
copy.AddRange(this);
|
||||
AddRange(copy);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddRange(series);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public new virtual void AddRange(IEnumerable<TValue> collection)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Sub(object source, in ValueEventArgs args) => Add(args.Tick);
|
||||
}
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// TSI: True Strength Index
|
||||
/// A momentum indicator that shows both trend direction and overbought/oversold conditions
|
||||
/// by using two smoothing steps on price changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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();
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="roc1Period">The first ROC period (default 14).</param>
|
||||
/// <param name="roc2Period">The second ROC period (default 11).</param>
|
||||
/// <param name="wmaPeriod">The WMA smoothing period (default 10).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="roc1Period">The first ROC period.</param>
|
||||
/// <param name="roc2Period">The second ROC period.</param>
|
||||
/// <param name="wmaPeriod">The WMA smoothing period.</param>
|
||||
[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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="period1">The first RSI period (default 3).</param>
|
||||
/// <param name="period2">The second RSI period (default 2).</param>
|
||||
/// <param name="period3">The third RSI period (default 1).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period1">The first RSI period.</param>
|
||||
/// <param name="period2">The second RSI period.</param>
|
||||
/// <param name="period3">The third RSI period.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="period">The lookback period (default 10).</param>
|
||||
/// <param name="smooth1">First smoothing period (default 3).</param>
|
||||
/// <param name="smooth2">Second smoothing period (default 3).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The lookback period.</param>
|
||||
/// <param name="smooth1">First smoothing period.</param>
|
||||
/// <param name="smooth2">Second smoothing period.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="rsiPeriod">The RSI period (default 14).</param>
|
||||
/// <param name="stochPeriod">The Stochastic period (default 14).</param>
|
||||
/// <param name="smoothK">K line smoothing period (default 3).</param>
|
||||
/// <param name="smoothD">D line smoothing period (default 3).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="rsiPeriod">The RSI period.</param>
|
||||
/// <param name="stochPeriod">The Stochastic period.</param>
|
||||
/// <param name="smoothK">K line smoothing period.</param>
|
||||
/// <param name="smoothD">D line smoothing period.</param>
|
||||
[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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the K line value (raw Stochastic RSI)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double K() => _srsiValues[0];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the D line value (signal line)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double D() => Value;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// STC: Schaff Trend Cycle
|
||||
/// A trend-following indicator that combines MACD and stochastic concepts
|
||||
/// to create a smoother, more responsive indicator with less noise.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="cyclePeriod">The lookback period for highs/lows (default 10).</param>
|
||||
/// <param name="fastPeriod">Fast EMA period (default 23).</param>
|
||||
/// <param name="slowPeriod">Slow EMA period (default 50).</param>
|
||||
/// <param name="d1Period">First %D smoothing period (default 3).</param>
|
||||
/// <param name="stcPeriod">Final STC smoothing period (default 3).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="cyclePeriod">The lookback period for highs/lows.</param>
|
||||
/// <param name="fastPeriod">Fast EMA period.</param>
|
||||
/// <param name="slowPeriod">Slow EMA period.</param>
|
||||
/// <param name="d1Period">First %D smoothing period.</param>
|
||||
/// <param name="stcPeriod">Final STC smoothing period.</param>
|
||||
[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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="period">The lookback period (default 14).</param>
|
||||
/// <param name="smoothK">%K smoothing period (default 3).</param>
|
||||
/// <param name="smoothD">%D smoothing period (default 3).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The lookback period.</param>
|
||||
/// <param name="smoothK">%K smoothing period.</param>
|
||||
/// <param name="smoothD">%D smoothing period.</param>
|
||||
[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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the %K line value (Fast Stochastic)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double K() => _fastK.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the %D line value (Slow Stochastic)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double D() => Value;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="firstPeriod">The first EMA smoothing period (default 25).</param>
|
||||
/// <param name="secondPeriod">The second EMA smoothing period (default 13).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="firstPeriod">The first EMA smoothing period.</param>
|
||||
/// <param name="secondPeriod">The second EMA smoothing period.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="period1">The first period (default 7).</param>
|
||||
/// <param name="period2">The second period (default 14).</param>
|
||||
/// <param name="period3">The third period (default 28).</param>
|
||||
/// <param name="weight1">Weight for first period (default 4).</param>
|
||||
/// <param name="weight2">Weight for second period (default 2).</param>
|
||||
/// <param name="weight3">Weight for third period (default 1).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any period is less than 1 or any weight is less than or equal to 0.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period1">The first period.</param>
|
||||
/// <param name="period2">The second period.</param>
|
||||
/// <param name="period3">The third period.</param>
|
||||
/// <param name="weight1">Weight for first period.</param>
|
||||
/// <param name="weight2">Weight for second period.</param>
|
||||
/// <param name="weight3">Weight for third period.</param>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="period">The lookback period (default 14).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The lookback period.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -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
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns a value between 0 and 1
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Hurst : AbstractBase
|
||||
{
|
||||
@@ -184,7 +183,7 @@ public sealed class Hurst : AbstractBase
|
||||
double hurst = 0.5; // Default to random walk
|
||||
if (numPoints > 1)
|
||||
{
|
||||
double slope = (numPoints * sumXY - sumX * sumY) / (numPoints * sumX2 - sumX * sumX);
|
||||
double slope = ((numPoints * sumXY) - (sumX * sumY)) / ((numPoints * sumX2) - (sumX * sumX));
|
||||
hurst = Math.Max(0, Math.Min(1, slope)); // Clamp between 0 and 1
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -41,7 +41,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns three values: upper, middle, and lower bands
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Bband : AbstractBase
|
||||
{
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns annualized volatility as a percentage
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ccv : AbstractBase
|
||||
{
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns two values: long exit and short exit levels
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ce : AbstractBase
|
||||
{
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns annualized volatility as a percentage
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Cv : AbstractBase
|
||||
{
|
||||
@@ -126,7 +125,7 @@ public sealed class Cv : AbstractBase
|
||||
}
|
||||
|
||||
// Update variance estimate using GARCH(1,1)
|
||||
double variance = _omega + _alpha * squaredReturn + _beta * _prevVariance;
|
||||
double variance = _omega + (_alpha * squaredReturn) + (_beta * _prevVariance);
|
||||
_prevVariance = variance;
|
||||
|
||||
// Calculate annualized volatility as percentage
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Dchn : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _highs;
|
||||
private readonly CircularBuffer _lows;
|
||||
private const int DefaultPeriod = 20;
|
||||
|
||||
/// <param name="period">The number of periods for DCHN calculation (default 20).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods for DCHN calculation.</param>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the upper channel value (highest high)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double Upper() => _highs.Max();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lower channel value (lowest low)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double Lower() => _lows.Min();
|
||||
}
|
||||
@@ -41,7 +41,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns annualized volatility as a percentage
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Ewma : AbstractBase
|
||||
{
|
||||
@@ -121,7 +120,7 @@ public sealed class Ewma : AbstractBase
|
||||
}
|
||||
|
||||
// Update EWMA
|
||||
_ewma = _lambda * _ewma + (1 - _lambda) * squaredReturn;
|
||||
_ewma = (_lambda * _ewma) + ((1 - _lambda) * squaredReturn);
|
||||
|
||||
// Calculate volatility
|
||||
double volatility = Math.Sqrt(_ewma);
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns three values: upper, middle, and lower bands
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Fcb : AbstractBase
|
||||
{
|
||||
@@ -134,8 +133,8 @@ public sealed class Fcb : AbstractBase
|
||||
}
|
||||
|
||||
// Apply smoothing to bands
|
||||
_upperBand = _smoothing * _upperEma + (1 - _smoothing) * BarInput.High;
|
||||
_lowerBand = _smoothing * _lowerEma + (1 - _smoothing) * BarInput.Low;
|
||||
_upperBand = (_smoothing * _upperEma) + ((1 - _smoothing) * BarInput.High);
|
||||
_lowerBand = (_smoothing * _lowerEma) + ((1 - _smoothing) * BarInput.Low);
|
||||
_middleBand = (_upperBand + _lowerBand) / 2;
|
||||
|
||||
IsHot = _index >= WarmupPeriod;
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns annualized volatility as a percentage
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Gkv : AbstractBase
|
||||
{
|
||||
@@ -97,7 +96,7 @@ public sealed class Gkv : AbstractBase
|
||||
c = c * c;
|
||||
|
||||
// Combine components with optimal weights
|
||||
double component = 0.5 * u - (2 * _ln2 - 1) * c;
|
||||
double component = (0.5 * u) - (((2 * _ln2) - 1) * c);
|
||||
_components.Add(component);
|
||||
|
||||
// Need enough values for calculation
|
||||
|
||||
@@ -37,7 +37,6 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// Note: Returns annualized volatility as a percentage
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Hlv : AbstractBase
|
||||
{
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Natr : AbstractBase
|
||||
{
|
||||
private readonly Sma _ma;
|
||||
private double _prevClose;
|
||||
private const int DefaultPeriod = 14;
|
||||
|
||||
/// <param name="period">The number of periods for NATR calculation (default 14).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Natr(int period = DefaultPeriod)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
|
||||
_ma = new(period);
|
||||
WarmupPeriod = period;
|
||||
Name = $"NATR({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods for NATR calculation.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Pch : AbstractBase
|
||||
{
|
||||
private readonly CircularBuffer _highs;
|
||||
private readonly CircularBuffer _lows;
|
||||
private const int DefaultPeriod = 20;
|
||||
|
||||
/// <param name="period">The number of periods for PCH calculation (default 20).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods for PCH calculation.</param>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the upper channel value (highest high)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double Upper() => _highs.Max();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lower channel value (lowest low)
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public double Lower() => _lows.Min();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// PV: Parkinson Volatility
|
||||
/// A volatility measure that uses the high and low prices to estimate
|
||||
/// volatility, assuming continuous trading and log-normal price distribution.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Pv : AbstractBase
|
||||
{
|
||||
private readonly Sma _ma;
|
||||
private readonly double _scaleFactor;
|
||||
private const int DefaultPeriod = 10;
|
||||
private double _prevValue;
|
||||
|
||||
/// <param name="period">The number of periods for PV calculation (default 10).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods for PV calculation.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// RSV: Rogers-Satchell Volatility
|
||||
/// A volatility measure that accounts for drift in the price process and
|
||||
/// is independent of the mean return level.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Rsv : AbstractBase
|
||||
{
|
||||
private readonly Sma _ma;
|
||||
private const int DefaultPeriod = 10;
|
||||
private double _prevValue;
|
||||
|
||||
/// <param name="period">The number of periods for RSV calculation (default 10).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Rsv(int period = DefaultPeriod)
|
||||
{
|
||||
if (period < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(period));
|
||||
|
||||
_ma = new(period);
|
||||
WarmupPeriod = period;
|
||||
Name = $"RSV({period})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods for RSV calculation.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="period">The number of periods for smoothing (default 20).</param>
|
||||
/// <param name="lambda">The decay factor for variance calculation (default 0.94).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1 or lambda is not between 0 and 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods for smoothing.</param>
|
||||
/// <param name="lambda">The decay factor for variance calculation.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
[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;
|
||||
|
||||
/// <param name="period">The number of periods for volatility calculation (default 20).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods for volatility calculation.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user