Merge branch 'dev'

This commit is contained in:
Miha Kralj
2024-11-03 18:27:28 -08:00
37 changed files with 2339 additions and 444 deletions
+130 -12
View File
@@ -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);
}
}
+1 -10
View File
@@ -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);
}
+112
View File
@@ -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);
}
}
+13 -16
View File
@@ -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`|
-107
View File
@@ -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);
}
}
+110
View File
@@ -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);
}
}
+122
View File
@@ -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);
}
-111
View File
@@ -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);
}
-150
View File
@@ -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;
}
}
+118
View File
@@ -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));
}
}
+97
View File
@@ -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;
}
}
+121
View File
@@ -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;
}
}
+145
View File
@@ -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;
}
+140
View File
@@ -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));
}
}
+126
View File
@@ -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;
}
+111
View File
@@ -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;
}
}
+179
View File
@@ -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);
}
}
+86
View File
@@ -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
View File
@@ -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
+1 -2
View File
@@ -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
}
+1 -2
View File
@@ -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
-1
View File
@@ -41,7 +41,6 @@ namespace QuanTAlib;
///
/// Note: Returns three values: upper, middle, and lower bands
/// </remarks>
[SkipLocalsInit]
public sealed class Bband : AbstractBase
{
-1
View File
@@ -36,7 +36,6 @@ namespace QuanTAlib;
///
/// Note: Returns annualized volatility as a percentage
/// </remarks>
[SkipLocalsInit]
public sealed class Ccv : AbstractBase
{
-1
View File
@@ -38,7 +38,6 @@ namespace QuanTAlib;
///
/// Note: Returns two values: long exit and short exit levels
/// </remarks>
[SkipLocalsInit]
public sealed class Ce : AbstractBase
{
+1 -2
View File
@@ -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
+100
View File
@@ -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();
}
+1 -2
View File
@@ -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);
+2 -3
View File
@@ -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;
+1 -2
View File
@@ -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
-1
View File
@@ -37,7 +37,6 @@ namespace QuanTAlib;
///
/// Note: Returns annualized volatility as a percentage
/// </remarks>
[SkipLocalsInit]
public sealed class Hlv : AbstractBase
{
+94
View File
@@ -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;
}
}
+102
View File
@@ -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();
}
+93
View File
@@ -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;
}
}
+93
View File
@@ -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;
}
}
+105
View File
@@ -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;
}
}
+113
View File
@@ -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;
}
}
+9 -9
View File
@@ -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