From c5b583cd0df0a363a5b910c18366f2ae27aa2f76 Mon Sep 17 00:00:00 2001 From: Miha Kralj <31756078+mihakralj@users.noreply.github.com> Date: Tue, 5 Nov 2024 06:42:31 -0800 Subject: [PATCH 1/8] Create EFI - Elder Ray's Force Index - add tests and xml comments --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/mihakralj/QuanTAlib?shareId=XXXX-XXXX-XXXX-XXXX). --- Tests/test_updates_oscillators.cs | 16 +++++ docs/indicators/indicators.md | 6 +- lib/oscillators/Efi.cs | 104 ++++++++++++++++++++++++++++++ lib/oscillators/_list.md | 4 +- 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 lib/oscillators/Efi.cs diff --git a/Tests/test_updates_oscillators.cs b/Tests/test_updates_oscillators.cs index c1630815..24964699 100644 --- a/Tests/test_updates_oscillators.cs +++ b/Tests/test_updates_oscillators.cs @@ -321,4 +321,20 @@ public class OscillatorsUpdateTests Assert.Equal(initialValue, finalValue, precision); } + + [Fact] + public void Efi_Update() + { + var indicator = new Efi(period: 13); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } } diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index 3916949a..a88d07f9 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -5,12 +5,12 @@ | Basic Transforms | 6 of 6 | 100% | | Averages & Trends | 33 of 33 | 100% | | Momentum | 16 of 16 | 100% | -| Oscillators | 21 of 29 | 72% | +| Oscillators | 22 of 29 | 76% | | Volatility | 24 of 35 | 69% | | Volume | 15 of 19 | 79% | | Numerical Analysis | 13 of 19 | 68% | | Errors | 16 of 16 | 100% | -| **Total** | **144 of 173** | **83%** | +| **Total** | **145 of 173** | **84%** | |Technical Indicator Name| Class Name| |-----------|:----------:| @@ -85,9 +85,9 @@ |COPPOCK - Coppock Curve|`Coppock`| |CRSI - Connor RSI|`Crsi`| |🚧 CTI - Ehler's Correlation Trend Indicator|`Cti`| -|🚧 EFI - Elder Ray's Force Index|`Efi`| |🚧 FISHER - Fisher Transform|`Fisher`| |🚧 FOSC - Forecast Oscillator|`Fosc`| +|EFI - Elder Ray's Force Index|`Efi`| |🚧 GATOR* - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)|`Gator`| |🚧 KDJ* - KDJ Indicator (K, D, J lines)|`Kdj`| |🚧 KRI - Kairi Relative Index|`Kri`| diff --git a/lib/oscillators/Efi.cs b/lib/oscillators/Efi.cs new file mode 100644 index 00000000..a22fdde1 --- /dev/null +++ b/lib/oscillators/Efi.cs @@ -0,0 +1,104 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// EFI: Elder Ray's Force Index +/// A volume-based oscillator that measures the strength of price movements using volume. +/// It helps identify potential trend reversals and confirm price movements. +/// +/// +/// The EFI calculation process: +/// 1. Calculate the difference between the current close and the previous close +/// 2. Multiply the difference by the current volume +/// 3. Apply an exponential moving average (EMA) to smooth the result +/// +/// Key characteristics: +/// - Oscillates above and below zero +/// - Positive values indicate buying pressure +/// - Negative values indicate selling pressure +/// - Crosses above zero suggest buying opportunities +/// - Crosses below zero suggest selling opportunities +/// +/// Formula: +/// EFI = EMA((Close - Close[1]) * Volume, period) +/// +/// Sources: +/// Alexander Elder - "Trading for a Living" (1993) +/// https://www.investopedia.com/terms/f/force-index.asp +/// +/// Note: Default period is 13 +/// +[SkipLocalsInit] +public sealed class Efi : AbstractBase +{ + private readonly Ema _ema; + private double _prevClose; + private double _p_prevClose; + private const int DefaultPeriod = 13; + + /// The smoothing period for EMA calculation (default 13). + /// Thrown when period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Efi(int period = DefaultPeriod) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + + _ema = new(period); + WarmupPeriod = period + 1; + Name = $"EFI({period})"; + } + + /// The data source object that publishes updates. + /// The smoothing period for EMA calculation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Efi(object source, int period = DefaultPeriod) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _ema.Init(); + _prevClose = double.NaN; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_prevClose = _prevClose; + } + else + { + _prevClose = _p_prevClose; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate raw force index + double priceChange = BarInput.Close - _prevClose; + double forceIndex = priceChange * BarInput.Volume; + + // Update previous close + _prevClose = BarInput.Close; + + // Apply EMA smoothing + return _ema.Calc(forceIndex, BarInput.IsNew); + } +} diff --git a/lib/oscillators/_list.md b/lib/oscillators/_list.md index 72b74c86..e2ab8a34 100644 --- a/lib/oscillators/_list.md +++ b/lib/oscillators/_list.md @@ -1,5 +1,5 @@ # Oscillators indicators -Done: 21, Todo: 8 +Done: 22, Todo: 7 ✔️ AC - Acceleration Oscillator ✔️ AO - Awesome Oscillator @@ -14,7 +14,7 @@ Done: 21, Todo: 8 ✔️ CRSI - Connor RSI CTI - Ehler's Correlation Trend Indicator ✔️ DOSC - Derivative Oscillator -EFI - Elder Ray's Force Index +✔️ EFI - Elder Ray's Force Index FISHER - Fisher Transform FOSC - Forecast Oscillator *GATOR - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth) From e934b424c2e794080922d83fb114afffdbbb28f2 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Tue, 5 Nov 2024 07:42:03 -0800 Subject: [PATCH 2/8] fix exception handling --- lib/oscillators/Efi.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/oscillators/Efi.cs b/lib/oscillators/Efi.cs index a22fdde1..d2f6d9f5 100644 --- a/lib/oscillators/Efi.cs +++ b/lib/oscillators/Efi.cs @@ -41,9 +41,7 @@ public sealed class Efi : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Efi(int period = DefaultPeriod) { - if (period < 1) - throw new ArgumentOutOfRangeException(nameof(period)); - + ArgumentOutOfRangeException.ThrowIfLessThan(period, 1); _ema = new(period); WarmupPeriod = period + 1; Name = $"EFI({period})"; From 0bae9ce15b639d21d76e4b98a1add5eb33c30426 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Tue, 5 Nov 2024 15:51:29 -0800 Subject: [PATCH 3/8] sonar fixes --- Tests/test_eventing.cs | 2 + Tests/test_updates_statistics.cs | 33 ++++++ docs/indicators/indicators.md | 30 +++-- lib/errors/Huber.cs | 11 +- lib/momentum/Macd.cs | 14 +-- lib/momentum/_list.md | 2 +- lib/oscillators/Coppock.cs | 9 +- lib/oscillators/Rsi.cs | 3 +- lib/oscillators/Smi.cs | 9 +- lib/oscillators/Srsi.cs | 22 +--- lib/oscillators/Stc.cs | 27 +---- lib/oscillators/Stoch.cs | 9 +- lib/oscillators/Uo.cs | 30 +---- lib/statistics/Beta.cs | 159 ++++++++++++++++++++++++++ lib/statistics/Corr.cs | 163 +++++++++++++++++++++++++++ lib/statistics/Percentile.cs | 15 +-- lib/statistics/Theil.cs | 167 ++++++++++++++++++++++++++++ lib/statistics/Tsf.cs | 185 +++++++++++++++++++++++++++++++ lib/statistics/_list.md | 52 +++++---- 19 files changed, 801 insertions(+), 141 deletions(-) create mode 100644 lib/statistics/Beta.cs create mode 100644 lib/statistics/Corr.cs create mode 100644 lib/statistics/Theil.cs create mode 100644 lib/statistics/Tsf.cs diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs index 5406876e..b0689b4d 100644 --- a/Tests/test_eventing.cs +++ b/Tests/test_eventing.cs @@ -76,6 +76,8 @@ public class EventingTests ("Stddev", new Stddev(p), new Stddev(input, p)), ("Variance", new Variance(p), new Variance(input, p)), ("Zscore", new Zscore(p), new Zscore(input, p)), + ("Beta", new Beta(p), new Beta(input, p)), + ("Corr", new Corr(p), new Corr(input, p)), // Volatility indicators (value-based) ("Hv", new Hv(p), new Hv(input, p)), ("Jvolty", new Jvolty(p), new Jvolty(input, p)), diff --git a/Tests/test_updates_statistics.cs b/Tests/test_updates_statistics.cs index e1c902aa..ff6ed756 100644 --- a/Tests/test_updates_statistics.cs +++ b/Tests/test_updates_statistics.cs @@ -26,6 +26,39 @@ public class StatisticsUpdateTests return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew); } + [Fact] + public void Beta_Update() + { + var indicator = new Beta(period: 14); + TBar marketBar = GetRandomBar(true); + TBar assetBar = GetRandomBar(true); + double initialValue = indicator.Calc(marketBar, assetBar); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(false), GetRandomBar(false)); + } + double finalValue = indicator.Calc(new TBar(marketBar.Time, marketBar.Open, marketBar.High, marketBar.Low, marketBar.Close, marketBar.Volume, false), + new TBar(assetBar.Time, assetBar.Open, assetBar.High, assetBar.Low, assetBar.Close, assetBar.Volume, false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Corr_Update() + { + var indicator = new Corr(period: 14); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true), new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false), new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false), new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + [Fact] public void Curvature_Update() { diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index a88d07f9..4fdc43aa 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -6,11 +6,12 @@ | Averages & Trends | 33 of 33 | 100% | | Momentum | 16 of 16 | 100% | | Oscillators | 22 of 29 | 76% | -| Volatility | 24 of 35 | 69% | -| Volume | 15 of 19 | 79% | -| Numerical Analysis | 13 of 19 | 68% | +| Volatility | 29 of 35 | 83% | +| Volume | 19 of 19 | 100% | +| Numerical Analysis | 15 of 19 | 79% | | Errors | 16 of 16 | 100% | -| **Total** | **145 of 173** | **84%** | +| Patterns | 0 of 8 | 0% | +| **Total** | **156 of 181** | **86%** | |Technical Indicator Name| Class Name| |-----------|:----------:| @@ -85,9 +86,10 @@ |COPPOCK - Coppock Curve|`Coppock`| |CRSI - Connor RSI|`Crsi`| |🚧 CTI - Ehler's Correlation Trend Indicator|`Cti`| +|DOSC - Derivative Oscillator|`Dosc`| +|EFI - Elder Ray's Force Index|`Efi`| |🚧 FISHER - Fisher Transform|`Fisher`| |🚧 FOSC - Forecast Oscillator|`Fosc`| -|EFI - Elder Ray's Force Index|`Efi`| |🚧 GATOR* - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)|`Gator`| |🚧 KDJ* - KDJ Indicator (K, D, J lines)|`Kdj`| |🚧 KRI - Kairi Relative Index|`Kri`| @@ -101,7 +103,15 @@ |TSI - True Strength Index|`Tsi`| |UO - Ultimate Oscillator|`Uo`| |WILLR - Larry Williams' %R|`Willr`| -|DOSC - Derivative Oscillator|`Dosc`| +|**PATTERNS**|| +|🚧 DOJI - Doji Candlestick Pattern|`Doji`| +|🚧 ER* - Elder Ray Pattern (Bull Power, Bear Power)|`Er`| +|🚧 MARU - Marubozu Candlestick Pattern|`Maru`| +|🚧 PIV* - Pivot Points (Support 1-3, Pivot, Resistance 1-3)|`Piv`| +|🚧 PP* - Price Pivots (Support 1-3, Pivot, Resistance 1-3)|`Pp`| +|🚧 RPP* - Rolling Pivot Points (Support 1-3, Pivot, Resistance 1-3)|`Rpp`| +|🚧 WF - Williams Fractal|`Wf`| +|🚧 ZZ - Zig Zag Pattern|`Zz`| |**VOLATILITY INDICATORS**|| |ADR - Average Daily Range|`Adr`| |AP - Andrew's Pitchfork|`Ap`| @@ -159,12 +169,12 @@ |VWAP - Volume Weighted Average Price|`Vwap`| |VWMA - Volume Weighted Moving Average|`Vwma`| |**NUMERICAL ANALYSIS**|| -|🚧 BETA* - Beta coefficient (Beta, R-squared)|`Beta`| -|🚧 CORR* - Correlation Coefficient (Correlation, P-value)|`Corr`| +|BETA* - Beta coefficient (Beta, R-squared)|`Beta`| +|CORR* - Correlation Coefficient (Correlation, P-value)|`Corr`| |CURVATURE - Rate of Change in Direction or Slope|`Curvature`| |ENTROPY - Measure of Uncertainty or Disorder|`Entropy`| -|🚧 HUBER - Huber Loss|`Huber`| -|🚧 HURST - Hurst Exponent|`Hurst`| +|HUBER - Huber Loss|`Huber`| +|HURST - Hurst Exponent|`Hurst`| |KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`| |MAX - Maximum with exponential decay|`Max`| |MEDIAN - Middle value|`Median`| diff --git a/lib/errors/Huber.cs b/lib/errors/Huber.cs index 12c5779b..ffe7a0a1 100644 --- a/lib/errors/Huber.cs +++ b/lib/errors/Huber.cs @@ -43,14 +43,9 @@ public sealed class Huber : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Huber(int period, double delta = 1.0) { - if (period < 1) - { - throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); - } - if (delta <= 0) - { - throw new ArgumentOutOfRangeException(nameof(delta), "Delta must be greater than 0."); - } + ArgumentOutOfRangeException.ThrowIfLessThan(period, 1); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(delta, 0); + WarmupPeriod = period; _actualBuffer = new CircularBuffer(period); _predictedBuffer = new CircularBuffer(period); diff --git a/lib/momentum/Macd.cs b/lib/momentum/Macd.cs index 67c4d589..263c80ec 100644 --- a/lib/momentum/Macd.cs +++ b/lib/momentum/Macd.cs @@ -60,14 +60,14 @@ public sealed class Macd : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Macd(int fastPeriod = DefaultFastPeriod, int slowPeriod = DefaultSlowPeriod, int signalPeriod = DefaultSignalPeriod) { - if (fastPeriod < 1) - throw new ArgumentOutOfRangeException(nameof(fastPeriod)); - if (slowPeriod < 1) - throw new ArgumentOutOfRangeException(nameof(slowPeriod)); - if (signalPeriod < 1) - throw new ArgumentOutOfRangeException(nameof(signalPeriod)); + ArgumentOutOfRangeException.ThrowIfLessThan(fastPeriod, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(slowPeriod, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(signalPeriod, 1); + if (fastPeriod >= slowPeriod) - throw new ArgumentException("Fast period must be less than slow period"); + { + throw new ArgumentOutOfRangeException(nameof(fastPeriod), "Fast period must be less than slow period"); + } _fastEma = new(fastPeriod); _slowEma = new(slowPeriod); diff --git a/lib/momentum/_list.md b/lib/momentum/_list.md index 1d01f144..85cc2072 100644 --- a/lib/momentum/_list.md +++ b/lib/momentum/_list.md @@ -4,7 +4,7 @@ Done: 15, Todo: 2 ✔️ ADX - Average Directional Movement Index ✔️ ADXR - Average Directional Movement Index Rating ✔️ APO - Absolute Price Oscillator -✔️ *DMI - Directional Movement Index (DI+, DI-) +✔️ DMI - Directional Movement Index (DI+, DI-) ✔️ DMX - Jurik Directional Movement Index ✔️ DPO - Detrended Price Oscillator ✔️ *MACD - Moving Average Convergence/Divergence (MACD, Signal, Histogram) diff --git a/lib/oscillators/Coppock.cs b/lib/oscillators/Coppock.cs index 9cd6231c..ed905b4c 100644 --- a/lib/oscillators/Coppock.cs +++ b/lib/oscillators/Coppock.cs @@ -51,12 +51,9 @@ public sealed class Coppock : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Coppock(int roc1Period = DefaultRoc1Period, int roc2Period = DefaultRoc2Period, int wmaPeriod = DefaultWmaPeriod) { - if (roc1Period < 1) - throw new ArgumentOutOfRangeException(nameof(roc1Period), "ROC1 period must be greater than 0"); - if (roc2Period < 1) - throw new ArgumentOutOfRangeException(nameof(roc2Period), "ROC2 period must be greater than 0"); - if (wmaPeriod < 1) - throw new ArgumentOutOfRangeException(nameof(wmaPeriod), "WMA period must be greater than 0"); + ArgumentOutOfRangeException.ThrowIfLessThan(roc1Period, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(roc2Period, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(wmaPeriod, 1); _roc1Period = roc1Period; _roc2Period = roc2Period; diff --git a/lib/oscillators/Rsi.cs b/lib/oscillators/Rsi.cs index c18f70aa..3db9f953 100644 --- a/lib/oscillators/Rsi.cs +++ b/lib/oscillators/Rsi.cs @@ -48,8 +48,7 @@ public sealed class Rsi : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rsi(int period = DefaultPeriod) { - if (period < 1) - throw new ArgumentOutOfRangeException(nameof(period)); + ArgumentOutOfRangeException.ThrowIfLessThan(period, 1); _avgGain = new(period, useSma: true); _avgLoss = new(period, useSma: true); _index = 0; diff --git a/lib/oscillators/Smi.cs b/lib/oscillators/Smi.cs index 27e6e635..989218d1 100644 --- a/lib/oscillators/Smi.cs +++ b/lib/oscillators/Smi.cs @@ -57,12 +57,9 @@ public sealed class Smi : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Smi(int period = DefaultPeriod, int smooth1 = DefaultSmooth1, int smooth2 = DefaultSmooth2) { - if (period < 1) - throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0"); - if (smooth1 < 1) - throw new ArgumentOutOfRangeException(nameof(smooth1), "Smooth1 must be greater than 0"); - if (smooth2 < 1) - throw new ArgumentOutOfRangeException(nameof(smooth2), "Smooth2 must be greater than 0"); + ArgumentOutOfRangeException.ThrowIfLessThan(period, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(smooth1, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(smooth2, 1); _highs = new(period); _lows = new(period); diff --git a/lib/oscillators/Srsi.cs b/lib/oscillators/Srsi.cs index c58d3fe9..bdb2c3df 100644 --- a/lib/oscillators/Srsi.cs +++ b/lib/oscillators/Srsi.cs @@ -40,7 +40,6 @@ public sealed class Srsi : AbstractBase private readonly CircularBuffer _srsiValues; private readonly Sma _signal; private readonly int _rsiPeriod; - private readonly int _stochPeriod; private const int DefaultRsiPeriod = 14; private const int DefaultStochPeriod = 14; private const int DefaultSmoothK = 3; @@ -56,25 +55,12 @@ public sealed class Srsi : AbstractBase public Srsi(int rsiPeriod = DefaultRsiPeriod, int stochPeriod = DefaultStochPeriod, int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD) { - if (rsiPeriod < 1) - { - throw new ArgumentOutOfRangeException(nameof(rsiPeriod), "Period must be greater than 0"); - } - if (stochPeriod < 1) - { - throw new ArgumentOutOfRangeException(nameof(stochPeriod), "Period must be greater than 0"); - } - if (smoothK < 1) - { - throw new ArgumentOutOfRangeException(nameof(smoothK), "Period must be greater than 0"); - } - if (smoothD < 1) - { - throw new ArgumentOutOfRangeException(nameof(smoothD), "Period must be greater than 0"); - } + ArgumentOutOfRangeException.ThrowIfLessThan(rsiPeriod, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(stochPeriod, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(smoothK, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(smoothD, 1); _rsiPeriod = rsiPeriod; - _stochPeriod = stochPeriod; _rsi = new(rsiPeriod); _rsiValues = new(stochPeriod); _srsiValues = new(smoothK); diff --git a/lib/oscillators/Stc.cs b/lib/oscillators/Stc.cs index b1ab2560..8b755952 100644 --- a/lib/oscillators/Stc.cs +++ b/lib/oscillators/Stc.cs @@ -62,32 +62,17 @@ public sealed class Stc : AbstractBase int slowPeriod = DefaultSlowPeriod, int d1Period = DefaultD1Period, int stcPeriod = DefaultStcPeriod) { - string err = "All periods must be greater than 0"; + ArgumentOutOfRangeException.ThrowIfLessThan(cyclePeriod, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(fastPeriod, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(slowPeriod, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(d1Period, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(stcPeriod, 1); - if (cyclePeriod < 1) - { - throw new ArgumentOutOfRangeException(nameof(cyclePeriod), err); - } - if (fastPeriod < 1) - { - throw new ArgumentOutOfRangeException(nameof(fastPeriod), err); - } - if (slowPeriod < 1) - { - throw new ArgumentOutOfRangeException(nameof(slowPeriod), err); - } - if (d1Period < 1) - { - throw new ArgumentOutOfRangeException(nameof(d1Period), err); - } - if (stcPeriod < 1) - { - throw new ArgumentOutOfRangeException(nameof(stcPeriod), err); - } if (fastPeriod >= slowPeriod) { throw new ArgumentOutOfRangeException(nameof(fastPeriod), "Fast period must be less than slow period"); } + _fastEma = new(fastPeriod); _slowEma = new(slowPeriod); _macdValues = new(cyclePeriod); diff --git a/lib/oscillators/Stoch.cs b/lib/oscillators/Stoch.cs index ab10d0b6..1f5d30a0 100644 --- a/lib/oscillators/Stoch.cs +++ b/lib/oscillators/Stoch.cs @@ -52,12 +52,9 @@ public sealed class Stoch : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Stoch(int period = DefaultPeriod, int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD) { - if (period < 1) - throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than 0"); - if (smoothK < 1) - throw new ArgumentOutOfRangeException(nameof(smoothK), "%K smoothing period must be greater than 0"); - if (smoothD < 1) - throw new ArgumentOutOfRangeException(nameof(smoothD), "%D smoothing period must be greater than 0"); + ArgumentOutOfRangeException.ThrowIfLessThan(period, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(smoothK, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(smoothD, 1); _highs = new(period); _lows = new(period); diff --git a/lib/oscillators/Uo.cs b/lib/oscillators/Uo.cs index ec247ebd..38a98129 100644 --- a/lib/oscillators/Uo.cs +++ b/lib/oscillators/Uo.cs @@ -67,30 +67,12 @@ public sealed class Uo : AbstractBase public Uo(int period1 = DefaultPeriod1, int period2 = DefaultPeriod2, int period3 = DefaultPeriod3, double weight1 = DefaultWeight1, double weight2 = DefaultWeight2, double weight3 = DefaultWeight3) { - if (period1 < 1) - { - throw new ArgumentOutOfRangeException(nameof(period1), "Period1 must be greater than 0"); - } - if (period2 < 1) - { - throw new ArgumentOutOfRangeException(nameof(period2), "Period2 must be greater than 0"); - } - if (period3 < 1) - { - throw new ArgumentOutOfRangeException(nameof(period3), "Period3 must be greater than 0"); - } - if (weight1 <= 0) - { - throw new ArgumentOutOfRangeException(nameof(weight1), "Weight1 must be greater than 0"); - } - if (weight2 <= 0) - { - throw new ArgumentOutOfRangeException(nameof(weight2), "Weight2 must be greater than 0"); - } - if (weight3 <= 0) - { - throw new ArgumentOutOfRangeException(nameof(weight3), "Weight3 must be greater than 0"); - } + ArgumentOutOfRangeException.ThrowIfLessThan(period1, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(period2, 1); + ArgumentOutOfRangeException.ThrowIfLessThan(period3, 1); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight1, 0); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight2, 0); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(weight3, 0); _weight1 = weight1; _weight2 = weight2; diff --git a/lib/statistics/Beta.cs b/lib/statistics/Beta.cs new file mode 100644 index 00000000..97e065f2 --- /dev/null +++ b/lib/statistics/Beta.cs @@ -0,0 +1,159 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// BETA: Beta Coefficient +/// A statistical measure that quantifies the volatility of an asset or portfolio +/// in relation to the overall market. Beta is used to assess the risk and return +/// characteristics of an investment. +/// +/// +/// The Beta calculation process: +/// 1. Calculates covariance between asset and market returns +/// 2. Computes variance of market returns +/// 3. Divides covariance by market variance +/// +/// Key characteristics: +/// - Measures relative volatility +/// - Beta > 1: More volatile than market +/// - Beta < 1: Less volatile than market +/// - Beta = 1: Same volatility as market +/// - Beta < 0: Inverse relationship with market +/// +/// Formula: +/// β = Cov(Ra, Rm) / Var(Rm) +/// where: +/// Ra = asset returns +/// Rm = market returns +/// +/// Market Applications: +/// - Risk assessment +/// - Portfolio management +/// - Asset allocation +/// - Performance analysis +/// - Hedging strategies +/// +/// Sources: +/// https://en.wikipedia.org/wiki/Beta_(finance) +/// "Modern Portfolio Theory" - Harry Markowitz +/// +/// Note: Assumes linear relationship between asset and market returns +/// +[SkipLocalsInit] +public sealed class Beta : AbstractBase +{ + private readonly int Period; + private readonly CircularBuffer _assetReturns; + private readonly CircularBuffer _marketReturns; + private const double Epsilon = 1e-10; + private const int MinimumPoints = 2; + + /// The number of points to consider for beta calculation. + /// Thrown when period is less than 2. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Beta(int period) + { + if (period < MinimumPoints) + { + throw new ArgumentOutOfRangeException(nameof(period), + "Period must be greater than or equal to 2 for beta calculation."); + } + Period = period; + WarmupPeriod = MinimumPoints; + _assetReturns = new CircularBuffer(period); + _marketReturns = new CircularBuffer(period); + Name = $"Beta(period={period})"; + Init(); + } + + /// The data source object that publishes updates. + /// The number of points to consider for beta calculation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Beta(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _assetReturns.Clear(); + _marketReturns.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Input.Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateMean(ReadOnlySpan values) + { + double sum = 0; + for (int i = 0; i < values.Length; i++) + { + sum += values[i]; + } + return sum / values.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateCovariance(ReadOnlySpan assetReturns, ReadOnlySpan marketReturns, double assetMean, double marketMean) + { + double covariance = 0; + for (int i = 0; i < assetReturns.Length; i++) + { + covariance += (assetReturns[i] - assetMean) * (marketReturns[i] - marketMean); + } + return covariance / assetReturns.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateVariance(ReadOnlySpan values, double mean) + { + double variance = 0; + for (int i = 0; i < values.Length; i++) + { + double diff = values[i] - mean; + variance += diff * diff; + } + return variance / values.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + _assetReturns.Add(Input.Value, Input.IsNew); + _marketReturns.Add(Input2.Value, Input.IsNew); + + double beta = 0; + if (_assetReturns.Count >= MinimumPoints && _marketReturns.Count >= MinimumPoints) + { + ReadOnlySpan assetValues = _assetReturns.GetSpan(); + ReadOnlySpan marketValues = _marketReturns.GetSpan(); + + double assetMean = CalculateMean(assetValues); + double marketMean = CalculateMean(marketValues); + + double covariance = CalculateCovariance(assetValues, marketValues, assetMean, marketMean); + double marketVariance = CalculateVariance(marketValues, marketMean); + + if (marketVariance > Epsilon) + { + beta = covariance / marketVariance; + } + } + + IsHot = _assetReturns.Count >= Period && _marketReturns.Count >= Period; + return beta; + } +} diff --git a/lib/statistics/Corr.cs b/lib/statistics/Corr.cs new file mode 100644 index 00000000..0eb18c50 --- /dev/null +++ b/lib/statistics/Corr.cs @@ -0,0 +1,163 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// CORR: Correlation Coefficient +/// A statistical measure that quantifies the strength and direction of the relationship +/// between two variables. The correlation coefficient ranges from -1 to 1, where 1 indicates +/// a perfect positive correlation, -1 indicates a perfect negative correlation, and 0 indicates +/// no correlation. +/// +/// +/// The Correlation calculation process: +/// 1. Calculates mean of both variables +/// 2. Computes covariance between variables +/// 3. Calculates standard deviation of both variables +/// 4. Divides covariance by product of standard deviations +/// +/// Key characteristics: +/// - Measures linear relationship strength +/// - Symmetric around zero +/// - Scale-independent measure +/// - Sensitive to outliers +/// - Useful for portfolio diversification +/// +/// Formula: +/// ρ = Cov(X, Y) / (σX * σY) +/// where: +/// X, Y = variables +/// Cov = covariance +/// σ = standard deviation +/// +/// Market Applications: +/// - Portfolio diversification +/// - Risk management +/// - Pairs trading +/// - Performance analysis +/// - Market sentiment analysis +/// +/// Sources: +/// https://en.wikipedia.org/wiki/Correlation_coefficient +/// "Modern Portfolio Theory" - Harry Markowitz +/// +/// Note: Assumes linear relationship between variables +/// +[SkipLocalsInit] +public sealed class Corr : AbstractBase +{ + private readonly int Period; + private readonly CircularBuffer _xValues; + private readonly CircularBuffer _yValues; + private const double Epsilon = 1e-10; + private const int MinimumPoints = 2; + + /// The number of points to consider for correlation calculation. + /// Thrown when period is less than 2. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Corr(int period) + { + if (period < MinimumPoints) + { + throw new ArgumentOutOfRangeException(nameof(period), + "Period must be greater than or equal to 2 for correlation calculation."); + } + Period = period; + WarmupPeriod = MinimumPoints; + _xValues = new CircularBuffer(period); + _yValues = new CircularBuffer(period); + Name = $"Corr(period={period})"; + Init(); + } + + /// The data source object that publishes updates. + /// The number of points to consider for correlation calculation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Corr(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _xValues.Clear(); + _yValues.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Input.Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateMean(ReadOnlySpan values) + { + double sum = 0; + for (int i = 0; i < values.Length; i++) + { + sum += values[i]; + } + return sum / values.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateCovariance(ReadOnlySpan xValues, ReadOnlySpan yValues, double xMean, double yMean) + { + double covariance = 0; + for (int i = 0; i < xValues.Length; i++) + { + covariance += (xValues[i] - xMean) * (yValues[i] - yMean); + } + return covariance / xValues.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateStandardDeviation(ReadOnlySpan values, double mean) + { + double sumSquaredDeviations = 0; + for (int i = 0; i < values.Length; i++) + { + double deviation = values[i] - mean; + sumSquaredDeviations += deviation * deviation; + } + return Math.Sqrt(sumSquaredDeviations / values.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + _xValues.Add(Input.Value, Input.IsNew); + _yValues.Add(Input2.Value, Input.IsNew); + + double correlation = 0; + if (_xValues.Count >= MinimumPoints && _yValues.Count >= MinimumPoints) + { + ReadOnlySpan xValues = _xValues.GetSpan(); + ReadOnlySpan yValues = _yValues.GetSpan(); + + double xMean = CalculateMean(xValues); + double yMean = CalculateMean(yValues); + + double covariance = CalculateCovariance(xValues, yValues, xMean, yMean); + double xStdDev = CalculateStandardDeviation(xValues, xMean); + double yStdDev = CalculateStandardDeviation(yValues, yMean); + + if (xStdDev > Epsilon && yStdDev > Epsilon) + { + correlation = covariance / (xStdDev * yStdDev); + } + } + + IsHot = _xValues.Count >= Period && _yValues.Count >= Period; + return correlation; + } +} diff --git a/lib/statistics/Percentile.cs b/lib/statistics/Percentile.cs index 635f6680..c8ef7be9 100644 --- a/lib/statistics/Percentile.cs +++ b/lib/statistics/Percentile.cs @@ -45,7 +45,6 @@ public sealed class Percentile : AbstractBase private readonly int Period; private readonly double Percent; private readonly CircularBuffer _buffer; - private const double Epsilon = 1e-10; private const int MinimumPoints = 2; /// The number of points to consider for percentile calculation. @@ -56,16 +55,10 @@ public sealed class Percentile : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining)] public Percentile(int period, double percent) { - if (period < MinimumPoints) - { - throw new ArgumentOutOfRangeException(nameof(period), - "Period must be greater than or equal to 2 for percentile calculation."); - } - if (percent < 0 || percent > 100) - { - throw new ArgumentOutOfRangeException(nameof(percent), - "Percent must be between 0 and 100."); - } + ArgumentOutOfRangeException.ThrowIfLessThan(period, MinimumPoints); + ArgumentOutOfRangeException.ThrowIfLessThan(percent, 0); + ArgumentOutOfRangeException.ThrowIfGreaterThan(percent, 100); + Period = period; Percent = percent; WarmupPeriod = MinimumPoints; // Minimum number of points needed for percentile calculation diff --git a/lib/statistics/Theil.cs b/lib/statistics/Theil.cs new file mode 100644 index 00000000..3ebcb7b5 --- /dev/null +++ b/lib/statistics/Theil.cs @@ -0,0 +1,167 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// THEIL: Theil's U Statistics (U1, U2) +/// A statistical measure that quantifies the accuracy of forecasts compared to actual values +/// and naive forecasts. +/// +/// +/// The Theil's U calculation process: +/// 1. Calculate U1 statistic (relative accuracy) +/// 2. Calculate U2 statistic (comparison with naive forecast) +/// +/// Key characteristics: +/// - U1 ranges from 0 to 1, with 0 indicating perfect forecast +/// - U2 < 1: forecast better than naive forecast +/// - U2 = 1: forecast equal to naive forecast +/// - U2 > 1: forecast worse than naive forecast +/// +/// Formula: +/// U1 = √[Σ(Ft - At)² / Σ(At)²] +/// U2 = √[Σ(Ft - At)² / Σ(At - At-1)²] +/// where: +/// Ft = forecasted value +/// At = actual value +/// At-1 = previous actual value +/// +/// Market Applications: +/// - Evaluating forecast accuracy +/// - Comparing forecasting models +/// - Assessing forecasting methods +/// - Model selection +/// - Performance analysis +/// +/// Sources: +/// https://en.wikipedia.org/wiki/Theil%27s_U +/// "Forecasting: Principles and Practice" - Rob J Hyndman +/// +/// Note: Should be used alongside other accuracy measures +/// +[SkipLocalsInit] +public sealed class Theil : AbstractBase +{ + private readonly int Period; + private readonly CircularBuffer _actual; + private readonly CircularBuffer _forecast; + private const int MinimumPoints = 2; + + /// + /// Gets the U2 statistic comparing forecast with naive forecast + /// + public double U2 { get; private set; } + + /// The number of points to consider for Theil's U calculation. + /// Thrown when period is less than 2. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Theil(int period) + { + if (period < MinimumPoints) + { + throw new ArgumentOutOfRangeException(nameof(period), + "Period must be greater than or equal to 2 for Theil's U calculation."); + } + Period = period; + WarmupPeriod = MinimumPoints; + _actual = new CircularBuffer(period); + _forecast = new CircularBuffer(period); + Name = $"Theil(period={period})"; + Init(); + } + + /// The data source object that publishes updates. + /// The number of points to consider for Theil's U calculation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Theil(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _actual.Clear(); + _forecast.Clear(); + U2 = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Input.Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateSquaredSum(ReadOnlySpan values) + { + double sum = 0; + for (int i = 0; i < values.Length; i++) + { + sum += values[i] * values[i]; + } + return sum; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateSquaredErrorSum(ReadOnlySpan forecast, ReadOnlySpan actual) + { + double sum = 0; + for (int i = 0; i < forecast.Length; i++) + { + double error = forecast[i] - actual[i]; + sum += error * error; + } + return sum; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateNaiveSquaredErrorSum(ReadOnlySpan actual) + { + double sum = 0; + for (int i = 1; i < actual.Length; i++) + { + double error = actual[i] - actual[i - 1]; + sum += error * error; + } + return sum; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + _actual.Add(Input.Value, Input.IsNew); + _forecast.Add(Input2.Value, Input.IsNew); + + double u1 = 0; + if (_actual.Count >= MinimumPoints && _forecast.Count >= MinimumPoints) + { + ReadOnlySpan actualValues = _actual.GetSpan(); + ReadOnlySpan forecastValues = _forecast.GetSpan(); + + double squaredErrorSum = CalculateSquaredErrorSum(forecastValues, actualValues); + double squaredActualSum = CalculateSquaredSum(actualValues); + double naiveSquaredErrorSum = CalculateNaiveSquaredErrorSum(actualValues); + + if (squaredActualSum > double.Epsilon) + { + u1 = Math.Sqrt(squaredErrorSum / squaredActualSum); + } + + if (naiveSquaredErrorSum > double.Epsilon) + { + U2 = Math.Sqrt(squaredErrorSum / naiveSquaredErrorSum); + } + } + + IsHot = _actual.Count >= Period && _forecast.Count >= Period; + return u1; + } +} diff --git a/lib/statistics/Tsf.cs b/lib/statistics/Tsf.cs new file mode 100644 index 00000000..7001e1e0 --- /dev/null +++ b/lib/statistics/Tsf.cs @@ -0,0 +1,185 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// TSF: Time Series Forecast +/// A statistical indicator that provides a linear regression forecast of future values +/// based on historical data. It includes both the forecast value and a confidence interval. +/// +/// +/// The Time Series Forecast calculation process: +/// 1. Calculates linear regression on the input data +/// 2. Extrapolates the regression line to forecast future values +/// 3. Computes confidence intervals based on the standard error of the forecast +/// +/// Key characteristics: +/// - Provides point forecast and confidence interval +/// - Based on linear regression principles +/// - Assumes trend continuity +/// - Sensitive to recent data changes +/// - Useful for short-term predictions +/// +/// Formula: +/// Forecast = a + b * (n + 1) +/// where: +/// a = y-intercept +/// b = slope +/// n = number of periods +/// +/// Confidence Interval = Forecast ± (t * SE) +/// where: +/// t = t-value for desired confidence level +/// SE = Standard Error of the forecast +/// +/// Market Applications: +/// - Price target estimation +/// - Trend analysis +/// - Risk assessment +/// - Trading strategy development +/// - Market behavior prediction +/// +/// Sources: +/// https://en.wikipedia.org/wiki/Time_series +/// "Forecasting: Principles and Practice" - Rob J Hyndman and George Athanasopoulos +/// +/// Note: Assumes linear trend in the data and may not capture non-linear patterns +/// +[SkipLocalsInit] +public sealed class Tsf : AbstractBase +{ + private readonly int Period; + private readonly CircularBuffer _values; + private const int MinimumPoints = 2; + + /// + /// The forecasted value for the next period. + /// + public double Forecast { get; private set; } + + /// + /// The lower bound of the confidence interval. + /// + public double LowerBound { get; private set; } + + /// + /// The upper bound of the confidence interval. + /// + public double UpperBound { get; private set; } + + /// The number of historical data points to consider for forecasting. + /// Thrown when period is less than 2. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Tsf(int period) + { + if (period < MinimumPoints) + { + throw new ArgumentOutOfRangeException(nameof(period), + "Period must be greater than or equal to 2 for time series forecasting."); + } + Period = period; + WarmupPeriod = MinimumPoints; + _values = new CircularBuffer(period); + Name = $"TSF(period={period})"; + Init(); + } + + /// The data source object that publishes updates. + /// The number of historical data points to consider for forecasting. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Tsf(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _values.Clear(); + Forecast = 0; + LowerBound = 0; + UpperBound = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Input.Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static (double slope, double intercept) CalculateLinearRegression(ReadOnlySpan values) + { + int n = values.Length; + double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0; + + for (int i = 0; i < n; i++) + { + double x = i + 1; + double y = values[i]; + sumX += x; + sumY += y; + sumXY += x * y; + sumX2 += x * x; + } + + double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); + double intercept = (sumY - slope * sumX) / n; + + return (slope, intercept); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateStandardError(ReadOnlySpan values, double slope, double intercept) + { + int n = values.Length; + double sumSquaredResiduals = 0; + + for (int i = 0; i < n; i++) + { + double x = i + 1; + double y = values[i]; + double predicted = slope * x + intercept; + double residual = y - predicted; + sumSquaredResiduals += residual * residual; + } + + return Math.Sqrt(sumSquaredResiduals / (n - 2)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + _values.Add(Input.Value, Input.IsNew); + + if (_values.Count >= MinimumPoints) + { + ReadOnlySpan values = _values.GetSpan(); + + var (slope, intercept) = CalculateLinearRegression(values); + + // Calculate forecast for the next period + Forecast = slope * (Period + 1) + intercept; + + // Calculate standard error + double standardError = CalculateStandardError(values, slope, intercept); + + // Calculate confidence interval (using t-distribution with n-2 degrees of freedom) + double tValue = 1.96; // Approximation for 95% confidence interval + double marginOfError = tValue * standardError * Math.Sqrt(1 + 1.0 / Period); + + LowerBound = Forecast - marginOfError; + UpperBound = Forecast + marginOfError; + } + + IsHot = _values.Count >= Period; + return Forecast; + } +} diff --git a/lib/statistics/_list.md b/lib/statistics/_list.md index 4e16b0de..3ca1af8c 100644 --- a/lib/statistics/_list.md +++ b/lib/statistics/_list.md @@ -1,22 +1,32 @@ -# Statistics indicators -Done: 13, Todo: 6 +# Statistics -*BETA - Beta coefficient (Beta, R-squared) -*CORR - Correlation Coefficient (Correlation, P-value) -✔️ CURVATURE - Rate of Change in Direction or Slope -✔️ ENTROPY - Measure of Uncertainty or Disorder -✔️ HURST - Hurst Exponent -✔️ KURTOSIS - Measure of Tails/Peakedness -✔️ MAX - Maximum with exponential decay -✔️ MEDIAN - Middle value -✔️ MIN - Minimum with exponential decay -✔️ MODE - Most Frequent Value -✔️ PERCENTILE - Rank Order -*RSQUARED - Coefficient of Determination (R-squared, Adjusted R-squared) -✔️ SKEW - Skewness, asymmetry of distribution -✔️ SLOPE - Rate of Change, Linear Regression -✔️ STDDEV - Standard Deviation, Measure of Spread -*THEIL - Theil's U Statistics (U1, U2) -*TSF - Time Series Forecast (Forecast, Confidence Interval) -✔️ VARIANCE - Average of Squared Deviations -✔️ ZSCORE - Standardized Score +Statistical functions and indicators for financial analysis. + +## Implemented + +- [Beta](Beta.cs) - Beta coefficient measuring volatility relative to market +- [Corr](Corr.cs) - Correlation coefficient between two series +- [Curvature](Curvature.cs) - Curvature of a time series +- [Entropy](Entropy.cs) - Information entropy of a series +- [Hurst](Hurst.cs) - Hurst exponent for trend strength +- [Kurtosis](Kurtosis.cs) - Kurtosis measuring tail extremity +- [Max](Max.cs) - Maximum value over period +- [Median](Median.cs) - Median value over period +- [Min](Min.cs) - Minimum value over period +- [Mode](Mode.cs) - Mode (most frequent value) +- [Percentile](Percentile.cs) - Percentile rank calculation +- [Skew](Skew.cs) - Skewness measuring distribution asymmetry +- [Slope](Slope.cs) - Linear regression slope +- [Stddev](Stddev.cs) - Standard deviation +- [Theil](Theil.cs) - Theil's U statistics for forecast accuracy +- [Tsf](Tsf.cs) - Time series forecast +- [Variance](Variance.cs) - Statistical variance +- [Zscore](Zscore.cs) - Z-score standardization + +## Planned + +- Cointegration - Test for cointegrated series +- Granger - Granger causality test +- Jarque-Bera - Normality test +- Kendall - Kendall rank correlation +- Spearman - Spearman rank correlation From 085c6dc6340f33a8c6ec3787b1e9ebdd2a6f705f Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Tue, 5 Nov 2024 23:51:44 +0000 Subject: [PATCH 4/8] [CodeFactor] Apply fixes to commit 0bae9ce --- lib/statistics/Tsf.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/statistics/Tsf.cs b/lib/statistics/Tsf.cs index 7001e1e0..6aa9b798 100644 --- a/lib/statistics/Tsf.cs +++ b/lib/statistics/Tsf.cs @@ -128,8 +128,8 @@ public sealed class Tsf : AbstractBase sumX2 += x * x; } - double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); - double intercept = (sumY - slope * sumX) / n; + double slope = ((n * sumXY) - (sumX * sumY)) / ((n * sumX2) - (sumX * sumX)); + double intercept = (sumY - (slope * sumX)) / n; return (slope, intercept); } @@ -144,7 +144,7 @@ public sealed class Tsf : AbstractBase { double x = i + 1; double y = values[i]; - double predicted = slope * x + intercept; + double predicted = (slope * x) + intercept; double residual = y - predicted; sumSquaredResiduals += residual * residual; } @@ -166,14 +166,14 @@ public sealed class Tsf : AbstractBase var (slope, intercept) = CalculateLinearRegression(values); // Calculate forecast for the next period - Forecast = slope * (Period + 1) + intercept; + Forecast = (slope * (Period + 1)) + intercept; // Calculate standard error double standardError = CalculateStandardError(values, slope, intercept); // Calculate confidence interval (using t-distribution with n-2 degrees of freedom) double tValue = 1.96; // Approximation for 95% confidence interval - double marginOfError = tValue * standardError * Math.Sqrt(1 + 1.0 / Period); + double marginOfError = tValue * standardError * Math.Sqrt(1 + (1.0 / Period)); LowerBound = Forecast - marginOfError; UpperBound = Forecast + marginOfError; From 582a0256ecde7c66afa5b5102f928172d3120011 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Wed, 6 Nov 2024 20:56:32 -0800 Subject: [PATCH 5/8] Momentum charts for Quantower --- QuanTAlib.sln | 39 ++++- lib/momentum/Dmi.cs | 113 ++++++-------- lib/momentum/Dmx.cs | 145 ++++-------------- lib/momentum/Dpo.cs | 11 +- lib/momentum/_list.md | 1 - quantower/Averages/AfirmaIndicator.cs | 2 +- quantower/Averages/AlmaIndicator.cs | 2 +- quantower/Averages/DemaIndicator.cs | 2 +- quantower/Averages/DsmaIndicator.cs | 2 +- quantower/Averages/DwmaIndicator.cs | 2 +- quantower/Averages/EmaIndicator.cs | 4 +- quantower/Averages/EpmaIndicator.cs | 2 +- quantower/Averages/FramaIndicator.cs | 2 +- quantower/Averages/FwmaIndicator.cs | 2 +- quantower/Averages/GmaIndicator.cs | 2 +- quantower/Averages/HmaIndicator.cs | 2 +- quantower/Averages/HtitIndicator.cs | 2 +- quantower/Averages/HwmaIndicator.cs | 2 +- quantower/Averages/JmaIndicator.cs | 6 +- quantower/Averages/KamaIndicator.cs | 2 +- quantower/Averages/LtmaIndicator.cs | 2 +- quantower/Averages/MaafIndicator.cs | 2 +- quantower/Averages/MamaIndicator.cs | 2 +- quantower/Averages/MgdiIndicator.cs | 2 +- quantower/Averages/MmaIndicator.cs | 2 +- quantower/Averages/PwmaIndicator.cs | 2 +- quantower/Averages/QemaIndicator.cs | 2 +- quantower/Averages/RemaIndicator.cs | 2 +- quantower/Averages/RmaIndicator.cs | 2 +- quantower/Averages/SinemaIndicator.cs | 2 +- quantower/Averages/SmaIndicator.cs | 2 +- quantower/Averages/SmmaIndicator.cs | 2 +- quantower/Averages/T3Indicator.cs | 2 +- quantower/Averages/TemaIndicator.cs | 2 +- quantower/Averages/TrimaIndicator.cs | 2 +- quantower/Averages/VidyaIndicator.cs | 2 +- quantower/Averages/WmaIndicator.cs | 2 +- quantower/Averages/ZlemaIndicator.cs | 2 +- .../FlowIndicator.cs | 0 .../TestIndicator.cs | 2 +- quantower/Experiments/_Experiments.csproj | 30 ++++ quantower/IndicatorExtensions.cs | 11 +- quantower/Momentum/AdxIndicator.cs | 53 +++++++ quantower/Momentum/AdxrIndicator.cs | 55 +++++++ quantower/Momentum/ApoIndicator.cs | 71 +++++++++ quantower/Momentum/DmiIndicator.cs | 59 +++++++ quantower/Momentum/DmxIndicator.cs | 68 ++++++++ quantower/Momentum/DpoIndicator.cs | 67 ++++++++ .../{Averages => Momentum}/MacdIndicator.cs | 31 ++-- quantower/Momentum/_Momentum.csproj | 2 +- .../RsiIndicator.cs | 2 +- .../RsxIndicator.cs | 2 +- quantower/Statistics/CurvatureIndicator.cs | 2 +- quantower/Statistics/EntropyIndicator.cs | 2 +- quantower/Statistics/KurtosisIndicator.cs | 2 +- quantower/Statistics/MaxIndicator.cs | 2 +- quantower/Statistics/MedianIndicator.cs | 2 +- quantower/Statistics/MinIndicator.cs | 2 +- quantower/Statistics/ModeIndicator.cs | 2 +- quantower/Statistics/PercentileIndicator.cs | 2 +- quantower/Statistics/SkewIndicator.cs | 2 +- quantower/Statistics/SlopeIndicator.cs | 2 +- quantower/Statistics/StddevIndicator.cs | 2 +- quantower/Statistics/VarianceIndicator.cs | 2 +- quantower/Statistics/ZscoreIndicator.cs | 2 +- quantower/Volatility/AtrIndicator.cs | 2 +- quantower/Volatility/CmoIndicator.cs | 2 +- quantower/Volatility/CviIndicator.cs | 4 +- quantower/Volatility/HistoricalIndicator.cs | 2 +- quantower/Volatility/JbandsIndicator.cs | 4 +- quantower/Volatility/JvoltyIndicator.cs | 2 +- quantower/Volatility/RealizedIndicator.cs | 2 +- quantower/Volatility/RviIndicator.cs | 2 +- quantower/Volume/ObvIndicator.cs | 51 ++++++ quantower/Volume/_Volume.csproj | 2 +- 75 files changed, 652 insertions(+), 281 deletions(-) rename quantower/{Volatility => Experiments}/FlowIndicator.cs (100%) rename quantower/{Volatility => Experiments}/TestIndicator.cs (94%) create mode 100644 quantower/Experiments/_Experiments.csproj create mode 100644 quantower/Momentum/AdxIndicator.cs create mode 100644 quantower/Momentum/AdxrIndicator.cs create mode 100644 quantower/Momentum/ApoIndicator.cs create mode 100644 quantower/Momentum/DmiIndicator.cs create mode 100644 quantower/Momentum/DmxIndicator.cs create mode 100644 quantower/Momentum/DpoIndicator.cs rename quantower/{Averages => Momentum}/MacdIndicator.cs (85%) rename quantower/{Volatility => Oscillators}/RsiIndicator.cs (95%) rename quantower/{Volatility => Oscillators}/RsxIndicator.cs (95%) create mode 100644 quantower/Volume/ObvIndicator.cs diff --git a/QuanTAlib.sln b/QuanTAlib.sln index 068fcd79..ad664505 100644 --- a/QuanTAlib.sln +++ b/QuanTAlib.sln @@ -12,6 +12,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Averages", "quantower\Avera EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volatility", "quantower\Volatility\_Volatility.csproj", "{B7DC44F7-D3A3-4C70-9025-513E0182B646}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oscillators", "quantower\Oscillators\_Oscillators.csproj", "{C4D8F5D0-E6A7-4B7D-B8E9-F55C3F8D9D01}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volume", "quantower\Volume\_Volume.csproj", "{D5E9F6D1-B8A8-4C7E-9FA0-F66C3F8D9D02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Momentum", "quantower\Momentum\_Momentum.csproj", "{E6F0F7D2-C9B9-4D8F-0FA1-F77C4F9D9D03}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Experiments", "quantower\Experiments\_Experiments.csproj", "{F7F1F8D3-DAC0-4E9F-1FB2-F88D5F0E0E04}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyntheticVendor", "SyntheticVendor\SyntheticVendor.csproj", "{1CF111D9-33E6-4A11-8FEC-F23300A78D15}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{2D97C971-20BF-40DB-94AA-3279F787D3CB}" @@ -40,12 +48,27 @@ Global {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Release | Any CPU.ActiveCfg = Release | Any CPU - {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Release | Any CPU.Build.0 = Release | Any CPU - {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Debug | Any CPU.ActiveCfg = Debug | Any CPU - {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Debug | Any CPU.Build.0 = Debug | Any CPU - {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Release | Any CPU.ActiveCfg = Release | Any CPU - {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Release | Any CPU.Build.0 = Release | Any CPU + {B7DC44F7-D3A3-4C70-9025-513E0182B646}.Release|Any CPU.Build.0 = Release|Any CPU + {C4D8F5D0-E6A7-4B7D-B8E9-F55C3F8D9D01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4D8F5D0-E6A7-4B7D-B8E9-F55C3F8D9D01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4D8F5D0-E6A7-4B7D-B8E9-F55C3F8D9D01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4D8F5D0-E6A7-4B7D-B8E9-F55C3F8D9D01}.Release|Any CPU.Build.0 = Release|Any CPU + {D5E9F6D1-B8A8-4C7E-9FA0-F66C3F8D9D02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5E9F6D1-B8A8-4C7E-9FA0-F66C3F8D9D02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5E9F6D1-B8A8-4C7E-9FA0-F66C3F8D9D02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5E9F6D1-B8A8-4C7E-9FA0-F66C3F8D9D02}.Release|Any CPU.Build.0 = Release|Any CPU + {E6F0F7D2-C9B9-4D8F-0FA1-F77C4F9D9D03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6F0F7D2-C9B9-4D8F-0FA1-F77C4F9D9D03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6F0F7D2-C9B9-4D8F-0FA1-F77C4F9D9D03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6F0F7D2-C9B9-4D8F-0FA1-F77C4F9D9D03}.Release|Any CPU.Build.0 = Release|Any CPU + {F7F1F8D3-DAC0-4E9F-1FB2-F88D5F0E0E04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7F1F8D3-DAC0-4E9F-1FB2-F88D5F0E0E04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7F1F8D3-DAC0-4E9F-1FB2-F88D5F0E0E04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7F1F8D3-DAC0-4E9F-1FB2-F88D5F0E0E04}.Release|Any CPU.Build.0 = Release|Any CPU + {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CF111D9-33E6-4A11-8FEC-F23300A78D15}.Release|Any CPU.Build.0 = Release|Any CPU {2D97C971-20BF-40DB-94AA-3279F787D3CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2D97C971-20BF-40DB-94AA-3279F787D3CB}.Debug|Any CPU.Build.0 = Debug|Any CPU {2D97C971-20BF-40DB-94AA-3279F787D3CB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -55,5 +78,9 @@ Global {2E9427C7-144F-488E-A29D-789ACC1C32AE} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} {6BE10C39-4127-446C-818B-7976FCDD51D5} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} {B7DC44F7-D3A3-4C70-9025-513E0182B646} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} + {C4D8F5D0-E6A7-4B7D-B8E9-F55C3F8D9D01} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} + {D5E9F6D1-B8A8-4C7E-9FA0-F66C3F8D9D02} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} + {E6F0F7D2-C9B9-4D8F-0FA1-F77C4F9D9D03} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} + {F7F1F8D3-DAC0-4E9F-1FB2-F88D5F0E0E04} = {1B9AC248-76F8-44DD-958D-F1DC08EE1E87} EndGlobalSection EndGlobal diff --git a/lib/momentum/Dmi.cs b/lib/momentum/Dmi.cs index d71b284f..432e2c53 100644 --- a/lib/momentum/Dmi.cs +++ b/lib/momentum/Dmi.cs @@ -24,10 +24,13 @@ namespace QuanTAlib; /// /// Formula: /// TR = max(high-low, abs(high-prevClose), abs(low-prevClose)) -/// +DM = if(high-prevHigh > prevLow-low) then max(high-prevHigh, 0) else 0 -/// -DM = if(prevLow-low > high-prevHigh) then max(prevLow-low, 0) else 0 -/// +DI = 100 * smoothed(+DM) / smoothed(TR) -/// -DI = 100 * smoothed(-DM) / smoothed(TR) +/// +DM = if(high-prevHigh > prevLow-low && high-prevHigh > 0) then high-prevHigh else 0 +/// -DM = if(prevLow-low > high-prevHigh && prevLow-low > 0) then prevLow-low else 0 +/// Smoothed TR = Wilder's smoothing of TR (ATR) +/// Smoothed +DM = Wilder's smoothing of +DM +/// Smoothed -DM = Wilder's smoothing of -DM +/// +DI = 100 * Smoothed(+DM) / Smoothed(TR) +/// -DI = 100 * Smoothed(-DM) / Smoothed(TR) /// /// Sources: /// J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978) @@ -36,49 +39,41 @@ namespace QuanTAlib; /// Note: Default period of 14 was recommended by Wilder /// [SkipLocalsInit] -public sealed class Dmi : AbstractBarBase +public sealed class Dmi : AbstractBase { - private readonly Rma _smoothedTr; + private readonly Atr _atr; private readonly Rma _smoothedPlusDm; private readonly Rma _smoothedMinusDm; - private double _prevHigh, _prevLow, _prevClose; - private double _p_prevHigh, _p_prevLow, _p_prevClose; + private double _prevHigh, _prevLow; + private double _p_prevHigh, _p_prevLow; private double _plusDi, _minusDi; private const double ScalingFactor = 100.0; private const int DefaultPeriod = 14; - /// - /// Gets the most recent +DI value - /// public double PlusDI => _plusDi; - - /// - /// Gets the most recent -DI value - /// public double MinusDI => _minusDi; - /// The number of periods used in the DMI calculation (default 14). - /// Thrown when period is less than 1. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Dmi(int period = DefaultPeriod) { if (period < 1) throw new ArgumentOutOfRangeException(nameof(period)); - _smoothedTr = new(period, useSma: true); - _smoothedPlusDm = new(period, useSma: true); - _smoothedMinusDm = new(period, useSma: true); - _index = 0; + _atr = new(period); + _smoothedPlusDm = new(period); + _smoothedMinusDm = new(period); WarmupPeriod = period + 1; Name = $"DMI({period})"; } - /// The data source object that publishes updates. - /// The number of periods used in the DMI calculation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Dmi(object source, int period) : this(period) + public override void Init() { - var pubEvent = source.GetType().GetEvent("Pub"); - pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + base.Init(); + _atr.Init(); + _smoothedPlusDm.Init(); + _smoothedMinusDm.Init(); + _prevHigh = _prevLow = double.NaN; + _p_prevHigh = _p_prevLow = double.NaN; + _plusDi = _minusDi = 0; + _index = 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -89,25 +84,14 @@ public sealed class Dmi : AbstractBarBase _index++; _p_prevHigh = _prevHigh; _p_prevLow = _prevLow; - _p_prevClose = _prevClose; } else { _prevHigh = _p_prevHigh; _prevLow = _p_prevLow; - _prevClose = _p_prevClose; } } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static double CalculateTrueRange(double high, double low, double prevClose) - { - double hl = high - low; - double hpc = Math.Abs(high - prevClose); - double lpc = Math.Abs(low - prevClose); - return Math.Max(hl, Math.Max(hpc, lpc)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static (double plusDm, double minusDm) CalculateDirectionalMovement( double high, double low, double prevHigh, double prevLow) @@ -115,13 +99,8 @@ public sealed class Dmi : AbstractBarBase double upMove = high - prevHigh; double downMove = prevLow - low; - double plusDm = 0.0; - double minusDm = 0.0; - - if (upMove > downMove && upMove > 0) - plusDm = upMove; - else if (downMove > upMove && downMove > 0) - minusDm = downMove; + double plusDm = (upMove > downMove && upMove > 0) ? upMove : 0; + double minusDm = (downMove > upMove && downMove > 0) ? downMove : 0; return (plusDm, minusDm); } @@ -129,38 +108,36 @@ public sealed class Dmi : AbstractBarBase [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected override double Calculation() { - ManageState(Input.IsNew); + ManageState(BarInput.IsNew); - if (_index == 1) + if (double.IsNaN(_prevHigh)) { - _prevHigh = Input.High; - _prevLow = Input.Low; - _prevClose = Input.Close; + _prevHigh = BarInput.High; + _prevLow = BarInput.Low; return 0.0; } - // Calculate True Range and Directional Movement - double tr = CalculateTrueRange(Input.High, Input.Low, _prevClose); + // Calculate ATR + double atr = _atr.Calc(BarInput).Value; + + // Calculate Directional Movement var (plusDm, minusDm) = CalculateDirectionalMovement( - Input.High, Input.Low, _prevHigh, _prevLow); + BarInput.High, BarInput.Low, _prevHigh, _prevLow); - // Update previous values - _prevHigh = Input.High; - _prevLow = Input.Low; - _prevClose = Input.Close; + // Update previous values for next calculation + _prevHigh = BarInput.High; + _prevLow = BarInput.Low; - // Smooth the indicators using Wilder's method - _smoothedTr.Calc(tr, Input.IsNew); - _smoothedPlusDm.Calc(plusDm, Input.IsNew); - _smoothedMinusDm.Calc(minusDm, Input.IsNew); + // Smooth DM values using Wilder's method + double smoothedPlusDm = _smoothedPlusDm.Calc(plusDm, BarInput.IsNew).Value; + double smoothedMinusDm = _smoothedMinusDm.Calc(minusDm, BarInput.IsNew).Value; - // Calculate +DI and -DI - double smoothedTr = _smoothedTr.Value; - if (smoothedTr > 0) + // Calculate DI values + if (atr > 0) { - _plusDi = ScalingFactor * _smoothedPlusDm.Value / smoothedTr; - _minusDi = ScalingFactor * _smoothedMinusDm.Value / smoothedTr; - return _plusDi - _minusDi; // Return the difference as main value + _plusDi = ScalingFactor * smoothedPlusDm / atr; + _minusDi = ScalingFactor * smoothedMinusDm / atr; + return _plusDi - _minusDi; } _plusDi = 0.0; diff --git a/lib/momentum/Dmx.cs b/lib/momentum/Dmx.cs index b3c3efbf..a5acf5e6 100644 --- a/lib/momentum/Dmx.cs +++ b/lib/momentum/Dmx.cs @@ -4,16 +4,13 @@ namespace QuanTAlib; /// /// DMX: Enhanced Directional Movement Index using JMA smoothing /// An improvement over the traditional DMI indicator that uses Jurik Moving Average (JMA) -/// for smoothing instead of Wilder's moving average. This enhancement provides better -/// noise reduction while maintaining responsiveness to significant price movements. +/// for smoothing. This enhancement provides better noise reduction while maintaining +/// responsiveness to significant price movements. /// /// /// The DMX calculation process: -/// 1. Calculate True Range (TR) -/// 2. Calculate +DM (Positive Directional Movement) -/// 3. Calculate -DM (Negative Directional Movement) -/// 4. Smooth TR, +DM, and -DM using JMA instead of Wilder's smoothing -/// 5. Calculate +DI and -DI as percentages +/// 1. Calculate DMI using the standard Dmi class +/// 2. Apply JMA smoothing to the +DI and -DI values /// /// Key improvements over DMI: /// - Uses JMA's adaptive volatility-based smoothing @@ -22,11 +19,9 @@ namespace QuanTAlib; /// - Reduced lag through JMA's phase-shifting /// /// Formula: -/// TR = max(high-low, abs(high-prevClose), abs(low-prevClose)) -/// +DM = if(high-prevHigh > prevLow-low) then max(high-prevHigh, 0) else 0 -/// -DM = if(prevLow-low > high-prevHigh) then max(prevLow-low, 0) else 0 -/// +DI = 100 * JMA(+DM) / JMA(TR) -/// -DI = 100 * JMA(-DM) / JMA(TR) +/// DMI calculation as per standard DMI +/// DMX +DI = JMA(DMI +DI) +/// DMX -DI = JMA(DMI -DI) /// /// Sources: /// Original DMI by J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978) @@ -35,53 +30,40 @@ namespace QuanTAlib; [SkipLocalsInit] public sealed class Dmx : AbstractBarBase { - private readonly Jma _smoothedTr; - private readonly Jma _smoothedPlusDm; - private readonly Jma _smoothedMinusDm; - private double _prevHigh, _prevLow, _prevClose; - private double _p_prevHigh, _p_prevLow, _p_prevClose; + private readonly Dmi _dmi; + private readonly Jma _smoothedPlusDi; + private readonly Jma _smoothedMinusDi; private double _plusDi, _minusDi; - private const double ScalingFactor = 100.0; - private const int DefaultPeriod = 10; + private const int DefaultDmiPeriod = 14; + private const int DefaultJmaPeriod = 7; private const int DefaultPhase = 100; private const double DefaultFactor = 0.25; /// - /// Gets the most recent +DI value + /// Gets the most recent smoothed +DI value /// public double PlusDI => _plusDi; /// - /// Gets the most recent -DI value + /// Gets the most recent smoothed -DI value /// public double MinusDI => _minusDi; - /// The number of periods used in the DMX calculation (default 14). - /// The phase for the JMA smoothing (default 0). - /// The factor for the JMA smoothing (default 0.45). + /// The number of periods used in the DMI calculation (default 14). + /// The number of periods used in the JMA smoothing (default 10). + /// The phase for the JMA smoothing (default 100). + /// The factor for the JMA smoothing (default 0.25). /// Thrown when period is less than 1. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Dmx(int period = DefaultPeriod, int phase = DefaultPhase, double factor = DefaultFactor) + public Dmx(int period = DefaultDmiPeriod, int jmaPeriod = DefaultJmaPeriod, int phase = DefaultPhase, double factor = DefaultFactor) { - if (period < 1) - throw new ArgumentOutOfRangeException(nameof(period)); - _smoothedTr = new(period, phase, factor); - _smoothedPlusDm = new(period, phase, factor); - _smoothedMinusDm = new(period, phase, factor); - _index = 0; - WarmupPeriod = period * 2; // JMA needs more warmup periods than RMA - Name = $"DMX({period})"; - } - - /// The data source object that publishes updates. - /// The number of periods used in the DMX calculation. - /// The phase for the JMA smoothing. - /// The factor for the JMA smoothing. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Dmx(object source, int period, int phase = DefaultPhase, double factor = DefaultFactor) : this(period, phase, factor) - { - var pubEvent = source.GetType().GetEvent("Pub"); - pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + if (period < 1 || jmaPeriod < 1) + throw new ArgumentOutOfRangeException(nameof(period), "Periods must be greater than or equal to 1."); + _dmi = new(period); + _smoothedPlusDi = new(jmaPeriod, phase, factor); + _smoothedMinusDi = new(jmaPeriod, phase, factor); + WarmupPeriod = period + jmaPeriod; + Name = $"DMX({period},{jmaPeriod})"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -90,43 +72,7 @@ public sealed class Dmx : AbstractBarBase if (isNew) { _index++; - _p_prevHigh = _prevHigh; - _p_prevLow = _prevLow; - _p_prevClose = _prevClose; } - else - { - _prevHigh = _p_prevHigh; - _prevLow = _p_prevLow; - _prevClose = _p_prevClose; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static double CalculateTrueRange(double high, double low, double prevClose) - { - double hl = high - low; - double hpc = Math.Abs(high - prevClose); - double lpc = Math.Abs(low - prevClose); - return Math.Max(hl, Math.Max(hpc, lpc)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static (double plusDm, double minusDm) CalculateDirectionalMovement( - double high, double low, double prevHigh, double prevLow) - { - double upMove = high - prevHigh; - double downMove = prevLow - low; - - double plusDm = 0.0; - double minusDm = 0.0; - - if (upMove > downMove && upMove > 0) - plusDm = upMove; - else if (downMove > upMove && downMove > 0) - minusDm = downMove; - - return (plusDm, minusDm); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -134,40 +80,13 @@ public sealed class Dmx : AbstractBarBase { ManageState(Input.IsNew); - if (_index == 1) - { - _prevHigh = Input.High; - _prevLow = Input.Low; - _prevClose = Input.Close; - return 0.0; - } + // Calculate DMI + _dmi.Calc(Input); - // Calculate True Range and Directional Movement - double tr = CalculateTrueRange(Input.High, Input.Low, _prevClose); - var (plusDm, minusDm) = CalculateDirectionalMovement( - Input.High, Input.Low, _prevHigh, _prevLow); + // Smooth the DMI values using JMA + _plusDi = _smoothedPlusDi.Calc(_dmi.PlusDI, Input.IsNew).Value; + _minusDi = _smoothedMinusDi.Calc(_dmi.MinusDI, Input.IsNew).Value; - // Update previous values - _prevHigh = Input.High; - _prevLow = Input.Low; - _prevClose = Input.Close; - - // Smooth the indicators using JMA - _smoothedTr.Calc(tr, Input.IsNew); - _smoothedPlusDm.Calc(plusDm, Input.IsNew); - _smoothedMinusDm.Calc(minusDm, Input.IsNew); - - // Calculate +DI and -DI - double smoothedTr = _smoothedTr.Value; - if (smoothedTr > 0) - { - _plusDi = ScalingFactor * _smoothedPlusDm.Value / smoothedTr; - _minusDi = ScalingFactor * _smoothedMinusDm.Value / smoothedTr; - return _plusDi - _minusDi; // Return the difference as main value - } - - _plusDi = 0.0; - _minusDi = 0.0; - return 0.0; + return _plusDi - _minusDi; // Return the difference as main value } } diff --git a/lib/momentum/Dpo.cs b/lib/momentum/Dpo.cs index 87273cb4..1e19bed6 100644 --- a/lib/momentum/Dpo.cs +++ b/lib/momentum/Dpo.cs @@ -82,12 +82,13 @@ public sealed class Dpo : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected override double Calculation() { + ManageState(BarInput.IsNew); // Add current price to buffer - _prices.Add(BarInput.Close); - + _prices.Add(BarInput.Close, BarInput.IsNew); // Need enough prices for the shifted SMA calculation + if (_index <= _shift) { return 0; @@ -96,12 +97,6 @@ public sealed class Dpo : AbstractBase // Add price from shift periods ago to SMA buffer _sma.Add(_prices[_shift]); - // Need enough prices for full calculation - if (_index <= WarmupPeriod) - { - return 0; - } - // Calculate DPO double dpo = BarInput.Close - _sma.Average(); diff --git a/lib/momentum/_list.md b/lib/momentum/_list.md index 85cc2072..ccfdf582 100644 --- a/lib/momentum/_list.md +++ b/lib/momentum/_list.md @@ -1,5 +1,4 @@ # Momentum indicators -Done: 15, Todo: 2 ✔️ ADX - Average Directional Movement Index ✔️ ADXR - Average Directional Movement Index Rating diff --git a/quantower/Averages/AfirmaIndicator.cs b/quantower/Averages/AfirmaIndicator.cs index a094c320..486052da 100644 --- a/quantower/Averages/AfirmaIndicator.cs +++ b/quantower/Averages/AfirmaIndicator.cs @@ -50,7 +50,7 @@ public class AfirmaIndicator : Indicator, IWatchlistIndicator Name = "AFIRMA - Adaptive Finite Impulse Response Moving Average"; Description = "Adaptive Finite Impulse Response Moving Average with ARMA component"; - Series = new(name: $"AFIRMA {Taps}:{Periods}:{Window}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"AFIRMA {Taps}:{Periods}:{Window}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/AlmaIndicator.cs b/quantower/Averages/AlmaIndicator.cs index 50b49d30..95bee6b7 100644 --- a/quantower/Averages/AlmaIndicator.cs +++ b/quantower/Averages/AlmaIndicator.cs @@ -46,7 +46,7 @@ public class AlmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "ALMA - Arnaud Legoux Moving Average"; Description = "Arnaud Legoux Moving Average"; - Series = new(name: $"ALMA {Period}:{Offset:F2}:{Sigma:F0}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"ALMA {Period}:{Offset:F2}:{Sigma:F0}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/DemaIndicator.cs b/quantower/Averages/DemaIndicator.cs index 7ca1824f..6db92ee5 100644 --- a/quantower/Averages/DemaIndicator.cs +++ b/quantower/Averages/DemaIndicator.cs @@ -40,7 +40,7 @@ public class DemaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "DEMA - Double Exponential Moving Average"; Description = "A faster-responding moving average that reduces lag by applying the EMA twice."; - Series = new(name: $"DEMA {Period}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"DEMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/DsmaIndicator.cs b/quantower/Averages/DsmaIndicator.cs index 8c22a636..f26fc389 100644 --- a/quantower/Averages/DsmaIndicator.cs +++ b/quantower/Averages/DsmaIndicator.cs @@ -43,7 +43,7 @@ public class DsmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "DSMA - Deviation Scaled Moving Average"; Description = "A moving average that adjusts its responsiveness based on price deviations from the mean."; - Series = new(name: $"DSMA {Period}:{Scale:F2}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"DSMA {Period}:{Scale:F2}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/DwmaIndicator.cs b/quantower/Averages/DwmaIndicator.cs index 6b195215..6f88e496 100644 --- a/quantower/Averages/DwmaIndicator.cs +++ b/quantower/Averages/DwmaIndicator.cs @@ -40,7 +40,7 @@ public class DwmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "DWMA - Double Weighted Moving Average"; Description = "A moving average that applies double weighting to recent prices for increased responsiveness."; - Series = new(name: $"DWMA {Period}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"DWMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/EmaIndicator.cs b/quantower/Averages/EmaIndicator.cs index 3e16cd9b..a50eaf48 100644 --- a/quantower/Averages/EmaIndicator.cs +++ b/quantower/Averages/EmaIndicator.cs @@ -7,7 +7,7 @@ public class EmaIndicator : Indicator, IWatchlistIndicator { [InputParameter("Periods", sortIndex: 1, 1, 1000, 1, 0)] public int Periods { get; set; } = 10; - [InputParameter("Use SMA for warmup period", sortIndex: 2)] + [InputParameter("Use SMA for warmup period", sortIndex: 2)] public bool UseSMA { get; set; } = false; [InputParameter("Data source", sortIndex: 3, variants: [ @@ -42,7 +42,7 @@ public class EmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "EMA - Exponential Moving Average"; Description = "Exponential Moving Average"; - Series = new(name: $"EMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"EMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/EpmaIndicator.cs b/quantower/Averages/EpmaIndicator.cs index 39aad553..eaa32fd0 100644 --- a/quantower/Averages/EpmaIndicator.cs +++ b/quantower/Averages/EpmaIndicator.cs @@ -40,7 +40,7 @@ public class EpmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "EPMA - Exponential Percentage Moving Average"; Description = "Exponential Percentage Moving Average"; - Series = new(name: $"EPMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"EPMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/FramaIndicator.cs b/quantower/Averages/FramaIndicator.cs index ddcceede..038165ae 100644 --- a/quantower/Averages/FramaIndicator.cs +++ b/quantower/Averages/FramaIndicator.cs @@ -40,7 +40,7 @@ public class FramaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "FRAMA - Fractal Adaptive Moving Average"; Description = "Fractal Adaptive Moving Average"; - Series = new(name: $"FRAMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"FRAMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/FwmaIndicator.cs b/quantower/Averages/FwmaIndicator.cs index bb9c89a1..2a6a90a2 100644 --- a/quantower/Averages/FwmaIndicator.cs +++ b/quantower/Averages/FwmaIndicator.cs @@ -40,7 +40,7 @@ public class FwmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "FWMA - Fibonacci Weighted Moving Average"; Description = "Fibonacci Weighted Moving Average"; - Series = new(name: $"FWMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"FWMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/GmaIndicator.cs b/quantower/Averages/GmaIndicator.cs index f4cfce92..ab93afe3 100644 --- a/quantower/Averages/GmaIndicator.cs +++ b/quantower/Averages/GmaIndicator.cs @@ -40,7 +40,7 @@ public class GmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "GMA - Gaussian Moving Average"; Description = "Gaussian Moving Average"; - Series = new(name: $"GMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"GMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/HmaIndicator.cs b/quantower/Averages/HmaIndicator.cs index e9241249..d1d159da 100644 --- a/quantower/Averages/HmaIndicator.cs +++ b/quantower/Averages/HmaIndicator.cs @@ -40,7 +40,7 @@ public class HmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "HMA - Hull Moving Average"; Description = "Hull Moving Average"; - Series = new(name: $"HMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"HMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/HtitIndicator.cs b/quantower/Averages/HtitIndicator.cs index 411d8afa..04daa1b4 100644 --- a/quantower/Averages/HtitIndicator.cs +++ b/quantower/Averages/HtitIndicator.cs @@ -37,7 +37,7 @@ public class HtitIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "HTIT - Hilbert Transform Instantaneous Trendline"; Description = "Hilbert Transform Instantaneous Trendline (Note: This indicator may not be fully functional)"; - Series = new(name: "HTIT", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: "HTIT", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/HwmaIndicator.cs b/quantower/Averages/HwmaIndicator.cs index de81b817..b201a20a 100644 --- a/quantower/Averages/HwmaIndicator.cs +++ b/quantower/Averages/HwmaIndicator.cs @@ -49,7 +49,7 @@ public class HwmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "HWMA - Holt-Winter Moving Average"; Description = "Holt-Winter Moving Average"; - Series = new(name: $"HWMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"HWMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/JmaIndicator.cs b/quantower/Averages/JmaIndicator.cs index 5050d3da..382e93c0 100644 --- a/quantower/Averages/JmaIndicator.cs +++ b/quantower/Averages/JmaIndicator.cs @@ -11,7 +11,7 @@ public class JmaIndicator : Indicator, IWatchlistIndicator [InputParameter("Phase", sortIndex: 2, -100, 100, 1, 0)] public int Phase { get; set; } = 0; - [InputParameter("Beta factor", sortIndex: 3, minimum: 0, maximum:5 , increment: 0.01, decimalPlaces: 2)] + [InputParameter("Beta factor", sortIndex: 3, minimum: 0, maximum: 5, increment: 0.01, decimalPlaces: 2)] public double Factor { get; set; } = 0.45; [InputParameter("Data source", sortIndex: 4, variants: [ @@ -34,7 +34,7 @@ public class JmaIndicator : Indicator, IWatchlistIndicator private Jma? ma; protected LineSeries? Series; protected string? SourceName; - public int MinHistoryDepths => Math.Max(65,Periods * 2); + public int MinHistoryDepths => Math.Max(65, Periods * 2); int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; public override string ShortName => $"JMA {Periods}:{Phase}:{Factor:F2}:{SourceName}"; @@ -46,7 +46,7 @@ public class JmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "JMA - Jurik Moving Average"; Description = "Jurik Moving Average (Note: This indicator may have consistency issues)"; - Series = new(name: $"JMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"JMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/KamaIndicator.cs b/quantower/Averages/KamaIndicator.cs index dfc14a90..f84c87df 100644 --- a/quantower/Averages/KamaIndicator.cs +++ b/quantower/Averages/KamaIndicator.cs @@ -46,7 +46,7 @@ public class KamaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "KAMA - Kaufman's Adaptive Moving Average"; Description = "Kaufman's Adaptive Moving Average"; - Series = new(name: $"KAMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"KAMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/LtmaIndicator.cs b/quantower/Averages/LtmaIndicator.cs index 77f2f868..ce1c4ad4 100644 --- a/quantower/Averages/LtmaIndicator.cs +++ b/quantower/Averages/LtmaIndicator.cs @@ -40,7 +40,7 @@ public class LtmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "LTMA - Laguerre Time Moving Average"; Description = "Laguerre Time Moving Average"; - Series = new(name: $"LTMA {Gamma}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"LTMA {Gamma}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/MaafIndicator.cs b/quantower/Averages/MaafIndicator.cs index fc474704..3a5a9647 100644 --- a/quantower/Averages/MaafIndicator.cs +++ b/quantower/Averages/MaafIndicator.cs @@ -43,7 +43,7 @@ public class MaafIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "MAAF - Median Adaptive Averaging Filter"; Description = "Median Adaptive Averaging Filter (Note: This indicator may have consistency issues)"; - Series = new(name: $"MAAF {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"MAAF {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/MamaIndicator.cs b/quantower/Averages/MamaIndicator.cs index 39b31438..09b9e086 100644 --- a/quantower/Averages/MamaIndicator.cs +++ b/quantower/Averages/MamaIndicator.cs @@ -44,7 +44,7 @@ public class MamaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "MAMA - MESA Adaptive Moving Average"; Description = "MESA Adaptive Moving Average"; - MamaSeries = new(name: "MAMA", color: Color.Yellow, width: 2, style: LineStyle.Solid); + MamaSeries = new(name: "MAMA", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); FamaSeries = new(name: "FAMA", color: Color.Red, width: 2, style: LineStyle.Solid); AddLineSeries(MamaSeries); AddLineSeries(FamaSeries); diff --git a/quantower/Averages/MgdiIndicator.cs b/quantower/Averages/MgdiIndicator.cs index 7a865b5b..7e63ffa2 100644 --- a/quantower/Averages/MgdiIndicator.cs +++ b/quantower/Averages/MgdiIndicator.cs @@ -43,7 +43,7 @@ public class MgdiIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "MGDI - McGinley Dynamic Indicator"; Description = "McGinley Dynamic Indicator"; - Series = new(name: $"MGDI {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"MGDI {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/MmaIndicator.cs b/quantower/Averages/MmaIndicator.cs index c7701c74..e4c4fb50 100644 --- a/quantower/Averages/MmaIndicator.cs +++ b/quantower/Averages/MmaIndicator.cs @@ -40,7 +40,7 @@ public class MmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "MMA - Modified Moving Average"; Description = "Modified Moving Average"; - Series = new(name: $"MMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"MMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/PwmaIndicator.cs b/quantower/Averages/PwmaIndicator.cs index 14eb349d..52d9ea72 100644 --- a/quantower/Averages/PwmaIndicator.cs +++ b/quantower/Averages/PwmaIndicator.cs @@ -40,7 +40,7 @@ public class PwmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "PWMA - Pascal's Weighted Moving Average"; Description = "Pascal's Weighted Moving Average"; - Series = new(name: $"PWMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"PWMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/QemaIndicator.cs b/quantower/Averages/QemaIndicator.cs index d9a114eb..1c0e908f 100644 --- a/quantower/Averages/QemaIndicator.cs +++ b/quantower/Averages/QemaIndicator.cs @@ -49,7 +49,7 @@ public class QemaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "QEMA - Quadruple Exponential Moving Average"; Description = "Quadruple Exponential Moving Average"; - Series = new(name: $"QEMA {K1},{K2},{K3},{K4}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"QEMA {K1},{K2},{K3},{K4}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/RemaIndicator.cs b/quantower/Averages/RemaIndicator.cs index 3197b9bf..b48147d3 100644 --- a/quantower/Averages/RemaIndicator.cs +++ b/quantower/Averages/RemaIndicator.cs @@ -43,7 +43,7 @@ public class RemaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "REMA - Regularized Exponential Moving Average"; Description = "Regularized Exponential Moving Average"; - Series = new(name: $"REMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"REMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/RmaIndicator.cs b/quantower/Averages/RmaIndicator.cs index b0cf4306..5265517e 100644 --- a/quantower/Averages/RmaIndicator.cs +++ b/quantower/Averages/RmaIndicator.cs @@ -40,7 +40,7 @@ public class RmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "RMA - Relative Moving Average (Wilder's Moving Average)"; Description = "Relative Moving Average, also known as Wilder's Moving Average"; - Series = new(name: $"RMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"RMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/SinemaIndicator.cs b/quantower/Averages/SinemaIndicator.cs index 0aa8feb8..8c788f8d 100644 --- a/quantower/Averages/SinemaIndicator.cs +++ b/quantower/Averages/SinemaIndicator.cs @@ -40,7 +40,7 @@ public class SinemaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "SINEMA - Sine-Weighted Moving Average"; Description = "Sine-Weighted Moving Average"; - Series = new(name: $"SINEMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"SINEMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/SmaIndicator.cs b/quantower/Averages/SmaIndicator.cs index 692b5545..66b3d853 100644 --- a/quantower/Averages/SmaIndicator.cs +++ b/quantower/Averages/SmaIndicator.cs @@ -39,7 +39,7 @@ public class SmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "SMA - Simple Moving Average"; Description = "Simple Moving Average"; - Series = new(name: $"SMA {Period}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"SMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/SmmaIndicator.cs b/quantower/Averages/SmmaIndicator.cs index befb1816..eae89e0a 100644 --- a/quantower/Averages/SmmaIndicator.cs +++ b/quantower/Averages/SmmaIndicator.cs @@ -40,7 +40,7 @@ public class SmmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "SMMA - Smoothed Moving Average"; Description = "Smoothed Moving Average"; - Series = new(name: $"SMMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"SMMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/T3Indicator.cs b/quantower/Averages/T3Indicator.cs index 7ab6d14e..953cc773 100644 --- a/quantower/Averages/T3Indicator.cs +++ b/quantower/Averages/T3Indicator.cs @@ -46,7 +46,7 @@ public class T3Indicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "T3 - Tillson T3 Moving Average"; Description = "Tillson T3 Moving Average"; - Series = new(name: $"T3 {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"T3 {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/TemaIndicator.cs b/quantower/Averages/TemaIndicator.cs index 23862874..5fe12a90 100644 --- a/quantower/Averages/TemaIndicator.cs +++ b/quantower/Averages/TemaIndicator.cs @@ -40,7 +40,7 @@ public class TemaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "TEMA - Triple Exponential Moving Average"; Description = "Triple Exponential Moving Average"; - Series = new(name: $"TEMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"TEMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/TrimaIndicator.cs b/quantower/Averages/TrimaIndicator.cs index 14e477df..73e138db 100644 --- a/quantower/Averages/TrimaIndicator.cs +++ b/quantower/Averages/TrimaIndicator.cs @@ -40,7 +40,7 @@ public class TrimaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "TRIMA - Triangular Moving Average"; Description = "Triangular Moving Average"; - Series = new(name: $"TRIMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"TRIMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/VidyaIndicator.cs b/quantower/Averages/VidyaIndicator.cs index 5e411693..54a6b18f 100644 --- a/quantower/Averages/VidyaIndicator.cs +++ b/quantower/Averages/VidyaIndicator.cs @@ -46,7 +46,7 @@ public class VidyaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "VIDYA - Variable Index Dynamic Average"; Description = "Variable Index Dynamic Average"; - Series = new(name: $"VIDYA {ShortPeriod}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"VIDYA {ShortPeriod}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/WmaIndicator.cs b/quantower/Averages/WmaIndicator.cs index ba8d8d60..ef1653dd 100644 --- a/quantower/Averages/WmaIndicator.cs +++ b/quantower/Averages/WmaIndicator.cs @@ -40,7 +40,7 @@ public class WmaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "WMA - Weighted Moving Average"; Description = "Weighted Moving Average"; - Series = new(name: $"WMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"WMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Averages/ZlemaIndicator.cs b/quantower/Averages/ZlemaIndicator.cs index a6ffd28e..268d142b 100644 --- a/quantower/Averages/ZlemaIndicator.cs +++ b/quantower/Averages/ZlemaIndicator.cs @@ -41,7 +41,7 @@ public class ZlemaIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "ZLEMA - Zero Lag Exponential Moving Average"; Description = "Zero Lag Exponential Moving Average"; - Series = new(name: $"ZLEMA {Periods}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"ZLEMA {Periods}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Volatility/FlowIndicator.cs b/quantower/Experiments/FlowIndicator.cs similarity index 100% rename from quantower/Volatility/FlowIndicator.cs rename to quantower/Experiments/FlowIndicator.cs diff --git a/quantower/Volatility/TestIndicator.cs b/quantower/Experiments/TestIndicator.cs similarity index 94% rename from quantower/Volatility/TestIndicator.cs rename to quantower/Experiments/TestIndicator.cs index fbb60c9a..665f2b09 100644 --- a/quantower/Volatility/TestIndicator.cs +++ b/quantower/Experiments/TestIndicator.cs @@ -37,7 +37,7 @@ public class TestIndicator : Indicator, IWatchlistIndicator SeparateWindow = false; Name = "TEST"; Description = "test and test and test and more test."; - Series = new(name: $"{Name}", color: Color.Yellow, width: 2, style: LineStyle.Solid); + Series = new(name: $"{Name}", color: IndicatorExtensions.Volatility, width: 2, style: LineStyle.Solid); AddLineSeries(Series); } diff --git a/quantower/Experiments/_Experiments.csproj b/quantower/Experiments/_Experiments.csproj new file mode 100644 index 00000000..a788a51e --- /dev/null +++ b/quantower/Experiments/_Experiments.csproj @@ -0,0 +1,30 @@ + + + Experiments + Indicator + bin\$(Configuration)\ + false + + + + + + + + + + + + ..\..\.github\TradingPlatform.BusinessLayer.dll + + + TradingPlatform.BusinessLayer.xml + + + + + + + + diff --git a/quantower/IndicatorExtensions.cs b/quantower/IndicatorExtensions.cs index 1bfd2d5f..7bcbc61d 100644 --- a/quantower/IndicatorExtensions.cs +++ b/quantower/IndicatorExtensions.cs @@ -16,6 +16,12 @@ public enum MaType public static class IndicatorExtensions { + public static readonly Color Averages = Color.FromArgb(255, 255, 128); // #FFFF80 - Yellow + public static readonly Color Volume = Color.FromArgb(128, 255, 128); // #80FF80 - Green + public static readonly Color Volatility = Color.FromArgb(255, 128, 128); // #FF8080 - Red + public static readonly Color Statistics = Color.FromArgb(128, 128, 255); // #8080FF - Blue + public static readonly Color Oscillators = Color.FromArgb(255, 128, 255); // #FF80FF - Magenta + public static readonly Color Momentum = Color.FromArgb(128, 255, 255); // #80FFFF - Cyan public static TValue GetInputValue(this Indicator indicator, UpdateArgs args, SourceType source) { var historicalData = indicator.HistoricalData; @@ -179,7 +185,6 @@ public static class IndicatorExtensions } } - public static void DrawText(this Indicator indicator, PaintChartEventArgs args, string text) { if (indicator.CurrentChart == null) @@ -210,7 +215,3 @@ public static class IndicatorExtensions }; } } - - - - diff --git a/quantower/Momentum/AdxIndicator.cs b/quantower/Momentum/AdxIndicator.cs new file mode 100644 index 00000000..d376d60d --- /dev/null +++ b/quantower/Momentum/AdxIndicator.cs @@ -0,0 +1,53 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class AdxIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 14; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Adx? adx; + protected LineSeries? AdxSeries; + public int MinHistoryDepths => Math.Max(5, Periods * 3); // Need extra periods for ADX calculation + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public AdxIndicator() + { + Name = "ADX - Average Directional Movement Index"; + Description = "Measures the strength of a trend, regardless of its direction."; + SeparateWindow = true; + + AdxSeries = new($"ADX {Periods}", color: IndicatorExtensions.Momentum, 2, LineStyle.Solid); + AddLineSeries(AdxSeries); + } + + protected override void OnInit() + { + adx = new Adx(Periods); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar input = IndicatorExtensions.GetInputBar(this, args); + TValue result = adx!.Calc(input); + + AdxSeries!.SetValue(result.Value); + AdxSeries!.SetMarker(0, Color.Transparent); + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override string ShortName => $"ADX ({Periods})"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, AdxSeries!, adx!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Momentum/AdxrIndicator.cs b/quantower/Momentum/AdxrIndicator.cs new file mode 100644 index 00000000..2a55689a --- /dev/null +++ b/quantower/Momentum/AdxrIndicator.cs @@ -0,0 +1,55 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class AdxrIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 14; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Adxr? adxr; + protected LineSeries? AdxrSeries; + public int MinHistoryDepths => Math.Max(5, Periods * 4); // Need extra periods for ADXR calculation + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public AdxrIndicator() + { + Name = "ADXR - Average Directional Movement Index Rating"; + Description = "Measures trend strength by comparing current ADX with historical ADX values."; + SeparateWindow = true; + + AdxrSeries = new($"ADXR {Periods}", Color.Blue, 2, LineStyle.Solid); + AddLineSeries(AdxrSeries); + } + + protected override void OnInit() + { + adxr = new Adxr(Periods); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar input = IndicatorExtensions.GetInputBar(this, args); + TValue result = adxr!.Calc(input); + + AdxrSeries!.SetValue(result.Value); + AdxrSeries!.SetMarker(0, Color.Transparent); + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override string ShortName => $"ADXR ({Periods})"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintHLine(args, 25, new Pen(color: IndicatorExtensions.Momentum, width: 1)); // Strong trend line + this.PaintHLine(args, 20, new Pen(color: IndicatorExtensions.Momentum, width: 1)); // Weak trend line + this.PaintSmoothCurve(args, AdxrSeries!, adxr!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Momentum/ApoIndicator.cs b/quantower/Momentum/ApoIndicator.cs new file mode 100644 index 00000000..23748888 --- /dev/null +++ b/quantower/Momentum/ApoIndicator.cs @@ -0,0 +1,71 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class ApoIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Fast Period", sortIndex: 1, 1, 2000, 1, 0)] + public int FastPeriod { get; set; } = 12; + + [InputParameter("Slow Period", sortIndex: 2, 1, 2000, 1, 0)] + public int SlowPeriod { get; set; } = 26; + + [InputParameter("Data source", sortIndex: 4, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Apo? apo; + protected LineSeries? ApoSeries; + public int MinHistoryDepths => Math.Max(FastPeriod, SlowPeriod) * 2; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public ApoIndicator() + { + Name = "APO - Absolute Price Oscillator"; + Description = "Shows the difference between two moving averages of different periods."; + SeparateWindow = true; + + ApoSeries = new($"APO {FastPeriod},{SlowPeriod}", color: IndicatorExtensions.Momentum, 2, LineStyle.Solid); + AddLineSeries(ApoSeries); + } + + protected override void OnInit() + { + apo = new Apo(FastPeriod, SlowPeriod); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + TValue result = apo!.Calc(input); + + ApoSeries!.SetValue(result.Value); + ApoSeries!.SetMarker(0, Color.Transparent); + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override string ShortName => $"APO ({FastPeriod},{SlowPeriod})"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, ApoSeries!, apo!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Momentum/DmiIndicator.cs b/quantower/Momentum/DmiIndicator.cs new file mode 100644 index 00000000..7cd72f7b --- /dev/null +++ b/quantower/Momentum/DmiIndicator.cs @@ -0,0 +1,59 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class DmiIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 14; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Dmi? dmi; + protected LineSeries? PlusDiSeries; + protected LineSeries? MinusDiSeries; + public int MinHistoryDepths => Math.Max(5, Periods * 2); + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public DmiIndicator() + { + Name = "DMI - Directional Movement Index"; + Description = "Identifies the directional movement of a price by comparing successive highs and lows."; + SeparateWindow = true; + + PlusDiSeries = new($"+DI {Periods}", color: Color.Red, 2, LineStyle.Solid); + MinusDiSeries = new($"-DI {Periods}", color: Color.Blue, 2, LineStyle.Solid); + AddLineSeries(PlusDiSeries); + AddLineSeries(MinusDiSeries); + } + + protected override void OnInit() + { + dmi = new Dmi(Periods); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar input = IndicatorExtensions.GetInputBar(this, args); + var result = dmi!.Calc(input); + + PlusDiSeries!.SetValue(dmi.PlusDI); + MinusDiSeries!.SetValue(dmi.MinusDI); + PlusDiSeries!.SetMarker(0, Color.Transparent); + MinusDiSeries!.SetMarker(0, Color.Transparent); + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override string ShortName => $"DMI ({Periods})"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, PlusDiSeries!, dmi!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + this.PaintSmoothCurve(args, MinusDiSeries!, dmi!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Momentum/DmxIndicator.cs b/quantower/Momentum/DmxIndicator.cs new file mode 100644 index 00000000..fa55606a --- /dev/null +++ b/quantower/Momentum/DmxIndicator.cs @@ -0,0 +1,68 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class DmxIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("DMI Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int DmiPeriods { get; set; } = 14; + + [InputParameter("JMA Smoothing Periods", sortIndex: 2, 1, 2000, 1, 0)] + public int JmaPeriods { get; set; } = 12; + + [InputParameter("JMA Phase", sortIndex: 3, -100, 100, 1, 0)] + public int JmaPhase { get; set; } = 100; + + [InputParameter("JMA Factor", sortIndex: 4, 0.01, 1, 0.01, 2)] + public double JmaFactor { get; set; } = 0.3; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Dmx? dmx; + protected LineSeries? PlusDiSeries; + protected LineSeries? MinusDiSeries; + public int MinHistoryDepths => Math.Max(5, (DmiPeriods + JmaPeriods) * 2); + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public DmxIndicator() + { + Name = "DMX - Enhanced Directional Movement Index"; + Description = "An enhanced version of DMI using JMA smoothing for better noise reduction and responsiveness."; + SeparateWindow = true; + + PlusDiSeries = new($"+DI {DmiPeriods}", color: Color.Red, 2, LineStyle.Solid); + MinusDiSeries = new($"-DI {DmiPeriods}", color: Color.Blue, 2, LineStyle.Solid); + AddLineSeries(PlusDiSeries); + AddLineSeries(MinusDiSeries); + } + + protected override void OnInit() + { + dmx = new Dmx(DmiPeriods, JmaPeriods, JmaPhase, JmaFactor); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar input = IndicatorExtensions.GetInputBar(this, args); + var result = dmx!.Calc(input); + + PlusDiSeries!.SetValue(dmx.PlusDI); + MinusDiSeries!.SetValue(dmx.MinusDI); + PlusDiSeries!.SetMarker(0, Color.Transparent); + MinusDiSeries!.SetMarker(0, Color.Transparent); + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override string ShortName => $"DMX ({DmiPeriods})"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, PlusDiSeries!, dmx!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + this.PaintSmoothCurve(args, MinusDiSeries!, dmx!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Momentum/DpoIndicator.cs b/quantower/Momentum/DpoIndicator.cs new file mode 100644 index 00000000..61ea6f3f --- /dev/null +++ b/quantower/Momentum/DpoIndicator.cs @@ -0,0 +1,67 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class DpoIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)] + public int Period { get; set; } = 20; + + [InputParameter("Data source", sortIndex: 2, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("Show cold values", sortIndex: 3)] + public bool ShowColdValues { get; set; } = true; + + private Dpo? Dpo; + protected LineSeries? DpoSeries; + public int MinHistoryDepths => Period * 2; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public DpoIndicator() + { + Name = "DPO - Detrended Price Oscillator"; + Description = "Removes trend from price by comparing current price to a past moving average, helping identify cycles in the price."; + SeparateWindow = true; + + DpoSeries = new($"DPO {Period}", color: IndicatorExtensions.Momentum, 2, LineStyle.Solid); + AddLineSeries(DpoSeries); + } + + protected override void OnInit() + { + Dpo = new Dpo(Period); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar input = this.GetInputBar(args); + TValue result = Dpo!.Calc(input); + + DpoSeries!.SetValue(result.Value); + DpoSeries!.SetMarker(0, Color.Transparent); + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override string ShortName => $"DPO ({Period})"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, DpoSeries!, Dpo!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Averages/MacdIndicator.cs b/quantower/Momentum/MacdIndicator.cs similarity index 85% rename from quantower/Averages/MacdIndicator.cs rename to quantower/Momentum/MacdIndicator.cs index cc98d2c6..fd39ce81 100644 --- a/quantower/Averages/MacdIndicator.cs +++ b/quantower/Momentum/MacdIndicator.cs @@ -36,9 +36,7 @@ public class MacdIndicator : Indicator, IWatchlistIndicator [InputParameter("Show cold values", sortIndex: 21)] public bool ShowColdValues { get; set; } = true; - private Ema? slow_ma; - private Ema? fast_ma; - private Ema? signal_ma; + private Macd? macd; private Slope? histSlope; protected LineSeries? MainSeries; protected LineSeries? SignalSeries; @@ -58,8 +56,8 @@ public class MacdIndicator : Indicator, IWatchlistIndicator SourceName = Source.ToString(); Name = "MACD - Moving Average Convergence Divergence"; Description = "MACD"; - MainSeries = new(name: $"MAIN", color: Color.Blue, width: 2, style: LineStyle.Solid); - SignalSeries = new(name: $"SIGNAL", color: Color.Yellow, width: 2, style: LineStyle.Solid); + MainSeries = new(name: $"MAIN", color: Color.RoyalBlue, width: 2, style: LineStyle.Solid); + SignalSeries = new(name: $"SIGNAL", color: Color.Red, width: 2, style: LineStyle.Solid); HistogramSeries = new(name: $"HISTOGRAM", color: Color.White, width: 2, style: LineStyle.Solid); HistSlopeSeries = new(name: $"SLOPE", color: Color.Transparent, width: 2, style: LineStyle.Solid); HistSlopeSeries.Visible = false; @@ -72,9 +70,7 @@ public class MacdIndicator : Indicator, IWatchlistIndicator protected override void OnInit() { - slow_ma = new(Slow, useSma: UseSMA); - fast_ma = new(Fast, useSma: UseSMA); - signal_ma = new(Signal, useSma: UseSMA); + macd = new(fastPeriod: Fast, slowPeriod: Slow, signalPeriod: Signal); histSlope = new(2); SourceName = Source.ToString(); base.OnInit(); @@ -83,19 +79,22 @@ public class MacdIndicator : Indicator, IWatchlistIndicator protected override void OnUpdate(UpdateArgs args) { TValue input = this.GetInputValue(args, Source); - slow_ma!.Calc(input); - fast_ma!.Calc(input); - double main = fast_ma.Value - slow_ma.Value; - double signal = signal_ma!.Calc(main); - double histogram = main - signal; + macd!.Calc(input); + + double main = macd.MacdLine; + double signal = macd.SignalLine; + double histogram = macd.Value; histSlope!.Calc(histogram); MainSeries!.SetValue(main); MainSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + SignalSeries!.SetValue(signal); SignalSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + HistogramSeries!.SetValue(histogram); HistogramSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + HistSlopeSeries!.SetValue(histSlope.Value); HistSlopeSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here } @@ -118,7 +117,7 @@ public class MacdIndicator : Indicator, IWatchlistIndicator for (int i = rightIndex; i < leftIndex; i++) { int barX = (int)converter.GetChartX(this.HistoricalData.Time(i)); - int barY = (int)converter.GetChartY(HistogramSeries![i]*2.0); + int barY = (int)converter.GetChartY(HistogramSeries![i] * 2.0); int barY0 = (int)converter.GetChartY(0); int HistBarWidth = this.CurrentChart.BarsWidth - 2; @@ -139,8 +138,8 @@ public class MacdIndicator : Indicator, IWatchlistIndicator } } - this.PaintSmoothCurve(args, MainSeries!, slow_ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.3); - this.PaintSmoothCurve(args, SignalSeries!, slow_ma!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + this.PaintSmoothCurve(args, MainSeries!, macd!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.3); + this.PaintSmoothCurve(args, SignalSeries!, macd!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); base.OnPaintChart(args); } } diff --git a/quantower/Momentum/_Momentum.csproj b/quantower/Momentum/_Momentum.csproj index 29adbe09..f82befdd 100644 --- a/quantower/Momentum/_Momentum.csproj +++ b/quantower/Momentum/_Momentum.csproj @@ -27,4 +27,4 @@ - \ No newline at end of file + diff --git a/quantower/Volatility/RsiIndicator.cs b/quantower/Oscillators/RsiIndicator.cs similarity index 95% rename from quantower/Volatility/RsiIndicator.cs rename to quantower/Oscillators/RsiIndicator.cs index fd0a4eec..0f4fd41d 100644 --- a/quantower/Volatility/RsiIndicator.cs +++ b/quantower/Oscillators/RsiIndicator.cs @@ -37,7 +37,7 @@ public class RsiIndicator : Indicator, IWatchlistIndicator Description = "Measures the speed and magnitude of recent price changes to evaluate overbought or oversold conditions."; SeparateWindow = true; SourceName = Source.ToString(); - RsiSeries = new($"RSI {Periods}", Color.Blue, 2, LineStyle.Solid); + RsiSeries = new($"RSI {Periods}", color: IndicatorExtensions.Oscillators, 2, LineStyle.Solid); AddLineSeries(RsiSeries); } diff --git a/quantower/Volatility/RsxIndicator.cs b/quantower/Oscillators/RsxIndicator.cs similarity index 95% rename from quantower/Volatility/RsxIndicator.cs rename to quantower/Oscillators/RsxIndicator.cs index 5158bc77..16fb8d43 100644 --- a/quantower/Volatility/RsxIndicator.cs +++ b/quantower/Oscillators/RsxIndicator.cs @@ -37,7 +37,7 @@ public class RsxIndicator : Indicator, IWatchlistIndicator Description = "Measures the speed and magnitude of recent price changes to evaluate overbought or oversold conditions."; SeparateWindow = true; SourceName = Source.ToString(); - RsxSeries = new($"RSX {Period}", Color.Blue, 2, LineStyle.Solid); + RsxSeries = new($"RSX {Period}", color: IndicatorExtensions.Oscillators, 2, LineStyle.Solid); AddLineSeries(RsxSeries); } diff --git a/quantower/Statistics/CurvatureIndicator.cs b/quantower/Statistics/CurvatureIndicator.cs index 2180cd61..7bade979 100644 --- a/quantower/Statistics/CurvatureIndicator.cs +++ b/quantower/Statistics/CurvatureIndicator.cs @@ -36,7 +36,7 @@ public class CurvatureIndicator : Indicator, IWatchlistIndicator SeparateWindow = true; SourceName = Source.ToString(); - CurvatureSeries = new("Curvature", Color.Blue, 2, LineStyle.Solid); + CurvatureSeries = new("Curvature", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(CurvatureSeries); } diff --git a/quantower/Statistics/EntropyIndicator.cs b/quantower/Statistics/EntropyIndicator.cs index 5fb2a517..cea46fa5 100644 --- a/quantower/Statistics/EntropyIndicator.cs +++ b/quantower/Statistics/EntropyIndicator.cs @@ -34,7 +34,7 @@ public class EntropyIndicator : Indicator, IWatchlistIndicator SeparateWindow = true; SourceName = Source.ToString(); - EntropySeries = new("Entropy", Color.Blue, 2, LineStyle.Solid); + EntropySeries = new("Entropy", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(EntropySeries); } diff --git a/quantower/Statistics/KurtosisIndicator.cs b/quantower/Statistics/KurtosisIndicator.cs index 620f53dd..4e8a0e3d 100644 --- a/quantower/Statistics/KurtosisIndicator.cs +++ b/quantower/Statistics/KurtosisIndicator.cs @@ -35,7 +35,7 @@ public class KurtosisIndicator : Indicator, IWatchlistIndicator SeparateWindow = true; SourceName = Source.ToString(); - KurtosisSeries = new("Kurtosis", Color.Blue, 2, LineStyle.Solid); + KurtosisSeries = new("Kurtosis", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(KurtosisSeries); } diff --git a/quantower/Statistics/MaxIndicator.cs b/quantower/Statistics/MaxIndicator.cs index 5c696095..ee5edb0f 100644 --- a/quantower/Statistics/MaxIndicator.cs +++ b/quantower/Statistics/MaxIndicator.cs @@ -38,7 +38,7 @@ public class MaxIndicator : Indicator, IWatchlistIndicator SeparateWindow = false; SourceName = Source.ToString(); - MaxSeries = new("Max", Color.Blue, 2, LineStyle.Solid); + MaxSeries = new("Max", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(MaxSeries); } diff --git a/quantower/Statistics/MedianIndicator.cs b/quantower/Statistics/MedianIndicator.cs index 471ace45..c2195066 100644 --- a/quantower/Statistics/MedianIndicator.cs +++ b/quantower/Statistics/MedianIndicator.cs @@ -35,7 +35,7 @@ public class MedianIndicator : Indicator, IWatchlistIndicator SeparateWindow = false; SourceName = Source.ToString(); - MedianSeries = new("Median", Color.Blue, 2, LineStyle.Solid); + MedianSeries = new("Median", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(MedianSeries); } diff --git a/quantower/Statistics/MinIndicator.cs b/quantower/Statistics/MinIndicator.cs index 7f55fcd4..e7545a57 100644 --- a/quantower/Statistics/MinIndicator.cs +++ b/quantower/Statistics/MinIndicator.cs @@ -38,7 +38,7 @@ public class MinIndicator : Indicator, IWatchlistIndicator SeparateWindow = false; SourceName = Source.ToString(); - MinSeries = new("Min", Color.Blue, 2, LineStyle.Solid); + MinSeries = new("Min", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(MinSeries); } diff --git a/quantower/Statistics/ModeIndicator.cs b/quantower/Statistics/ModeIndicator.cs index 7294ed2c..2df23e93 100644 --- a/quantower/Statistics/ModeIndicator.cs +++ b/quantower/Statistics/ModeIndicator.cs @@ -35,7 +35,7 @@ public class ModeIndicator : Indicator, IWatchlistIndicator SeparateWindow = false; SourceName = Source.ToString(); - ModeSeries = new("Mode", Color.Blue, 2, LineStyle.Solid); + ModeSeries = new("Mode", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(ModeSeries); } diff --git a/quantower/Statistics/PercentileIndicator.cs b/quantower/Statistics/PercentileIndicator.cs index a6cc5a2d..96c1cf65 100644 --- a/quantower/Statistics/PercentileIndicator.cs +++ b/quantower/Statistics/PercentileIndicator.cs @@ -38,7 +38,7 @@ public class PercentileIndicator : Indicator, IWatchlistIndicator SeparateWindow = false; SourceName = Source.ToString(); - PercentileSeries = new("Percentile", Color.Blue, 2, LineStyle.Solid); + PercentileSeries = new("Percentile", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(PercentileSeries); } diff --git a/quantower/Statistics/SkewIndicator.cs b/quantower/Statistics/SkewIndicator.cs index 0430c064..decd7444 100644 --- a/quantower/Statistics/SkewIndicator.cs +++ b/quantower/Statistics/SkewIndicator.cs @@ -35,7 +35,7 @@ public class SkewIndicator : Indicator, IWatchlistIndicator SeparateWindow = true; SourceName = Source.ToString(); - SkewSeries = new("Skew", Color.Blue, 2, LineStyle.Solid); + SkewSeries = new("Skew", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(SkewSeries); } diff --git a/quantower/Statistics/SlopeIndicator.cs b/quantower/Statistics/SlopeIndicator.cs index c5d32741..e5534d7c 100644 --- a/quantower/Statistics/SlopeIndicator.cs +++ b/quantower/Statistics/SlopeIndicator.cs @@ -36,7 +36,7 @@ public class SlopeIndicator : Indicator, IWatchlistIndicator SeparateWindow = true; SourceName = Source.ToString(); - SlopeSeries = new("Slope", Color.Blue, 2, LineStyle.Solid); + SlopeSeries = new("Slope", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); LineSeries = new("Regression Line", Color.Red, 1, LineStyle.Solid); AddLineSeries(SlopeSeries); AddLineSeries(LineSeries); diff --git a/quantower/Statistics/StddevIndicator.cs b/quantower/Statistics/StddevIndicator.cs index a9139d91..02d5f57a 100644 --- a/quantower/Statistics/StddevIndicator.cs +++ b/quantower/Statistics/StddevIndicator.cs @@ -38,7 +38,7 @@ public class StddevIndicator : Indicator, IWatchlistIndicator SeparateWindow = true; SourceName = Source.ToString(); - StddevSeries = new("StdDev", Color.Blue, 2, LineStyle.Solid); + StddevSeries = new("StdDev", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(StddevSeries); } diff --git a/quantower/Statistics/VarianceIndicator.cs b/quantower/Statistics/VarianceIndicator.cs index e2c6ca2e..5f3511f9 100644 --- a/quantower/Statistics/VarianceIndicator.cs +++ b/quantower/Statistics/VarianceIndicator.cs @@ -38,7 +38,7 @@ public class VarianceIndicator : Indicator, IWatchlistIndicator SeparateWindow = true; SourceName = Source.ToString(); - VarianceSeries = new("Variance", Color.Blue, 2, LineStyle.Solid); + VarianceSeries = new("Variance", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(VarianceSeries); } diff --git a/quantower/Statistics/ZscoreIndicator.cs b/quantower/Statistics/ZscoreIndicator.cs index e2a52ead..ba70a8a5 100644 --- a/quantower/Statistics/ZscoreIndicator.cs +++ b/quantower/Statistics/ZscoreIndicator.cs @@ -35,7 +35,7 @@ public class ZscoreIndicator : Indicator, IWatchlistIndicator SeparateWindow = true; SourceName = Source.ToString(); - ZscoreSeries = new("Z-Score", Color.Blue, 2, LineStyle.Solid); + ZscoreSeries = new("Z-Score", color: IndicatorExtensions.Statistics, 2, LineStyle.Solid); AddLineSeries(ZscoreSeries); } diff --git a/quantower/Volatility/AtrIndicator.cs b/quantower/Volatility/AtrIndicator.cs index 614fecdb..0999533a 100644 --- a/quantower/Volatility/AtrIndicator.cs +++ b/quantower/Volatility/AtrIndicator.cs @@ -47,7 +47,7 @@ public class AtrIndicator : Indicator, IWatchlistIndicator public override void OnPaintChart(PaintChartEventArgs args) { base.OnPaintChart(args); - this.PaintHLine(args, 0.05, new Pen(Color.DarkRed, width: 2)); + this.PaintHLine(args, 0.05, new Pen(color: IndicatorExtensions.Volatility, width: 2)); this.PaintSmoothCurve(args, AtrSeries!, atr!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); } } diff --git a/quantower/Volatility/CmoIndicator.cs b/quantower/Volatility/CmoIndicator.cs index a5ec940c..a507d652 100644 --- a/quantower/Volatility/CmoIndicator.cs +++ b/quantower/Volatility/CmoIndicator.cs @@ -38,7 +38,7 @@ public class CmoIndicator : Indicator, IWatchlistIndicator Description = "Measures the momentum of price changes using the difference between the sum of recent gains and the sum of recent losses."; SeparateWindow = true; SourceName = Source.ToString(); - CmoSeries = new($"CMO {Periods}", Color.Blue, 2, LineStyle.Solid); + CmoSeries = new($"CMO {Periods}", color: IndicatorExtensions.Volatility, 2, LineStyle.Solid); AddLineSeries(CmoSeries); } diff --git a/quantower/Volatility/CviIndicator.cs b/quantower/Volatility/CviIndicator.cs index 7a43523f..be14de29 100644 --- a/quantower/Volatility/CviIndicator.cs +++ b/quantower/Volatility/CviIndicator.cs @@ -22,7 +22,7 @@ public class CviIndicator : Indicator, IWatchlistIndicator Description = "Measures the volatility of a financial instrument by comparing the spread between the high and low prices."; SeparateWindow = true; - CviSeries = new($"CVI {Periods}", Color.Blue, 2, LineStyle.Solid); + CviSeries = new($"CVI {Periods}", color: IndicatorExtensions.Volatility, 2, LineStyle.Solid); AddLineSeries(CviSeries); } @@ -48,7 +48,7 @@ public class CviIndicator : Indicator, IWatchlistIndicator public override void OnPaintChart(PaintChartEventArgs args) { base.OnPaintChart(args); - this.PaintHLine(args, 0.05, new Pen(Color.DarkRed, width: 2)); + this.PaintHLine(args, 0.05, new Pen(color: IndicatorExtensions.Volatility, width: 2)); this.PaintSmoothCurve(args, CviSeries!, cvi!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); } } diff --git a/quantower/Volatility/HistoricalIndicator.cs b/quantower/Volatility/HistoricalIndicator.cs index 949b2dde..9637679f 100644 --- a/quantower/Volatility/HistoricalIndicator.cs +++ b/quantower/Volatility/HistoricalIndicator.cs @@ -22,7 +22,7 @@ public class HistoricalIndicator : Indicator, IWatchlistIndicator Description = "Measures price fluctuations over time, indicating market volatility based on past price movements."; SeparateWindow = true; - HvSeries = new("HV", Color.Blue, 2, LineStyle.Solid); + HvSeries = new("HV", color: IndicatorExtensions.Volatility, 2, LineStyle.Solid); AddLineSeries(HvSeries); } diff --git a/quantower/Volatility/JbandsIndicator.cs b/quantower/Volatility/JbandsIndicator.cs index a77312c8..e977bb63 100644 --- a/quantower/Volatility/JbandsIndicator.cs +++ b/quantower/Volatility/JbandsIndicator.cs @@ -39,8 +39,8 @@ public class JbandsIndicator : Indicator, IWatchlistIndicator Description = "Upper and Lower Bands."; SeparateWindow = false; - UbSeries = new("UB", Color.Blue, 2, LineStyle.Solid); - LbSeries = new("LB", Color.Red, 2, LineStyle.Solid); + UbSeries = new("UB", color: IndicatorExtensions.Volatility, 2, LineStyle.Solid); + LbSeries = new("LB", color: IndicatorExtensions.Volatility, 2, LineStyle.Solid); AddLineSeries(UbSeries); AddLineSeries(LbSeries); } diff --git a/quantower/Volatility/JvoltyIndicator.cs b/quantower/Volatility/JvoltyIndicator.cs index 7a49d9bb..c3e5146a 100644 --- a/quantower/Volatility/JvoltyIndicator.cs +++ b/quantower/Volatility/JvoltyIndicator.cs @@ -35,7 +35,7 @@ public class JvoltyIndicator : Indicator, IWatchlistIndicator Description = "Measures market volatility according to Mark Jurik."; SeparateWindow = true; - JvoltySeries = new("JVOLTY", Color.Blue, 2, LineStyle.Solid); + JvoltySeries = new("JVOLTY", color: IndicatorExtensions.Volatility, 2, LineStyle.Solid); AddLineSeries(JvoltySeries); } diff --git a/quantower/Volatility/RealizedIndicator.cs b/quantower/Volatility/RealizedIndicator.cs index 6625a71f..661626f5 100644 --- a/quantower/Volatility/RealizedIndicator.cs +++ b/quantower/Volatility/RealizedIndicator.cs @@ -22,7 +22,7 @@ public class RealizedIndicator : Indicator, IWatchlistIndicator Description = "Measures actual price volatility over a specific period, useful for risk assessment and forecasting."; SeparateWindow = true; - RvSeries = new("RV", Color.Blue, 2, LineStyle.Solid); + RvSeries = new("RV", color: IndicatorExtensions.Volatility, 2, LineStyle.Solid); AddLineSeries(RvSeries); } diff --git a/quantower/Volatility/RviIndicator.cs b/quantower/Volatility/RviIndicator.cs index 005ae679..516f4ac6 100644 --- a/quantower/Volatility/RviIndicator.cs +++ b/quantower/Volatility/RviIndicator.cs @@ -19,7 +19,7 @@ public class RviIndicator : Indicator, IWatchlistIndicator Description = "Measures the direction of volatility, helping to identify overbought or oversold conditions in price."; SeparateWindow = true; - RviSeries = new("RVI", Color.Blue, 2, LineStyle.Solid); + RviSeries = new("RVI", color: IndicatorExtensions.Volatility, 2, LineStyle.Solid); AddLineSeries(RviSeries); } diff --git a/quantower/Volume/ObvIndicator.cs b/quantower/Volume/ObvIndicator.cs new file mode 100644 index 00000000..bbbdb89b --- /dev/null +++ b/quantower/Volume/ObvIndicator.cs @@ -0,0 +1,51 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class ObvIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Obv? obv; + protected LineSeries? ObvSeries; + public int MinHistoryDepths => 5; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public ObvIndicator() + { + Name = "OBV - On-Balance Volume"; + Description = "Measures buying and selling pressure by analyzing volume in relation to price changes."; + SeparateWindow = true; + + ObvSeries = new("OBV", color: IndicatorExtensions.Volume, 2, LineStyle.Solid); + AddLineSeries(ObvSeries); + } + + protected override void OnInit() + { + obv = new Obv(); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar input = IndicatorExtensions.GetInputBar(this, args); + TValue result = obv!.Calc(input); + + ObvSeries!.SetValue(result.Value); + ObvSeries!.SetMarker(0, Color.Transparent); + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override string ShortName => "OBV"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintHLine(args, 0, new Pen(color: Color.DimGray, width: 1)); + this.PaintSmoothCurve(args, ObvSeries!, obv!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Volume/_Volume.csproj b/quantower/Volume/_Volume.csproj index db6a0d69..d2004efd 100644 --- a/quantower/Volume/_Volume.csproj +++ b/quantower/Volume/_Volume.csproj @@ -27,4 +27,4 @@ - \ No newline at end of file + From dd6c68f08f3de3097240de17cb031a7224d0ff13 Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Thu, 7 Nov 2024 04:56:55 +0000 Subject: [PATCH 6/8] [CodeFactor] Apply fixes to commit 582a025 --- lib/momentum/Dpo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/momentum/Dpo.cs b/lib/momentum/Dpo.cs index 1e19bed6..1ba8e4c7 100644 --- a/lib/momentum/Dpo.cs +++ b/lib/momentum/Dpo.cs @@ -82,7 +82,6 @@ public sealed class Dpo : AbstractBase [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected override double Calculation() { - ManageState(BarInput.IsNew); // Add current price to buffer From 69aef2b127f8330798513cec71c778aaf3a36b91 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Thu, 7 Nov 2024 09:55:19 -0800 Subject: [PATCH 7/8] Dpo chart + refactored tests --- Tests/Tests.csproj | 4 + Tests/UpdateTestBase.cs | 91 ++++++++ Tests/test_eventing.cs | 323 +++++++++++++------------- Tests/test_quantower.cs | 55 ++++- Tests/test_updates_oscillators.cs | 246 +++----------------- Tests/test_updates_statistics.cs | 198 +++------------- Tests/test_updates_volatility.cs | 355 +++-------------------------- quantower/Momentum/DpoIndicator.cs | 8 +- 8 files changed, 416 insertions(+), 864 deletions(-) create mode 100644 Tests/UpdateTestBase.cs diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 31f09837..5cef9f7c 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -46,6 +46,10 @@ + + + + diff --git a/Tests/UpdateTestBase.cs b/Tests/UpdateTestBase.cs new file mode 100644 index 00000000..7a245277 --- /dev/null +++ b/Tests/UpdateTestBase.cs @@ -0,0 +1,91 @@ +using Xunit; +using System.Security.Cryptography; + +namespace QuanTAlib.Tests; + +public abstract class UpdateTestBase +{ + protected readonly RandomNumberGenerator rng = RandomNumberGenerator.Create(); + protected const int RandomUpdates = 100; + protected const double ReferenceValue = 100.0; + protected const int precision = 8; + + protected double GetRandomDouble() + { + byte[] bytes = new byte[8]; + rng.GetBytes(bytes); + return ((double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue * 200) - 100; // Range: -100 to 100 + } + + protected 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); + } + + protected void TestTValueUpdate(T indicator, Func calc) where T : class + { + var initialValue = calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + var finalValue = calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue.Value, finalValue.Value, precision); + } + + protected void TestTBarUpdate(T indicator, Func calc) where T : class + { + TBar r = GetRandomBar(true); + var initialValue = calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + calc(GetRandomBar(IsNew: false)); + } + var finalValue = calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue.Value, finalValue.Value, precision); + } + + protected void TestDualTValueUpdate(T indicator, Func calc) where T : class + { + var initialValue = calc( + new TValue(DateTime.Now, ReferenceValue, IsNew: true), + new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + calc( + new TValue(DateTime.Now, GetRandomDouble(), IsNew: false), + new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + var finalValue = calc( + new TValue(DateTime.Now, ReferenceValue, IsNew: false), + new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue.Value, finalValue.Value, precision); + } + + protected void TestDualTBarUpdate(T indicator, Func calc) where T : class + { + TBar bar1 = GetRandomBar(true); + TBar bar2 = GetRandomBar(true); + var initialValue = calc(bar1, bar2); + + for (int i = 0; i < RandomUpdates; i++) + { + calc(GetRandomBar(false), GetRandomBar(false)); + } + var finalValue = calc( + new TBar(bar1.Time, bar1.Open, bar1.High, bar1.Low, bar1.Close, bar1.Volume, false), + new TBar(bar2.Time, bar2.Open, bar2.High, bar2.Low, bar2.Close, bar2.Volume, false)); + + Assert.Equal(initialValue.Value, finalValue.Value, precision); + } +} diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs index b0689b4d..5e4b1665 100644 --- a/Tests/test_eventing.cs +++ b/Tests/test_eventing.cs @@ -1,172 +1,112 @@ using Xunit; using System.Security.Cryptography; +using System.Reflection; -#pragma warning disable S1944, S2053, S2222, S2259, S2583, S2589, S3329, S3655, S3900, S3949, S3966, S4158, S4347, S5773, S6781 - -namespace QuanTAlib; +namespace QuanTAlib.Tests; public class EventingTests { - [Fact] - public void EventBasedCalculations() + private const int TestDataPoints = 200; + private const int DefaultPeriod = 10; + private const double Tolerance = 1e-9; + + private static readonly (string Name, object[] DirectParams, object[] EventParams)[] ValueIndicators = new[] { - // Create a cryptographically secure random number generator - using var rng = RandomNumberGenerator.Create(); + ("Afirma", new object[] { DefaultPeriod, DefaultPeriod, Afirma.WindowType.BlackmanHarris }, new object[] { new TSeries(), DefaultPeriod, DefaultPeriod, Afirma.WindowType.BlackmanHarris }), + ("Alma", new object[] { DefaultPeriod, 0.85, 6.0 }, new object[] { new TSeries(), DefaultPeriod, 0.85, 6.0 }), + ("Convolution", new object[] { new double[] {1,2,3,2,1} }, new object[] { new TSeries(), new double[] {1,2,3,2,1} }), + ("Dema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Dsma", new object[] { DefaultPeriod, 0.9 }, new object[] { new TSeries(), DefaultPeriod, 0.9 }), + ("Dwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Ema", new object[] { DefaultPeriod, true }, new object[] { new TSeries(), DefaultPeriod, true }), + ("Epma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Pwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Frama", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Fwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Gma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Hma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Htit", new object[] { }, new object[] { new TSeries() }), + ("Hwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Jma", new object[] { DefaultPeriod, 0, 0.45, 10 }, new object[] { new TSeries(), DefaultPeriod, 0, 0.45, 10 }), + ("Kama", new object[] { DefaultPeriod, 2, 30 }, new object[] { new TSeries(), DefaultPeriod, 2, 30 }), + ("Ltma", new object[] { 0.2 }, new object[] { new TSeries(), 0.2 }), + ("Maaf", new object[] { 39, 0.002 }, new object[] { new TSeries(), 39, 0.002 }), + ("Mama", new object[] { 0.5, 0.05 }, new object[] { new TSeries(), 0.5, 0.05 }), + ("Mgdi", new object[] { DefaultPeriod, 0.6 }, new object[] { new TSeries(), DefaultPeriod, 0.6 }), + ("Mma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Qema", new object[] { 0.2, 0.2, 0.2, 0.2 }, new object[] { new TSeries(), 0.2, 0.2, 0.2, 0.2 }), + ("Rema", new object[] { DefaultPeriod, 0.5 }, new object[] { new TSeries(), DefaultPeriod, 0.5 }), + ("Rma", new object[] { DefaultPeriod, true }, new object[] { new TSeries(), DefaultPeriod, true }), + ("Sma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Wma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Tema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Zlema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Sinema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Smma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("T3", new object[] { DefaultPeriod, 0.7, true }, new object[] { new TSeries(), DefaultPeriod, 0.7, true }), + ("Trima", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Vidya", new object[] { DefaultPeriod, 0, 0.2 }, new object[] { new TSeries(), DefaultPeriod, 0, 0.2 }), + ("Apo", new object[] { 12, 26 }, new object[] { new TSeries(), 12, 26 }), + ("Macd", new object[] { 12, 26, 9 }, new object[] { new TSeries(), 12, 26, 9 }), + ("Rsi", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Rsx", new object[] { DefaultPeriod, 0, 0.55 }, new object[] { new TSeries(), DefaultPeriod, 0, 0.55 }), + ("Cmo", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Cog", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Curvature", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Entropy", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Kurtosis", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Max", new object[] { DefaultPeriod, 0.0 }, new object[] { new TSeries(), DefaultPeriod, 0.0 }), + ("Median", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Min", new object[] { DefaultPeriod, 0.0 }, new object[] { new TSeries(), DefaultPeriod, 0.0 }), + ("Mode", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Percentile", new object[] { DefaultPeriod, 0.5 }, new object[] { new TSeries(), DefaultPeriod, 0.5 }), + ("Skew", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Slope", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Stddev", new object[] { DefaultPeriod, false }, new object[] { new TSeries(), DefaultPeriod, false }), + ("Variance", new object[] { DefaultPeriod, false }, new object[] { new TSeries(), DefaultPeriod, false }), + ("Zscore", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Beta", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Corr", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Hv", new object[] { DefaultPeriod, false }, new object[] { new TSeries(), DefaultPeriod, false }), + ("Jvolty", new object[] { DefaultPeriod, 0 }, new object[] { new TSeries(), DefaultPeriod, 0 }), + ("Rv", new object[] { DefaultPeriod, false }, new object[] { new TSeries(), DefaultPeriod, false }), + ("Rvi", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Mae", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Mapd", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Mape", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Mase", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Mda", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Me", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Mpe", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Mse", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Msle", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Rae", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Rmse", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Rmsle", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Rse", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Smape", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Rsquared", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), + ("Huber", new object[] { DefaultPeriod, 1.0 }, new object[] { new TSeries(), DefaultPeriod, 1.0 }) + }; - // Create input series to hold our random values - var input = new TSeries(); - var barInput = new TBarSeries(); - int p = 10; + private static readonly (string Name, object[] DirectParams, object[] EventParams)[] BarIndicators = new[] + { + ("Adl", new object[] { }, new object[] { new TBarSeries() }), + ("Adosc", new object[] { 3, 10 }, new object[] { new TBarSeries(), 3, 10 }), + ("Aobv", new object[] { }, new object[] { new TBarSeries() }), + ("Cmf", new object[] { 20 }, new object[] { new TBarSeries(), 20 }), + ("Eom", new object[] { 14 }, new object[] { new TBarSeries(), 14 }), + ("Kvo", new object[] { 34, 55 }, new object[] { new TBarSeries(), 34, 55 }), + ("Atr", new object[] { 14 }, new object[] { new TBarSeries(), 14 }), + ("Chop", new object[] { 14 }, new object[] { new TBarSeries(), 14 }), + ("Dosc", new object[] { }, new object[] { new TBarSeries() }) + }; - // Create a list of value-based indicator pairs - var valueIndicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)> - { - ("Afirma", new Afirma(p,p,Afirma.WindowType.BlackmanHarris), new Afirma(input, p,p,Afirma.WindowType.BlackmanHarris)), - ("Alma", new Alma(p), new Alma(input, p)), - ("Convolution", new Convolution(new double[] {1,2,3,2,1}), new Convolution(input, new double[] {1,2,3,2,1})), - ("Dema", new Dema(p), new Dema(input, p)), - ("Dsma", new Dsma(p), new Dsma(input, p)), - ("Dwma", new Dwma(p), new Dwma(input, p)), - ("Ema", new Ema(p), new Ema(input, p)), - ("Epma", new Epma(p), new Epma(input, p)), - ("Pwma", new Pwma(p), new Pwma(input, p)), - ("Frama", new Frama(p), new Frama(input, p)), - ("Fwma", new Fwma(p), new Fwma(input, p)), - ("Gma", new Gma(p), new Gma(input, p)), - ("Hma", new Hma(p), new Hma(input, p)), - ("Htit", new Htit(), new Htit(input)), - ("Hwma", new Hwma(p), new Hwma(input, p)), - ("Jma", new Jma(p), new Jma(input, p)), - ("Kama", new Kama(p), new Kama(input, p)), - ("Ltma", new Ltma(gamma: 0.2), new Ltma(input, gamma: 0.2)), - ("Maaf", new Maaf(p), new Maaf(input, p)), - ("Mama", new Mama(p), new Mama(input, p)), - ("Mgdi", new Mgdi(p, kFactor: 0.6), new Mgdi(input, p, kFactor: 0.6)), - ("Mma", new Mma(p), new Mma(input, p)), - ("Qema", new Qema(k1: 0.2, k2: 0.2, k3: 0.2, k4: 0.2), new Qema(input, k1: 0.2, k2: 0.2, k3: 0.2, k4: 0.2)), - ("Rema", new Rema(p), new Rema(input, p)), - ("Rma", new Rma(p), new Rma(input, p)), - ("Sma", new Sma(p), new Sma(input, p)), - ("Wma", new Wma(p), new Wma(input, p)), - ("Rma", new Rma(p), new Rma(input, p)), - ("Tema", new Tema(p), new Tema(input, p)), - ("Kama", new Kama(2, 30, 6), new Kama(input, 2, 30, 6)), - ("Zlema", new Zlema(p), new Zlema(input, p)), - ("Sinema", new Sinema(p), new Sinema(input, p)), - ("Smma", new Smma(p), new Smma(input, p)), - ("T3", new T3(p), new T3(input, p)), - ("Trima", new Trima(p), new Trima(input, p)), - ("Vidya", new Vidya(p), new Vidya(input, p)), - ("Apo", new Apo(12, 26), new Apo(input, 12, 26)), - ("Macd", new Macd(12, 26, 9), new Macd(input, 12, 26, 9)), - ("Rsi", new Rsi(p), new Rsi(input, p)), - ("Rsx", new Rsx(p), new Rsx(input, p)), - ("Cmo", new Cmo(p), new Cmo(input, p)), - ("Cog", new Cog(p), new Cog(input, p)), - ("Curvature", new Curvature(p), new Curvature(input, p)), - ("Entropy", new Entropy(p), new Entropy(input, p)), - ("Kurtosis", new Kurtosis(p), new Kurtosis(input, p)), - ("Max", new Max(p), new Max(input, p)), - ("Median", new Median(p), new Median(input, p)), - ("Min", new Min(p), new Min(input, p)), - ("Mode", new Mode(p), new Mode(input, p)), - ("Percentile", new Percentile(p, 0.5), new Percentile(input, p, 0.5)), - ("Skew", new Skew(p), new Skew(input, p)), - ("Slope", new Slope(p), new Slope(input, p)), - ("Stddev", new Stddev(p), new Stddev(input, p)), - ("Variance", new Variance(p), new Variance(input, p)), - ("Zscore", new Zscore(p), new Zscore(input, p)), - ("Beta", new Beta(p), new Beta(input, p)), - ("Corr", new Corr(p), new Corr(input, p)), - // Volatility indicators (value-based) - ("Hv", new Hv(p), new Hv(input, p)), - ("Jvolty", new Jvolty(p), new Jvolty(input, p)), - ("Rv", new Rv(p), new Rv(input, p)), - ("Rvi", new Rvi(p), new Rvi(input, p)), - // Error classes - ("Mae", new Mae(p), new Mae(input, p)), - ("Mapd", new Mapd(p), new Mapd(input, p)), - ("Mape", new Mape(p), new Mape(input, p)), - ("Mase", new Mase(p), new Mase(input, p)), - ("Mda", new Mda(p), new Mda(input, p)), - ("Me", new Me(p), new Me(input, p)), - ("Mpe", new Mpe(p), new Mpe(input, p)), - ("Mse", new Mse(p), new Mse(input, p)), - ("Msle", new Msle(p), new Msle(input, p)), - ("Rae", new Rae(p), new Rae(input, p)), - ("Rmse", new Rmse(p), new Rmse(input, p)), - ("Rmsle", new Rmsle(p), new Rmsle(input, p)), - ("Rse", new Rse(p), new Rse(input, p)), - ("Smape", new Smape(p), new Smape(input, p)), - ("Rsquared", new Rsquared(p), new Rsquared(input, p)), - ("Huber", new Huber(p), new Huber(input, p)) - }; + public static IEnumerable GetValueIndicatorData() + => ValueIndicators.Select(x => new object[] { x.Name, x.DirectParams, x.EventParams }); - // Create a list of bar-based indicator pairs - var barIndicators = new List<(string Name, AbstractBase Direct, AbstractBase EventBased)> - { - // Volume indicators - ("Adl", new Adl(), new Adl(barInput)), - ("Adosc", new Adosc(3, 10), new Adosc(barInput, 3, 10)), - ("Aobv", new Aobv(), new Aobv(barInput)), - ("Cmf", new Cmf(20), new Cmf(barInput, 20)), - ("Eom", new Eom(14), new Eom(barInput, 14)), - ("Kvo", new Kvo(34, 55), new Kvo(barInput, 34, 55)), - // Volatility indicators (bar-based) - ("Atr", new Atr(14), new Atr(barInput, 14)), - // Oscillators (bar-based) - ("Chop", new Chop(14), new Chop(barInput, 14)), - ("Dosc", new Dosc(), new Dosc(barInput)) - }; - - // Generate 200 random values and feed them to indicators - for (int i = 0; i < 200; i++) - { - // Generate random value for value-based indicators - double randomValue = GetRandomDouble(rng) * 100; - input.Add(randomValue); - - // Calculate value-based indicators - foreach (var (_, direct, _) in valueIndicators) - { - direct.Calc(randomValue); - } - - // Generate random bar for bar-based indicators - var bar = new TBar( - DateTime.Now, - randomValue, - randomValue + Math.Abs(GetRandomDouble(rng) * 10), - randomValue - Math.Abs(GetRandomDouble(rng) * 10), - randomValue + (GetRandomDouble(rng) * 5), - Math.Abs(GetRandomDouble(rng) * 1000), - true - ); - barInput.Add(bar); - - // Calculate bar-based indicators - foreach (var (_, direct, _) in barIndicators) - { - direct.Calc(bar); - } - } - - // Compare the results for value-based indicators - foreach (var (name, direct, eventBased) in valueIndicators) - { - bool areEqual = (double.IsNaN(direct.Value) && double.IsNaN(eventBased.Value)) || - Math.Abs(direct.Value - eventBased.Value) < 1e-9; - Assert.True(areEqual, $"Value indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}"); - } - - // Compare the results for bar-based indicators - foreach (var (name, direct, eventBased) in barIndicators) - { - bool areEqual = (double.IsNaN(direct.Value) && double.IsNaN(eventBased.Value)) || - Math.Abs(direct.Value - eventBased.Value) < 1e-9; - Assert.True(areEqual, $"Bar indicator {name} failed: Expected {direct.Value}, Actual {eventBased.Value}"); - } - } + public static IEnumerable GetBarIndicatorData() + => BarIndicators.Select(x => new object[] { x.Name, x.DirectParams, x.EventParams }); private static double GetRandomDouble(RandomNumberGenerator rng) { @@ -174,4 +114,69 @@ public class EventingTests rng.GetBytes(bytes); return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue; } + + private static TBar GenerateRandomBar(RandomNumberGenerator rng, double baseValue) + { + return new TBar( + DateTime.Now, + baseValue, + baseValue + Math.Abs(GetRandomDouble(rng) * 10), + baseValue - Math.Abs(GetRandomDouble(rng) * 10), + baseValue + (GetRandomDouble(rng) * 5), + Math.Abs(GetRandomDouble(rng) * 1000), + true + ); + } + + [Theory] + [MemberData(nameof(GetValueIndicatorData))] + public void ValueIndicatorEventTest(string indicatorName, object[] directParams, object[] eventParams) + { + using var rng = RandomNumberGenerator.Create(); + var input = (TSeries)eventParams[0]; + + // Create indicator instances using reflection + var indicatorType = Type.GetType($"QuanTAlib.{indicatorName}, QuanTAlib")!; + var directIndicator = (AbstractBase)Activator.CreateInstance(indicatorType, directParams)!; + var eventIndicator = (AbstractBase)Activator.CreateInstance(indicatorType, eventParams)!; + + // Generate test data and calculate + for (int i = 0; i < TestDataPoints; i++) + { + double randomValue = GetRandomDouble(rng) * 100; + input.Add(randomValue); + directIndicator.Calc(randomValue); + } + + bool areEqual = (double.IsNaN(directIndicator.Value) && double.IsNaN(eventIndicator.Value)) || + Math.Abs(directIndicator.Value - eventIndicator.Value) < Tolerance; + + Assert.True(areEqual, $"Value indicator {indicatorName} failed: Expected {directIndicator.Value}, Actual {eventIndicator.Value}"); + } + + [Theory] + [MemberData(nameof(GetBarIndicatorData))] + public void BarIndicatorEventTest(string indicatorName, object[] directParams, object[] eventParams) + { + using var rng = RandomNumberGenerator.Create(); + var barInput = (TBarSeries)eventParams[0]; + + // Create indicator instances using reflection + var indicatorType = Type.GetType($"QuanTAlib.{indicatorName}, QuanTAlib")!; + var directIndicator = (AbstractBase)Activator.CreateInstance(indicatorType, directParams)!; + var eventIndicator = (AbstractBase)Activator.CreateInstance(indicatorType, eventParams)!; + + // Generate test data and calculate + for (int i = 0; i < TestDataPoints; i++) + { + var bar = GenerateRandomBar(rng, GetRandomDouble(rng) * 100); + barInput.Add(bar); + directIndicator.Calc(bar); + } + + bool areEqual = (double.IsNaN(directIndicator.Value) && double.IsNaN(eventIndicator.Value)) || + Math.Abs(directIndicator.Value - eventIndicator.Value) < Tolerance; + + Assert.True(areEqual, $"Bar indicator {indicatorName} failed: Expected {directIndicator.Value}, Actual {eventIndicator.Value}"); + } } diff --git a/Tests/test_quantower.cs b/Tests/test_quantower.cs index 23430830..1429698c 100644 --- a/Tests/test_quantower.cs +++ b/Tests/test_quantower.cs @@ -1,6 +1,10 @@ extern alias volatility; extern alias averages; extern alias statistics; +extern alias momentum; +extern alias oscillators; +extern alias volume; +extern alias experiments; using Xunit; using System.Reflection; @@ -8,6 +12,10 @@ using TradingPlatform.BusinessLayer; using statistics::QuanTAlib; using averages::QuanTAlib; using volatility::QuanTAlib; +using momentum::QuanTAlib; +using oscillators::QuanTAlib; +using volume::QuanTAlib; +using experiments::QuanTAlib; namespace QuanTAlib { @@ -43,6 +51,39 @@ namespace QuanTAlib } } + private static void TestIndicatorMultipleFields(string[] fieldNames) where T : Indicator, new() + { + var indicator = new T(); + try + { + var onInitMethod = typeof(T).GetMethod("OnInit", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(onInitMethod); + onInitMethod.Invoke(indicator, null); + var onUpdateMethod = typeof(T).GetMethod("OnUpdate", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(onUpdateMethod); + + foreach (var fieldName in fieldNames) + { + var field = typeof(T).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(field); + var fieldValue = field.GetValue(indicator); + Assert.NotNull(fieldValue); + } + + Assert.NotNull(indicator.ShortName); + Assert.NotEmpty(indicator.ShortName); + Assert.NotNull(indicator.Name); + Assert.NotEmpty(indicator.Name); + Assert.NotNull(indicator.Description); + Assert.NotEmpty(indicator.Description); + Assert.IsAssignableFrom(indicator); + } + catch (Exception ex) + { + throw new Xunit.Sdk.XunitException($"Test failed for {typeof(T).Name}: {ex.Message}"); + } + } + // Averages Indicators [Fact] public void Afirma() => TestIndicator(); [Fact] public void Alma() => TestIndicator(); @@ -95,9 +136,21 @@ namespace QuanTAlib // Volatility Indicators [Fact] public void Atr() => TestIndicator("atr"); - + [Fact] public void Cmo() => TestIndicator("cmo"); + [Fact] public void Cvi() => TestIndicator("cvi"); [Fact] public void Historical() => TestIndicator("historical"); + [Fact] public void Jbands() => TestIndicatorMultipleFields(new[] { "jmaUp", "jmaLo" }); + [Fact] public void Jvolty() => TestIndicator("jma"); [Fact] public void Realized() => TestIndicator("realized"); [Fact] public void Rvi() => TestIndicator("rvi"); + + // Momentum Indicators + [Fact] public void Adx() => TestIndicator("adx"); + [Fact] public void Adxr() => TestIndicator("adxr"); + [Fact] public void Apo() => TestIndicator("apo"); + [Fact] public void Dmi() => TestIndicator("dmi"); + [Fact] public void Dmx() => TestIndicator("dmx"); + [Fact] public void Dpo() => TestIndicator("dpo"); + [Fact] public void Macd() => TestIndicator("macd"); } } diff --git a/Tests/test_updates_oscillators.cs b/Tests/test_updates_oscillators.cs index 24964699..bd049ab9 100644 --- a/Tests/test_updates_oscillators.cs +++ b/Tests/test_updates_oscillators.cs @@ -1,340 +1,160 @@ using Xunit; -using System.Security.Cryptography; namespace QuanTAlib.Tests; -public class OscillatorsUpdateTests +public class OscillatorsUpdateTests : UpdateTestBase { - private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create(); - private const int RandomUpdates = 100; - private const double ReferenceValue = 100.0; - private const int precision = 8; - - private double GetRandomDouble() - { - byte[] bytes = new byte[8]; - rng.GetBytes(bytes); - 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() { var indicator = new Rsi(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Rsx_Update() { var indicator = new Rsx(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Cmo_Update() { var indicator = new Cmo(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Ao_Update() { var indicator = new Ao(); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Ac_Update() { var indicator = new Ac(); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Aroon_Update() { var indicator = new Aroon(period: 25); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Bop_Update() { var indicator = new Bop(); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Cci_Update() { var indicator = new Cci(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Cfo_Update() { var indicator = new Cfo(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Chop_Update() { var indicator = new Chop(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Cog_Update() { var indicator = new Cog(period: 10); - double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + TestTValueUpdate(indicator, indicator.Calc); + } - 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)); + [Fact] + public void Coppock_Update() + { + var indicator = new Coppock(roc1Period: 14, roc2Period: 11, wmaPeriod: 10); + TestTValueUpdate(indicator, indicator.Calc); + } - Assert.Equal(initialValue, finalValue, precision); + [Fact] + public void Crsi_Update() + { + var indicator = new Crsi(period1: 10, period2: 14, period3: 30); + TestTValueUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [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); + TestTValueUpdate(indicator, indicator.Calc); } [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); + TestTValueUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [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); + TestTValueUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Dosc_Update() { var indicator = new Dosc(); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Efi_Update() { var indicator = new Efi(period: 13); - 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); + TestTBarUpdate(indicator, indicator.Calc); } } diff --git a/Tests/test_updates_statistics.cs b/Tests/test_updates_statistics.cs index ff6ed756..c28bc28e 100644 --- a/Tests/test_updates_statistics.cs +++ b/Tests/test_updates_statistics.cs @@ -1,272 +1,132 @@ using Xunit; -using System.Security.Cryptography; namespace QuanTAlib.Tests; -public class StatisticsUpdateTests +public class StatisticsUpdateTests : UpdateTestBase { - private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create(); - private const int RandomUpdates = 100; - private const double ReferenceValue = 100.0; - private const int precision = 8; - - private double GetRandomDouble() - { - byte[] bytes = new byte[8]; - rng.GetBytes(bytes); - 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 Beta_Update() { var indicator = new Beta(period: 14); - TBar marketBar = GetRandomBar(true); - TBar assetBar = GetRandomBar(true); - double initialValue = indicator.Calc(marketBar, assetBar); - - for (int i = 0; i < RandomUpdates; i++) - { - indicator.Calc(GetRandomBar(false), GetRandomBar(false)); - } - double finalValue = indicator.Calc(new TBar(marketBar.Time, marketBar.Open, marketBar.High, marketBar.Low, marketBar.Close, marketBar.Volume, false), - new TBar(assetBar.Time, assetBar.Open, assetBar.High, assetBar.Low, assetBar.Close, assetBar.Volume, false)); - - Assert.Equal(initialValue, finalValue, precision); + TestDualTBarUpdate(indicator, indicator.Calc); } [Fact] public void Corr_Update() { var indicator = new Corr(period: 14); - double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true), new TValue(DateTime.Now, ReferenceValue, IsNew: true)); - - for (int i = 0; i < RandomUpdates; i++) - { - indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false), new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); - } - double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false), new TValue(DateTime.Now, ReferenceValue, IsNew: false)); - - Assert.Equal(initialValue, finalValue, precision); + TestDualTValueUpdate(indicator, indicator.Calc); } [Fact] public void Curvature_Update() { var indicator = new Curvature(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Entropy_Update() { var indicator = new Entropy(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Hurst_Update() { var indicator = new Hurst(period: 100, minLength: 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Kurtosis_Update() { var indicator = new Kurtosis(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Max_Update() { var indicator = new Max(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Median_Update() { var indicator = new Median(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Min_Update() { var indicator = new Min(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Mode_Update() { var indicator = new Mode(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Percentile_Update() { var indicator = new Percentile(period: 14, percent: 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Skew_Update() { var indicator = new Skew(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Slope_Update() { var indicator = new Slope(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Stddev_Update() { var indicator = new Stddev(period: 14); - double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + TestTValueUpdate(indicator, indicator.Calc); + } - 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)); + [Fact] + public void Theil_Update() + { + var indicator = new Theil(period: 14); + TestTValueUpdate(indicator, indicator.Calc); + } - Assert.Equal(initialValue, finalValue, precision); + [Fact] + public void Tsf_Update() + { + var indicator = new Tsf(period: 14); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Variance_Update() { var indicator = new Variance(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Zscore_Update() { var indicator = new Zscore(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } } diff --git a/Tests/test_updates_volatility.cs b/Tests/test_updates_volatility.cs index 85a58ea7..7a9c1314 100644 --- a/Tests/test_updates_volatility.cs +++ b/Tests/test_updates_volatility.cs @@ -1,504 +1,223 @@ using Xunit; -using System.Security.Cryptography; namespace QuanTAlib.Tests; -public class VolatilityUpdateTests +public class VolatilityUpdateTests : UpdateTestBase { - private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create(); - private const int RandomUpdates = 100; - private const double ReferenceValue = 100.0; - private const int precision = 8; - - private double GetRandomDouble() - { - byte[] bytes = new byte[8]; - rng.GetBytes(bytes); - 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 Adr_Update() { var indicator = new Adr(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Atr_Update() { var indicator = new Atr(period: 14); - TBar r = GetRandomBar(true); - double initialValue = indicator.Calc(r); + TestTBarUpdate(indicator, indicator.Calc); + } - 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 Atrs_Update() + { + var indicator = new Atrs(period: 14, factor: 2.0); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Ap_Update() { var indicator = new Ap(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Atrp_Update() { var indicator = new Atrp(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Bband_Update() { var indicator = new Bband(period: 20, multiplier: 2.0); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Ccv_Update() { var indicator = new Ccv(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Ce_Update() { var indicator = new Ce(period: 22, multiplier: 3.0); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Cv_Update() { var indicator = new Cv(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Cvi_Update() { var indicator = new Cvi(period: 10, smoothPeriod: 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); + TestTBarUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Ewma_Update() { var indicator = new Ewma(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Fcb_Update() { var indicator = new Fcb(period: 20, smoothing: 0.5); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Gkv_Update() { var indicator = new Gkv(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Historical_Update() { var indicator = new Hv(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [Fact] public void Hlv_Update() { var indicator = new Hlv(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Jvolty_Update() { var indicator = new Jvolty(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Realized_Update() { var indicator = new Rv(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Rvi_Update() { var indicator = new Rvi(period: 14); - 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); + TestTValueUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Tr_Update() { var indicator = new Tr(); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Ui_Update() { var indicator = new Ui(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Vc_Update() { var indicator = new Vc(period: 20, deviations: 2.0); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Vov_Update() { var indicator = new Vov(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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Vr_Update() { var indicator = new Vr(shortPeriod: 10, longPeriod: 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); + TestTBarUpdate(indicator, indicator.Calc); } [Fact] public void Vs_Update() { var indicator = new Vs(period: 14, multiplier: 2.0); - 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); + TestTBarUpdate(indicator, indicator.Calc); } [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); + TestTBarUpdate(indicator, indicator.Calc); } } diff --git a/quantower/Momentum/DpoIndicator.cs b/quantower/Momentum/DpoIndicator.cs index 61ea6f3f..0937956d 100644 --- a/quantower/Momentum/DpoIndicator.cs +++ b/quantower/Momentum/DpoIndicator.cs @@ -25,7 +25,7 @@ public class DpoIndicator : Indicator, IWatchlistIndicator [InputParameter("Show cold values", sortIndex: 3)] public bool ShowColdValues { get; set; } = true; - private Dpo? Dpo; + private Dpo? dpo; protected LineSeries? DpoSeries; public int MinHistoryDepths => Period * 2; int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; @@ -42,14 +42,14 @@ public class DpoIndicator : Indicator, IWatchlistIndicator protected override void OnInit() { - Dpo = new Dpo(Period); + dpo = new Dpo(Period); base.OnInit(); } protected override void OnUpdate(UpdateArgs args) { TBar input = this.GetInputBar(args); - TValue result = Dpo!.Calc(input); + TValue result = dpo!.Calc(input); DpoSeries!.SetValue(result.Value); DpoSeries!.SetMarker(0, Color.Transparent); @@ -62,6 +62,6 @@ public class DpoIndicator : Indicator, IWatchlistIndicator public override void OnPaintChart(PaintChartEventArgs args) { base.OnPaintChart(args); - this.PaintSmoothCurve(args, DpoSeries!, Dpo!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + this.PaintSmoothCurve(args, DpoSeries!, dpo!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); } } From 5476240bba454c7c065114e0ff64bdd031db9c57 Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Thu, 7 Nov 2024 17:55:39 +0000 Subject: [PATCH 8/8] [CodeFactor] Apply fixes to commit 69aef2b --- Tests/test_eventing.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs index 5e4b1665..34c65028 100644 --- a/Tests/test_eventing.cs +++ b/Tests/test_eventing.cs @@ -25,7 +25,7 @@ public class EventingTests ("Fwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), ("Gma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), ("Hma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), - ("Htit", new object[] { }, new object[] { new TSeries() }), + ("Htit", System.Array.Empty(), new object[] { new TSeries() }), ("Hwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }), ("Jma", new object[] { DefaultPeriod, 0, 0.45, 10 }, new object[] { new TSeries(), DefaultPeriod, 0, 0.45, 10 }), ("Kama", new object[] { DefaultPeriod, 2, 30 }, new object[] { new TSeries(), DefaultPeriod, 2, 30 }), @@ -91,15 +91,15 @@ public class EventingTests private static readonly (string Name, object[] DirectParams, object[] EventParams)[] BarIndicators = new[] { - ("Adl", new object[] { }, new object[] { new TBarSeries() }), + ("Adl", System.Array.Empty(), new object[] { new TBarSeries() }), ("Adosc", new object[] { 3, 10 }, new object[] { new TBarSeries(), 3, 10 }), - ("Aobv", new object[] { }, new object[] { new TBarSeries() }), + ("Aobv", System.Array.Empty(), new object[] { new TBarSeries() }), ("Cmf", new object[] { 20 }, new object[] { new TBarSeries(), 20 }), ("Eom", new object[] { 14 }, new object[] { new TBarSeries(), 14 }), ("Kvo", new object[] { 34, 55 }, new object[] { new TBarSeries(), 34, 55 }), ("Atr", new object[] { 14 }, new object[] { new TBarSeries(), 14 }), ("Chop", new object[] { 14 }, new object[] { new TBarSeries(), 14 }), - ("Dosc", new object[] { }, new object[] { new TBarSeries() }) + ("Dosc", System.Array.Empty(), new object[] { new TBarSeries() }) }; public static IEnumerable GetValueIndicatorData()