From 6231bab9e528bea2dc1703afaefa71a7906ab170 Mon Sep 17 00:00:00 2001 From: Miha Date: Wed, 30 Oct 2024 13:45:36 -0700 Subject: [PATCH] feat: Dpo, Tsi, Vortex, Bpp, Cci, Cfo, Tr, Ui, Vc, Vov, Vr, Vs, Mfi, Nvi, Obv, Pvi, Pvo, Pvol, Pvr, Pvt, Tvi --- Tests/test_updates_momentum.cs | 46 ++++ Tests/test_updates_oscillators.cs | 47 ++++ Tests/test_updates_volatility.cs | 96 ++++++++ Tests/test_updates_volume.cs | 163 +++++++++++++ docs/indicators/indicators.md | 390 ++++++++++++++++-------------- lib/averages/_list.md | 4 +- lib/momentum/Dpo.cs | 112 +++++++++ lib/momentum/Tsi.cs | 151 ++++++++++++ lib/momentum/Vortex.cs | 162 +++++++++++++ lib/momentum/_list.md | 12 +- lib/oscillators/Bop.cs | 66 +++++ lib/oscillators/Cci.cs | 101 ++++++++ lib/oscillators/Cfo.cs | 117 +++++++++ lib/oscillators/_list.md | 12 +- lib/patterns/_list.md | 8 +- lib/statistics/_list.md | 10 +- lib/volatility/Tr.cs | 102 ++++++++ lib/volatility/Ui.cs | 114 +++++++++ lib/volatility/Vc.cs | 164 +++++++++++++ lib/volatility/Vov.cs | 131 ++++++++++ lib/volatility/Vr.cs | 135 +++++++++++ lib/volatility/Vs.cs | 155 ++++++++++++ lib/volatility/_list.md | 27 ++- lib/volatility/todo.md | 29 --- lib/volume/Mfi.cs | 141 +++++++++++ lib/volume/Nvi.cs | 114 +++++++++ lib/volume/Obv.cs | 117 +++++++++ lib/volume/Pvi.cs | 113 +++++++++ lib/volume/Pvo.cs | 111 +++++++++ lib/volume/Pvol.cs | 108 +++++++++ lib/volume/Pvr.cs | 109 +++++++++ lib/volume/Pvt.cs | 105 ++++++++ lib/volume/Tvi.cs | 112 +++++++++ lib/volume/_list.md | 21 +- 34 files changed, 3151 insertions(+), 254 deletions(-) create mode 100644 lib/momentum/Dpo.cs create mode 100644 lib/momentum/Tsi.cs create mode 100644 lib/momentum/Vortex.cs create mode 100644 lib/oscillators/Bop.cs create mode 100644 lib/oscillators/Cci.cs create mode 100644 lib/oscillators/Cfo.cs create mode 100644 lib/volatility/Tr.cs create mode 100644 lib/volatility/Ui.cs create mode 100644 lib/volatility/Vc.cs create mode 100644 lib/volatility/Vov.cs create mode 100644 lib/volatility/Vr.cs create mode 100644 lib/volatility/Vs.cs delete mode 100644 lib/volatility/todo.md create mode 100644 lib/volume/Mfi.cs create mode 100644 lib/volume/Nvi.cs create mode 100644 lib/volume/Obv.cs create mode 100644 lib/volume/Pvi.cs create mode 100644 lib/volume/Pvo.cs create mode 100644 lib/volume/Pvol.cs create mode 100644 lib/volume/Pvr.cs create mode 100644 lib/volume/Pvt.cs create mode 100644 lib/volume/Tvi.cs diff --git a/Tests/test_updates_momentum.cs b/Tests/test_updates_momentum.cs index 7bfe4a92..7338cbdd 100644 --- a/Tests/test_updates_momentum.cs +++ b/Tests/test_updates_momentum.cs @@ -105,6 +105,21 @@ public class MomentumUpdateTests Assert.Equal(initialValue, finalValue, precision); } + [Fact] + public void Dpo_Update() + { + var indicator = new Dpo(period: 20); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + [Fact] public void Pmo_Update() { @@ -214,6 +229,21 @@ public class MomentumUpdateTests Assert.Equal(initialValue, finalValue, precision); } + [Fact] + public void Tsi_Update() + { + var indicator = new Tsi(firstPeriod: 25, secondPeriod: 13); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble() + 100, IsNew: false)); // Ensure positive prices + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + [Fact] public void Vel_Update() { @@ -228,4 +258,20 @@ public class MomentumUpdateTests Assert.Equal(initialValue, finalValue, precision); } + + [Fact] + public void Vortex_Update() + { + var indicator = new Vortex(period: 14); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } } diff --git a/Tests/test_updates_oscillators.cs b/Tests/test_updates_oscillators.cs index 56cdbe93..ba552e4e 100644 --- a/Tests/test_updates_oscillators.cs +++ b/Tests/test_updates_oscillators.cs @@ -109,4 +109,51 @@ public class OscillatorsUpdateTests Assert.Equal(initialValue, finalValue, precision); } + + [Fact] + public void Bop_Update() + { + var indicator = new Bop(); + TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, IsNew: false)); + } + 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 Cci_Update() + { + var indicator = new Cci(period: 20); + TBar r = new(DateTime.Now, ReferenceValue, ReferenceValue, ReferenceValue, ReferenceValue, 1000, IsNew: true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TBar(DateTime.Now, GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), GetRandomDouble(), 1000, IsNew: false)); + } + 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 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); + } } diff --git a/Tests/test_updates_volatility.cs b/Tests/test_updates_volatility.cs index f8877a8e..ed2bedc0 100644 --- a/Tests/test_updates_volatility.cs +++ b/Tests/test_updates_volatility.cs @@ -101,4 +101,100 @@ public class VolatilityUpdateTests Assert.Equal(initialValue, finalValue, precision); } + + [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); + } + + [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); + } + + [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); + } + + [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); + } + + [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); + } + + [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); + } } diff --git a/Tests/test_updates_volume.cs b/Tests/test_updates_volume.cs index 472dd740..66058a26 100644 --- a/Tests/test_updates_volume.cs +++ b/Tests/test_updates_volume.cs @@ -156,4 +156,167 @@ public class VolumeUpdateTests Assert.Equal(initialValue, finalValue, precision); } + + [Fact] + public void Mfi_Update() + { + var indicator = new Mfi(period: 14); + TBar r = GetRandomBar(true); + + // Generate a sequence of bars for warmup + var warmupBars = new List(); + for (int i = 0; i < indicator.WarmupPeriod; i++) + { + var bar = GetRandomBar(IsNew: true); + warmupBars.Add(bar); + indicator.Calc(bar); + } + + // Calculate initial value after warmup + double initialValue = indicator.Calc(r); + + // Apply random updates + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + + // Reset and replay the same sequence + indicator.Init(); + foreach (var bar in warmupBars) + { + indicator.Calc(bar); + } + 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 Nvi_Update() + { + var indicator = new Nvi(); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Obv_Update() + { + var indicator = new Obv(); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Pvi_Update() + { + var indicator = new Pvi(); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Pvol_Update() + { + var indicator = new Pvol(); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Pvo_Update() + { + var indicator = new Pvo(shortPeriod: 12, longPeriod: 26); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Pvr_Update() + { + var indicator = new Pvr(); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Pvt_Update() + { + var indicator = new Pvt(); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Tvi_Update() + { + var indicator = new Tvi(minTick: 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); + } } diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index d0d66932..6848ff29 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -1,183 +1,213 @@ # Indicators in QuanTAlib -⭐= Validation against several TA libraries
-✔️= Validation tests passed
-❌= Issue +\* = Returns multiple values -|**MOMENTUM INDICATORS**|**Class Name**|Skender.Stock|TALib.NETCore| -|--|:--:|:--:|:--:| -|DMI - Directional Movement Index|`?`|GetDmi|| -|DMX - Jurik Directional Movement Index|`?`||| -|MOM - Momentum|`?`||| -|VEL - Jurik Signal Velocity|`?`||| -|ADX - Average Directional Movement Index|`?`|GetAdx|Adx| -|ADXR - Average Directional Movement Index|`?`|Rating|Adxr| -|APO - Absolute Price Oscillator|`?`|Apo|| -|DPO - Detrended Price Oscillator|`?`|GetDpo|| -|MACD - Moving Average Convergence/Divergence|`?`||| -|PO - Price Oscillator|`?`||| -|PPO - Percentage Price Oscillator|`?`||| -|PMO - Price Momentum Oscillator|`?`|GetPmo|| -|PRS - Price Relative Strength|`?`|GetPrs|| -|ROC - Rate of Change|`?`|GetRoc|| -|TRIX - 1-day ROC of TEMA|`?`|GetTrix|| -|VORTEX - Vortex Indicator|`?`||| -
-|**VOLATILITY INDICATORS**|**Class Name**|Skender.Stock|TALib.NETCore| -|ADR - Average Daily Range|`?`||| -|ANDREW - Andrew's Pitchfork|`?`||| -|ATR - Average True Range|`Atr`|GetAtr|Atr| -|ATRP - Average True Range Percent|`?`||| -|ATRSTOP - ATR Trailing Stop|`?`|GetAtrStop|| -|BBANDS - Bollinger Bands®|`?`|BollingerBands|| -|CHAND - Chandelier Exit|`?`|GetChandelier|| -|CVI - Chaikins Volatility|`?`||| -|DON - Donchian Channels|`?`|GetDonchian|| -|FCB - Fractal Chaos Bands|`?`|GetFcb|| -|HV - Historical Volatility|`Hv`||| -|ICH - Ichimoku Cloud|`?`|GetIchimoku|| -|KEL - Keltner Channels|`?`|GetKeltner|| -|NATR - Normalized Average True Range|`?`|GetAtr|| -|CHN - Price Channel Indicator|`?`||| -|SAR - Parabolic Stop and Reverse|`?`|GetParabolicSar|| -|STARC - Starc Bands|`?`|GetStarcBands|| -|TR - True Range|`?`||| -|UI - Ulcer Index|`?`|GetUlcerIndex|| -|VSTOP - Volatility Stop|`?`|GetVolatilityStop|| -
-|**OSCILLATORS**|**Class Name**|Skender.Stock|TALib.NETCore| -|RSI - Relative Strength Index|`Rsi`|GetRsi|| -|RSX - Jurik Trend Strength Index|`Rsx`||| -|AC - Acceleration Oscillator|`?`||| -|AO - Awesome Oscillator|`?`|GetAwesome|| -|AROON - Aroon oscillator|`?`|GetAroon|Aroon| -|BOP - Balance of Power|`?`|GetBop|Bop| -|CCI - Commodity Channel Index|`?`|GetCci|Cci| -|CFO - Chande Forcast Oscillator|`?`||| -|CMO - Chande Momentum Oscillator|`Cmo`|GetCmo|Cmo| -|CHOP - Choppiness Index|`?`|GetChop|| -|COG - Ehler's Center of Gravity|`?`||| -|COPPOCK - Coppock Curve|`?`||| -|CRSI - Connor RSI|`?`|GetConnorsRsi|| -|CTI - Ehler's Correlation Trend Indicator|`?`||| -|DOSC - Derivative Oscillator|`?`||| -|EFI - Elder Ray's Force Index|`?`|GetElderRay|| -|FISHER - Fisher Transform|`?`||| -|FOSC - Forecast Oscillator|`?`||| -|GATOR - Williams Alliator Oscillator|`?`|GetGator|| -|KDJ - KDJ Indicator (trend reversal)|`?`||| -|KRI - Kairi Relative Index|`?`||| -|RVGI - Relative Vigor Index|`?`||| -|SMI - Stochastic Momentum Index|`?`|GetSmi|| -|SRSI - Stochastic RSI|`?`|GetStochRsi|| -|STC - Schaff Trend Cycle|`?`|GetStc|| -|STOCH - Stochastic Oscillator|`?`|GetStoch|| -|TSI - True Strength Index|`?`|GetTsi|| -|UO - Ultimate Oscillator|`?`|GetUltimate|| -|WILLR - Larry Williams' %R|`?`|GetWilliamsR|| -
-|**VOLUME INDICATORS**|**Class Name**|Skender.Stock|TALib.NETCore| -|ADL - Chaikin Accumulation Distribution Line|`?`|GetAdl|Ad| -|ADOSC - Chaikin Accumulation Distribution Oscillator|`?`|GetChaikinOsc|AdOsc| -|AOBV - Archer On-Balance Volume|`?`||| -|CMF - Chaikin Money Flow|`?`|GetCmf|| -|EOM - Ease of Movement|`?`||| -|KVO - Klinger Volume Oscillator|`?`|GetKvo|| -|MFI - Money Flow Index|`?`|GetMfi|| -|NVI - Negative Volume Index|`?`||| -|OBV - On-Balance Volume|`?`|GetObv|| -|PVI - Positive Volume Index|`?`||| -|PVOL - Price-Volume|`?`||| -|PVO - Percentage Volume Oscillator|`?`|GetPvo|| -|PVR - Price Volume Rank|`?`||| -|PVT - Price Volume Trend|`?`||| -|TVI - Trade Volume Index|`?`||| -|VP - Volume Profile|`?`||| -|VWAP - Volume Weighted Average Price|`?`|GetVwap|| -|VWMA - Volume Weighted Moving Average|`?`|GetVwma|| -
-|**NUMERICAL ANALYSIS**|**Class Name**|Skender.Stock|TALib.NETCore| -|BETA - Beta coefficient|`?`||| -|CORR - Correlation Coefficient|`?`||| -|CURVATURE - Rate of Change in Direction or Slope|`Curvature`||| -|ENTROPY - Measure of Uncertainty or Disorder|`Entropy`||| -|KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`||| -|HUBER - Huber Loss|`Huber`||| -|HURST - Hurst Exponent|`?`|GetHurst|| -|MAX - Maximum with exponential decay|`Max`||| -|MEDIAN - Middle value|`Median`||| -|MIN - Minimum with exponential decay|`Min`||| -|MODE - Most Frequent Value|`Mode`||| -|PERCENTILE - Rank Order|`Percentile`||| -|RSQUARED - Coefficient of Determination R-Squared|`?`||| -|SKEW - Skewness, asymmetry of distribution|`Skew`||| -|SLOPE - Rate of Change, Linear Regression|`Slope`||| -|STDDEV - Standard Deviation, Measure of Spread|`Stddev`||| -|THEIL - Theil's U Statistics|`?`||| -|TSF - Time Series Forecast|`?`|✔️|✔️| -|VARIANCE - Average of Squared Deviations|`Variance`||| -|ZSCORE - Standardized Score|`Zscore`||| -
-|**ERRORS**|**Class Name**|Skender.Stock|TALib.NETCore| -|MAE - Mean Absolute Error|`Mae`||| -|MAPD - Mean Absolute Percentage Deviation|`Mapd`||| -|MAPE - Mean Absolute Percentage Error|`Mape`||| -|MASE - Mean Absolute Scaled Error|`Mase`||| -|MDA - Mean Directional Accuracy|`Mda`||| -|ME - Mean Error|`Me`||| -|MPE - Mean Percentage Error|`Mpe`||| -|MSE - Mean Squared Error|`Mse`||| -|MSLE - Mean Squared Logarithmic Error|`Msle`||| -|RAE - Relative Absolute Error|`Rae`||| -|RMSE - Root Mean Squared Error|`Rmse`||| -|RSE - Relative Squared Error|`Rse`||| -|RMSLE - Root Mean Squared Logarithmic Error|`Rmsle`||| -|SMAPE - Symmetric Mean Absolute Percentage Error|`Smape`||| -
-|**AVERAGES & TRENDS**|**Class Name**|Skender.Stock|TALib.NETCore| -|AFIRMA - Autoregressive Finite Impulse Response Moving Average|`Afirma`||| -|ALMA - Arnaud Legoux Moving Average|`Alma`|✔️|| -|DEMA - Double EMA Average|`Dema`|✔️|✔️| -|DSMA - Deviation Scaled Moving Average|`Dsma`||| -|DWMA - Double WMA Average|`Dwma`||| -|EMA - Exponential Moving Average|`Ema`|⭐|⭐| -|EPMA - Endpoint Moving Average|`Epma`|✔️|| -|FRAMA - Fractal Adaptive Moving Average|`Frama`||| -|FWMA - Fibonacci Weighted Moving Average|`Fwma`||| -|HILO - Gann High-Low Activator|`?`||| -|HTIT - Hilbert Transform Instantaneous Trendline|`Htit`|✔️|✔️| -|GMA - Gaussian-Weighted Moving Average|`Gma`||| -|HMA - Hull Moving Average|`Hma`|✔️|✔️| -|HWMA - Holt-Winter Moving Average|`Hwma`||| -|JMA - Jurik Moving Average|`Jma`||| -|JORDAN - Jordan Moving Average|`?`||| -|KAMA - Kaufman's Adaptive Moving Average|`Kama`|✔️|✔️| -|LTMA - Laguerre Transform Moving Average|`Ltma`||| -|MAAF - Median-Average Adaptive Filter|`Maaf`||| -|MAMA - MESA Adaptive Moving Average|`Mama`|✔️|✔️| -|MGDI - McGinley Dynamic Indicator|`Mgdi`|✔️|| -|MLMA - Minimal Lag Moving Average|`?`||| -|MMA - Modified Moving Average|`Mma`||| -|PPMA - Pivot Point Moving Average|`?`||| -|PWMA - Pascal's Weighted Moving Average|`Pwma`||| -|QEMA - Quad Exponential Moving Average|`Qema`||| -|RMA - WildeR's Moving Average|`Rma`||| -|SINEMA - Sine Weighted Moving Average|`Sinema`||| -|SMA - Simple Moving Average|`Sma`||| -|SMMA - Smoothed Moving Average|`Smma`|✔️|| -|SSF - Ehler's Super Smoother Filter|`?`||| -|SUPERTREND - Supertrend|`?`|✔️|| -|T3 - Tillson T3 Moving Average|`T3`|✔️|✔️| -|TEMA - Triple EMA Average|`Tema`|✔️|✔️| -|TRIMA - Triangular Moving Average|`Trima`|✔️|| -|VIDYA - Variable Index Dynamic Average|`Vidya`||| -|WMA - Weighted Moving Average|`Wma`|✔️|| -|ZLEMA - Zero Lag EMA Average|`Zlema`||| -
-|**BASIC TRANSFORMS**|**Class Name**|Skender.Stock|TALib.NETCore| -|OC2 - Midpoint price|`.OC2`|CandlePart.OC2|MidPoint| -|HL2 - Median Price|`.HL2`|CandlePart.HL2|MedPrice| -|HLC3 - Typical Price|`.HLC3`|CandlePart.HLC3|TypPrice| -|OHL3 - Mean Price|`.OHL3`|CandlePart.OHL3|| -|OHLC4 - Average Price|`.OHLC4`|CandlePart.OHLC4|AvgPrice| -|HLCC4 - Weighted Price|`.HLCC4`||WclPrice| +**Implementation Status:** +- Basic Transforms: 6 of 6 complete +- Averages & Trends: 33 of 33 complete +- Momentum: 16 of 17 complete +- Oscillators: 6 of 29 complete +- Volatility: 11 of 35 complete +- Volume: 15 of 19 complete +- Numerical Analysis: 13 of 20 complete +- Errors: 16 of 16 complete +- **Total: 116 of 175 indicators implemented (66%)** + +|**BASIC TRANSFORMS**|**Class Name**| +|--|:--:| +|OC2 - Midpoint price|`.OC2`| +|HL2 - Median Price|`.HL2`| +|HLC3 - Typical Price|`.HLC3`| +|OHL3 - Mean Price|`.OHL3`| +|OHLC4 - Average Price|`.OHLC4`| +|HLCC4 - Weighted Price|`.HLCC4`| + +|**AVERAGES & TRENDS**|**Class Name**| +|--|:--:| +|AFIRMA - Adaptive FIR Moving Average|`Afirma`| +|ALMA - Arnaud Legoux Moving Average|`Alma`| +|DEMA - Double Exponential Moving Average|`Dema`| +|DSMA - Dynamic Simple Moving Average|`Dsma`| +|DWMA - Dynamic Weighted Moving Average|`Dwma`| +|EMA - Exponential Moving Average|`Ema`| +|EPMA - Endpoint Moving Average|`Epma`| +|FRAMA - Fractal Adaptive Moving Average|`Frama`| +|FWMA - Forward Weighted Moving Average|`Fwma`| +|GMA - Gaussian Moving Average|`Gma`| +|HMA - Hull Moving Average|`Hma`| +|HTIT - Hilbert Transform Instantaneous Trendline|`Htit`| +|HWMA - Hann Weighted Moving Average|`Hwma`| +|JMA - Jurik Moving Average|`Jma`| +|KAMA - Kaufman Adaptive Moving Average|`Kama`| +|LTMA - Linear Time Moving Average|`Ltma`| +|MAAF - Moving Average Adaptive Filter|`Maaf`| +|MAMA* - MESA Adaptive Moving Average (MAMA, FAMA)|`Mama`| +|MGDI - McGinley Dynamic Indicator|`Mgdi`| +|MMA - Modified Moving Average|`Mma`| +|PWMA - Parabolic Weighted Moving Average|`Pwma`| +|QEMA - Quick Exponential Moving Average|`Qema`| +|REMA - Regularized Exponential Moving Average|`Rema`| +|RMA - Running Moving Average|`Rma`| +|SINEMA - Sine-weighted Moving Average|`Sinema`| +|SMA - Simple Moving Average|`Sma`| +|SMMA - Smoothed Moving Average|`Smma`| +|T3 - Triple Exponential Moving Average (T3)|`T3`| +|TEMA - Triple Exponential Moving Average|`Tema`| +|TRIMA - Triangular Moving Average|`Trima`| +|VIDYA - Variable Index Dynamic Average|`Vidya`| +|WMA - Weighted Moving Average|`Wma`| +|ZLEMA - Zero-Lag Exponential Moving Average|`Zlema`| + +|**MOMENTUM INDICATORS**|**Class Name**| +|--|:--:| +|ADX - Average Directional Movement Index|`Adx`| +|ADXR - Average Directional Movement Index Rating|`Adxr`| +|APO - Absolute Price Oscillator|`Apo`| +|DMI* - Directional Movement Index (DI+, DI-)|`Dmi`| +|DMX - Jurik Directional Movement Index|`Dmx`| +|DPO - Detrended Price Oscillator|`Dpo`| +|🚧 MACD* - Moving Average Convergence/Divergence|`Macd`| +|MOM - Momentum|`Mom`| +|PMO - Price Momentum Oscillator|`Pmo`| +|PO - Price Oscillator|`Po`| +|PPO - Percentage Price Oscillator|`Ppo`| +|PRS - Price Relative Strength|`Prs`| +|ROC - Rate of Change|`Roc`| +|TSI - True Strength Index|`Tsi`| +|TRIX - 1-day ROC of TEMA|`Trix`| +|VEL - Jurik Signal Velocity|`Vel`| +|VORTEX* - Vortex Indicator (VI+, VI-)|`Vortex`| + +|**OSCILLATORS**|**Class Name**| +|--|:--:| +|AC - Acceleration Oscillator|`Ac`| +|AO - Awesome Oscillator|`Ao`| +|AROON* - Aroon oscillator (Up, Down)|`Aroon`| +|🚧 BOP - Balance of Power|`Bop`| +|🚧 CCI - Commodity Channel Index|`Cci`| +|🚧 CFO - Chande Forcast Oscillator|`Cfo`| +|CMO - Chande Momentum Oscillator|`Cmo`| +|🚧 CHOP - Choppiness Index|`Chop`| +|🚧 COG - Ehler's Center of Gravity|`Cog`| +|🚧 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`| +|🚧 GATOR* - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth)|`Gator`| +|🚧 KDJ* - KDJ Indicator (K, D, J lines)|`Kdj`| +|🚧 KRI - Kairi Relative Index|`Kri`| +|RSI - Relative Strength Index|`Rsi`| +|RSX - Jurik Trend Strength Index|`Rsx`| +|🚧 RVGI* - Relative Vigor Index (RVGI, Signal)|`Rvgi`| +|🚧 SMI - Stochastic Momentum Index|`Smi`| +|🚧 SRSI* - Stochastic RSI (SRSI, Signal)|`Srsi`| +|🚧 STC - Schaff Trend Cycle|`Stc`| +|🚧 STOCH* - Stochastic Oscillator (%K, %D)|`Stoch`| +|🚧 TSI - True Strength Index|`Tsi`| +|🚧 UO - Ultimate Oscillator|`Uo`| +|🚧 WILLR - Larry Williams' %R|`Willr`| + +|**VOLATILITY INDICATORS**|**Class Name**| +|--|:--:| +|🚧 ADR - Average Daily Range|`Adr`| +|🚧 AP - Andrew's Pitchfork|`Ap`| +|ATR - Average True Range|`Atr`| +|🚧 ATRP - Average True Range Percent|`Atrp`| +|🚧 ATRS - ATR Trailing Stop|`Atrs`| +|🚧 BB* - Bollinger Bands® (Upper, Middle, Lower)|`Bb`| +|🚧 CCV - Close-to-Close Volatility|`Ccv`| +|🚧 CE - Chandelier Exit|`Ce`| +|🚧 CV - Conditional Volatility (ARCH/GARCH)|`Cv`| +|🚧 CVI - Chaikin's Volatility|`Cvi`| +|🚧 DC* - Donchian Channels (Upper, Middle, Lower)|`Dc`| +|🚧 EWMA - Exponential Weighted Moving Average Volatility|`Ewma`| +|🚧 FCB - Fractal Chaos Bands|`Fcb`| +|🚧 GKV - Garman-Klass Volatility|`Gkv`| +|🚧 HLV - High-Low Volatility|`Hlv`| +|HV - Historical Volatility|`Hv`| +|🚧 ICH* - Ichimoku Cloud (Conversion, Base, Leading Span A, Leading Span B, Lagging Span)|`Ich`| +|JVOLTY - Jurik Volatility|`Jvolty`| +|🚧 KC* - Keltner Channels (Upper, Middle, Lower)|`Kc`| +|🚧 NATR - Normalized Average True Range|`Natr`| +|🚧 PCH - Price Channel Indicator|`Pch`| +|🚧 PSAR* - Parabolic Stop and Reverse (Value, Trend)|`Psar`| +|🚧 PV - Parkinson Volatility|`Pv`| +|🚧 RSV - Rogers-Satchell Volatility|`Rsv`| +|RV - Realized Volatility|`Rv`| +|RVI - Relative Volatility Index|`Rvi`| +|🚧 STARC* - Starc Bands (Upper, Middle, Lower)|`Starc`| +|🚧 SV - Stochastic Volatility|`Sv`| +|TR - True Range|`Tr`| +|UI - Ulcer Index|`Ui`| +|VC* - Volatility Cone (Mean, Upper Bound, Lower Bound)|`Vc`| +|VOV - Volatility of Volatility|`Vov`| +|VR - Volatility Ratio|`Vr`| +|VS* - Volatility Stop (Long Stop, Short Stop)|`Vs`| +|🚧 YZV - Yang-Zhang Volatility|`Yzv`| + +|**VOLUME INDICATORS**|**Class Name**| +|--|:--:| +|ADL - Chaikin Accumulation Distribution Line|`Adl`| +|ADOSC - Chaikin Accumulation Distribution Oscillator|`Adosc`| +|AOBV - Archer On-Balance Volume|`Aobv`| +|CMF - Chaikin Money Flow|`Cmf`| +|EOM - Ease of Movement|`Eom`| +|KVO - Klinger Volume Oscillator|`Kvo`| +|MFI - Money Flow Index|`Mfi`| +|NVI - Negative Volume Index|`Nvi`| +|OBV - On-Balance Volume|`Obv`| +|PVI - Positive Volume Index|`Pvi`| +|PVOL - Price-Volume|`Pvol`| +|PVO - Percentage Volume Oscillator|`Pvo`| +|PVR - Price Volume Rank|`Pvr`| +|PVT - Price Volume Trend|`Pvt`| +|TVI - Trade Volume Index|`Tvi`| +|🚧 VF - Volume Force|`Vf`| +|🚧 VP - Volume Profile|`Vp`| +|🚧 VWAP - Volume Weighted Average Price|`Vwap`| +|🚧 VWMA - Volume Weighted Moving Average|`Vwma`| + +|**NUMERICAL ANALYSIS**|**Class Name**| +|--|:--:| +|🚧 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`| +|KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`| +|MAX - Maximum with exponential decay|`Max`| +|MEDIAN - Middle value|`Median`| +|MIN - Minimum with exponential decay|`Min`| +|MODE - Most Frequent Value|`Mode`| +|PERCENTILE - Rank Order|`Percentile`| +|🚧 RSQUARED* - Coefficient of Determination (R-squared, Adjusted R-squared)|`Rsquared`| +|SKEW - Skewness, asymmetry of distribution|`Skew`| +|SLOPE - Rate of Change, Linear Regression|`Slope`| +|STDDEV - Standard Deviation, Measure of Spread|`Stddev`| +|🚧 THEIL* - Theil's U Statistics (U1, U2)|`Theil`| +|🚧 TSF* - Time Series Forecast (Forecast, Confidence Interval)|`Tsf`| +|VARIANCE - Average of Squared Deviations|`Variance`| +|ZSCORE - Standardized Score|`Zscore`| + +|**ERRORS**|**Class Name**| +|--|:--:| +|HUBER - Huber Loss|`Huber`| +|MAE - Mean Absolute Error|`Mae`| +|MAPD - Mean Absolute Percentage Deviation|`Mapd`| +|MAPE - Mean Absolute Percentage Error|`Mape`| +|MASE - Mean Absolute Scaled Error|`Mase`| +|MDA - Mean Directional Accuracy|`Mda`| +|ME - Mean Error|`Me`| +|MPE - Mean Percentage Error|`Mpe`| +|MSE - Mean Squared Error|`Mse`| +|MSLE - Mean Squared Logarithmic Error|`Msle`| +|RAE - Relative Absolute Error|`Rae`| +|RMSE - Root Mean Squared Error|`Rmse`| +|RMSLE - Root Mean Squared Logarithmic Error|`Rmsle`| +|RSE - Relative Squared Error|`Rse`| +|RSQUARED - R-Squared (Coefficient of Determination)|`Rsquared`| +|SMAPE - Symmetric Mean Absolute Percentage Error|`Smape`| diff --git a/lib/averages/_list.md b/lib/averages/_list.md index 7ad3da91..4273225c 100644 --- a/lib/averages/_list.md +++ b/lib/averages/_list.md @@ -1,3 +1,5 @@ +# Averages indicators + ✔️ AFIRMA - Adaptive FIR Moving Average ✔️ ALMA - Arnaud Legoux Moving Average ✔️ DEMA - Double Exponential Moving Average @@ -15,7 +17,7 @@ ✔️ KAMA - Kaufman Adaptive Moving Average ✔️ LTMA - Linear Time Moving Average ✔️ MAAF - Moving Average Adaptive Filter -✔️ MAMA - MESA Adaptive Moving Average +✔️ *MAMA - MESA Adaptive Moving Average (MAMA, FAMA) ✔️ MGDI - McGinley Dynamic Indicator ✔️ MMA - Modified Moving Average ✔️ PWMA - Parabolic Weighted Moving Average diff --git a/lib/momentum/Dpo.cs b/lib/momentum/Dpo.cs new file mode 100644 index 00000000..f88ad083 --- /dev/null +++ b/lib/momentum/Dpo.cs @@ -0,0 +1,112 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// DPO: Detrended Price Oscillator +/// A momentum indicator that removes the trend from price by comparing the current price +/// to a past moving average, helping to identify cycles in the price. +/// +/// +/// The DPO calculation process: +/// 1. Calculate the period shifted back by (period / 2 + 1) days +/// 2. Calculate SMA for the shifted period +/// 3. DPO = Price - SMA(Price, period) shifted back +/// +/// Key characteristics: +/// - Removes long-term trends +/// - Helps identify cycles +/// - Oscillates above and below zero +/// - Default period is 20 days +/// - Uses price displacement +/// +/// Formula: +/// DPO = Price - SMA(Price, period) shifted (period/2 + 1) bars back +/// +/// Market Applications: +/// - Cycle identification +/// - Overbought/Oversold conditions +/// - Price momentum +/// - Trading signals +/// - Market timing +/// +/// Sources: +/// Donald Dorsey - Original development +/// https://www.investopedia.com/terms/d/detrended-price-oscillator-dpo.asp +/// +/// Note: DPO helps identify cycles by removing the trend component from the price data +/// + +[SkipLocalsInit] +public sealed class Dpo : AbstractBase +{ + private readonly int _shift; + private readonly CircularBuffer _prices; + private readonly CircularBuffer _sma; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Dpo(int period = 20) + { + _shift = period / 2 + 1; + WarmupPeriod = period + _shift; + Name = $"DPO({period})"; + _prices = new CircularBuffer(WarmupPeriod); + _sma = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Dpo(object source, int period = 20) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prices.Clear(); + _sma.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Add current price to buffer + _prices.Add(BarInput.Close); + + // Need enough prices for the shifted SMA calculation + if (_index <= _shift) + { + return 0; + } + + // 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(); + + IsHot = _index >= WarmupPeriod; + return dpo; + } +} diff --git a/lib/momentum/Tsi.cs b/lib/momentum/Tsi.cs new file mode 100644 index 00000000..350318c7 --- /dev/null +++ b/lib/momentum/Tsi.cs @@ -0,0 +1,151 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// TSI: True Strength Index +/// A momentum indicator that shows both trend direction and overbought/oversold conditions +/// by using two smoothing steps on price changes. +/// +/// +/// The TSI calculation process: +/// 1. Calculate price change (PC): +/// PC = Close - Previous Close +/// 2. Calculate absolute price change (APC): +/// APC = |PC| +/// 3. Double smooth both PC and APC using EMA: +/// First PC EMA = EMA(PC, firstPeriod) +/// Second PC EMA = EMA(First PC EMA, secondPeriod) +/// First APC EMA = EMA(APC, firstPeriod) +/// Second APC EMA = EMA(First APC EMA, secondPeriod) +/// 4. Calculate TSI: +/// TSI = (Second PC EMA / Second APC EMA) * 100 +/// +/// Key characteristics: +/// - Double smoothed momentum indicator +/// - Oscillates between +100 and -100 +/// - Default periods are 25 and 13 +/// - Shows trend direction +/// - Identifies overbought/oversold +/// +/// Formula: +/// TSI = (EMA(EMA(PC, r), s) / EMA(EMA(|PC|, r), s)) * 100 +/// where: +/// PC = Close - Previous Close +/// r = first period (default 25) +/// s = second period (default 13) +/// +/// Market Applications: +/// - Trend direction +/// - Overbought/Oversold levels +/// - Centerline crossovers +/// - Divergence analysis +/// - Signal line crossovers +/// +/// Sources: +/// William Blau - Original development (1991) +/// https://www.investopedia.com/terms/t/tsi.asp +/// +/// Note: Values above +25 indicate overbought conditions, while values below -25 indicate oversold conditions +/// + +[SkipLocalsInit] +public sealed class Tsi : AbstractBase +{ + private readonly int _firstPeriod; + private double _prevClose; + private double _pcFirstEma; + private double _pcSecondEma; + private double _apcFirstEma; + private double _apcSecondEma; + private readonly double _firstAlpha; + private readonly double _secondAlpha; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Tsi(int firstPeriod = 25, int secondPeriod = 13) + { + _firstPeriod = firstPeriod; + WarmupPeriod = firstPeriod + secondPeriod; + Name = $"TSI({_firstPeriod},{secondPeriod})"; + _firstAlpha = 2.0 / (firstPeriod + 1); + _secondAlpha = 2.0 / (secondPeriod + 1); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Tsi(object source, int firstPeriod = 25, int secondPeriod = 13) : this(firstPeriod, secondPeriod) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _pcFirstEma = 0; + _pcSecondEma = 0; + _apcFirstEma = 0; + _apcSecondEma = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate price changes + double pc = BarInput.Close - _prevClose; + double apc = Math.Abs(pc); + + // Initialize or update EMAs + if (_index <= _firstPeriod) + { + _pcFirstEma = pc; + _apcFirstEma = apc; + } + else + { + _pcFirstEma = (_firstAlpha * pc) + ((1 - _firstAlpha) * _pcFirstEma); + _apcFirstEma = (_firstAlpha * apc) + ((1 - _firstAlpha) * _apcFirstEma); + } + + if (_index <= WarmupPeriod) + { + _pcSecondEma = _pcFirstEma; + _apcSecondEma = _apcFirstEma; + } + else + { + _pcSecondEma = (_secondAlpha * _pcFirstEma) + ((1 - _secondAlpha) * _pcSecondEma); + _apcSecondEma = (_secondAlpha * _apcFirstEma) + ((1 - _secondAlpha) * _apcSecondEma); + } + + // Store current close for next calculation + _prevClose = BarInput.Close; + + // Calculate TSI + double tsi = Math.Abs(_apcSecondEma) > double.Epsilon ? (_pcSecondEma / _apcSecondEma) * 100 : 0; + + IsHot = _index >= WarmupPeriod; + return tsi; + } +} diff --git a/lib/momentum/Vortex.cs b/lib/momentum/Vortex.cs new file mode 100644 index 00000000..f113f193 --- /dev/null +++ b/lib/momentum/Vortex.cs @@ -0,0 +1,162 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// VORTEX: Vortex Indicator +/// A technical indicator consisting of two oscillating lines that identify trend reversals +/// and confirm current trends based on the highs and lows of the previous period. +/// +/// +/// The Vortex calculation process: +/// 1. Calculate True Range (TR): +/// TR = max(High - Low, |High - Previous Close|, |Low - Previous Close|) +/// 2. Calculate +VM (Positive Movement): +/// +VM = |Current High - Previous Low| +/// 3. Calculate -VM (Negative Movement): +/// -VM = |Current Low - Previous High| +/// 4. Calculate period sums: +/// TR Period Sum = Sum(TR, period) +/// +VM Period Sum = Sum(+VM, period) +/// -VM Period Sum = Sum(-VM, period) +/// 5. Calculate +VI and -VI: +/// +VI = +VM Period Sum / TR Period Sum +/// -VI = -VM Period Sum / TR Period Sum +/// +/// Key characteristics: +/// - Two oscillating lines (+VI and -VI) +/// - No upper or lower bounds +/// - Default period is 14 days +/// - Crossovers signal trend changes +/// - Uses true range normalization +/// +/// Formula: +/// +VI = Sum(+VM, period) / Sum(TR, period) +/// -VI = Sum(-VM, period) / Sum(TR, period) +/// +/// Market Applications: +/// - Trend identification +/// - Trend reversals +/// - Trend confirmation +/// - Trading signals +/// - Market momentum +/// +/// Sources: +/// Etienne Botes and Douglas Siepman - Original development (2010) +/// https://www.investopedia.com/terms/v/vortex-indicator-vi.asp +/// +/// Note: When +VI crosses above -VI, it signals a potential uptrend, and vice versa +/// + +[SkipLocalsInit] +public sealed class Vortex : AbstractBase +{ + private readonly CircularBuffer _tr; + private readonly CircularBuffer _vmPlus; + private readonly CircularBuffer _vmMinus; + private double _prevHigh; + private double _prevLow; + private double _prevClose; + public double _viPlus { get; set; } + public double _viMinus { get; set; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vortex(int period = 14) + { + WarmupPeriod = period + 1; // Need one extra period for previous values + Name = $"VORTEX({period})"; + _tr = new CircularBuffer(period); + _vmPlus = new CircularBuffer(period); + _vmMinus = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vortex(object source, int period = 14) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevHigh = 0; + _prevLow = 0; + _prevClose = 0; + _viPlus = 0; + _viMinus = 0; + _tr.Clear(); + _vmPlus.Clear(); + _vmMinus.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous values + if (_index == 1) + { + _prevHigh = BarInput.High; + _prevLow = BarInput.Low; + _prevClose = BarInput.Close; + return 0; + } + + // Calculate True Range + double tr = Math.Max(BarInput.High - BarInput.Low, + Math.Max(Math.Abs(BarInput.High - _prevClose), + Math.Abs(BarInput.Low - _prevClose))); + + // Calculate VM+ and VM- + double vmPlus = Math.Abs(BarInput.High - _prevLow); + double vmMinus = Math.Abs(BarInput.Low - _prevHigh); + + // Add values to buffers + _tr.Add(tr); + _vmPlus.Add(vmPlus); + _vmMinus.Add(vmMinus); + + // Calculate VI+ and VI- + double trSum = _tr.Sum(); + if (Math.Abs(trSum) > double.Epsilon) + { + _viPlus = _vmPlus.Sum() / trSum; + _viMinus = _vmMinus.Sum() / trSum; + } + + // Store current values for next calculation + _prevHigh = BarInput.High; + _prevLow = BarInput.Low; + _prevClose = BarInput.Close; + + // Return the difference between VI+ and VI- + double vortex = _viPlus - _viMinus; + + IsHot = _index >= WarmupPeriod; + return vortex; + } + + /// + /// Gets the positive Vortex line (VI+) + /// + public double ViPlus => _viPlus; + + /// + /// Gets the negative Vortex line (VI-) + /// + public double ViMinus => _viMinus; +} diff --git a/lib/momentum/_list.md b/lib/momentum/_list.md index fe0e04cc..e141b879 100644 --- a/lib/momentum/_list.md +++ b/lib/momentum/_list.md @@ -1,20 +1,20 @@ # Momentum indicators -Done: 12, Todo: 5 +Done: 15, Todo: 2 ✔️ ADX - Average Directional Movement Index ✔️ ADXR - Average Directional Movement Index Rating ✔️ APO - Absolute Price Oscillator -✔️ DMI - Directional Movement Index +✔️ *DMI - Directional Movement Index (DI+, DI-) ✔️ DMX - Jurik Directional Movement Index -DPO - Detrended Price Oscillator -MACD - Moving Average Convergence/Divergence +✔️ DPO - Detrended Price Oscillator +*MACD - Moving Average Convergence/Divergence (MACD, Signal, Histogram) ✔️ MOM - Momentum ✔️ PMO - Price Momentum Oscillator ✔️ PO - Price Oscillator ✔️ PPO - Percentage Price Oscillator ✔️ PRS - Price Relative Strength ✔️ ROC - Rate of Change -TSI - True Strength Index +✔️ TSI - True Strength Index ✔️ TRIX - 1-day ROC of TEMA ✔️ VEL - Jurik Signal Velocity -VORTEX - Vortex Indicator +✔️ *VORTEX - Vortex Indicator (VI+, VI-) diff --git a/lib/oscillators/Bop.cs b/lib/oscillators/Bop.cs new file mode 100644 index 00000000..210805ca --- /dev/null +++ b/lib/oscillators/Bop.cs @@ -0,0 +1,66 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// BOP: Balance of Power +/// A momentum oscillator that measures the strength of buying and selling pressure by comparing +/// closing prices to their corresponding opening prices. +/// +/// +/// The BOP calculation process: +/// 1. Calculate (Close - Open) / (High - Low) for each period +/// 2. A positive BOP indicates buying pressure (bullish) +/// 3. A negative BOP indicates selling pressure (bearish) +/// +/// Key characteristics: +/// - Oscillates above and below zero +/// - No upper or lower bounds +/// - Zero line acts as equilibrium between buying and selling pressure +/// - Can be used to identify potential trend reversals and divergences +/// +/// Formula: +/// BOP = (Close - Open) / (High - Low) +/// +/// Sources: +/// Igor Livshin (1990s) +/// https://www.investopedia.com/terms/b/bop.asp +/// + +[SkipLocalsInit] +public sealed class Bop : AbstractBase +{ + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Bop(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Bop() + { + WarmupPeriod = 1; + Name = "BOP"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + var range = BarInput.High - BarInput.Low; + if (range <= double.Epsilon) return 0; + + return (BarInput.Close - BarInput.Open) / range; + } +} diff --git a/lib/oscillators/Cci.cs b/lib/oscillators/Cci.cs new file mode 100644 index 00000000..56e1135f --- /dev/null +++ b/lib/oscillators/Cci.cs @@ -0,0 +1,101 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// CCI: Commodity Channel Index +/// A momentum oscillator used to identify cyclical trends and measure the deviation of price +/// from its statistical mean. +/// +/// +/// The CCI calculation process: +/// 1. Calculate Typical Price (TP) = (High + Low + Close) / 3 +/// 2. Calculate Simple Moving Average of TP +/// 3. Calculate Mean Deviation +/// 4. CCI = (TP - SMA(TP)) / (0.015 * Mean Deviation) +/// +/// Key characteristics: +/// - Oscillates above and below zero +/// - Typically ranges between +100 and -100 +/// - Values above +100 indicate overbought conditions +/// - Values below -100 indicate oversold conditions +/// - Can identify trend strength and reversals +/// +/// Formula: +/// CCI = (TypicalPrice - SMA(TypicalPrice, period)) / (0.015 * MeanDeviation) +/// where: +/// - TypicalPrice = (High + Low + Close) / 3 +/// - MeanDeviation = Mean(|TP - SMA(TP)|) +/// +/// Sources: +/// Donald Lambert (1980) +/// https://www.investopedia.com/terms/c/commoditychannelindex.asp +/// + +[SkipLocalsInit] +public sealed class Cci : AbstractBase +{ + private readonly int _period; + private readonly Sma _sma; + private readonly double[] _typicalPrices; + private readonly double _constant = 0.015; + + /// The data source object that publishes updates. + /// The calculation period (default: 20) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Cci(object source, int period = 20) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Cci(int period = 20) + { + _period = period; + _sma = new Sma(period); + _typicalPrices = new double[period]; + WarmupPeriod = period; + Name = "CCI"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private double CalculateMeanDeviation(double typicalPrice, double smaValue) + { + var sum = 0.0; + var count = System.Math.Min(_period, _index + 1); + + for (var i = 0; i < count; i++) + { + sum += System.Math.Abs(_typicalPrices[i] - smaValue); + } + + return sum / count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + var typicalPrice = (BarInput.High + BarInput.Low + BarInput.Close) / 3.0; + var idx = _index % _period; + _typicalPrices[idx] = typicalPrice; + + var smaValue = _sma.Calc(typicalPrice, BarInput.IsNew); + if (_index < _period - 1) return double.NaN; + + var meanDeviation = CalculateMeanDeviation(typicalPrice, smaValue); + if (meanDeviation <= double.Epsilon) return 0; + + return (typicalPrice - smaValue) / (_constant * meanDeviation); + } +} diff --git a/lib/oscillators/Cfo.cs b/lib/oscillators/Cfo.cs new file mode 100644 index 00000000..d2693a36 --- /dev/null +++ b/lib/oscillators/Cfo.cs @@ -0,0 +1,117 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// CFO: Chande Forecast Oscillator +/// A momentum oscillator that measures the percentage difference between the actual price +/// and its linear regression forecast value. +/// +/// +/// The CFO calculation process: +/// 1. Calculate linear regression forecast value for the current period +/// 2. Calculate percentage difference between actual price and forecast +/// +/// Key characteristics: +/// - Oscillates above and below zero +/// - Measures deviation of price from its forecasted value +/// - Positive values indicate price is above forecast (bullish) +/// - Negative values indicate price is below forecast (bearish) +/// - Can identify potential trend reversals and price divergences +/// +/// Formula: +/// CFO = ((Price - Forecast) / Price) * 100 +/// where: +/// - Price is typically the closing price +/// - Forecast is the linear regression forecast value +/// +/// Sources: +/// Tushar Chande (1990s) +/// Technical Analysis of Stocks and Commodities magazine +/// + +[SkipLocalsInit] +public sealed class Cfo : AbstractBase +{ + private readonly int _period; + private readonly double[] _prices; + private double _sumX; + private double _sumY; + private double _sumXY; + private double _sumX2; + + /// The data source object that publishes updates. + /// The calculation period (default: 14) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Cfo(object source, int period = 14) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Cfo(int period = 14) + { + _period = period; + _prices = new double[period]; + WarmupPeriod = period; + Name = "CFO"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateSums(double oldPrice, double newPrice, int oldX, int newX) + { + _sumY -= oldPrice; + _sumY += newPrice; + _sumXY -= oldPrice * oldX; + _sumXY += newPrice * newX; + _sumX -= oldX; + _sumX += newX; + _sumX2 -= oldX * oldX; + _sumX2 += newX * newX; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private double CalculateForecast() + { + var count = System.Math.Min(_period, _index + 1); + var n = (double)count; + + // Calculate linear regression coefficients + var slope = (n * _sumXY - _sumX * _sumY) / (n * _sumX2 - _sumX * _sumX); + var intercept = (_sumY - slope * _sumX) / n; + + // Calculate forecast for next period + return intercept + slope * count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + var price = Input.Value; + var idx = _index % _period; + var oldPrice = _prices[idx]; + _prices[idx] = price; + + var oldX = idx + 1; + var newX = _index < _period ? idx + 1 : _period; + + UpdateSums(oldPrice, price, oldX, newX); + if (_index < _period - 1) return double.NaN; + + var forecast = CalculateForecast(); + if (price <= double.Epsilon) return 0; + + return ((price - forecast) / price) * 100; + } +} diff --git a/lib/oscillators/_list.md b/lib/oscillators/_list.md index 99e19436..418edeba 100644 --- a/lib/oscillators/_list.md +++ b/lib/oscillators/_list.md @@ -3,7 +3,7 @@ Done: 6, Todo: 23 ✔️ AC - Acceleration Oscillator ✔️ AO - Awesome Oscillator -✔️ AROON - Aroon oscillator +✔️ *AROON - Aroon oscillator (Up, Down) BOP - Balance of Power CCI - Commodity Channel Index CFO - Chande Forcast Oscillator @@ -17,16 +17,16 @@ DOSC - Derivative Oscillator EFI - Elder Ray's Force Index FISHER - Fisher Transform FOSC - Forecast Oscillator -GATOR - Williams Alliator Oscillator -KDJ - KDJ Indicator (trend reversal) +*GATOR - Williams Alliator Oscillator (Upper Jaw, Lower Jaw, Teeth) +*KDJ - KDJ Indicator (K, D, J lines) KRI - Kairi Relative Index ✔️ RSI - Relative Strength Index ✔️ RSX - Jurik Trend Strength Index -RVGI - Relative Vigor Index +*RVGI - Relative Vigor Index (RVGI, Signal) SMI - Stochastic Momentum Index -SRSI - Stochastic RSI +*SRSI - Stochastic RSI (SRSI, Signal) STC - Schaff Trend Cycle -STOCH - Stochastic Oscillator +*STOCH - Stochastic Oscillator (%K, %D) TSI - True Strength Index UO - Ultimate Oscillator WILLR - Larry Williams' %R diff --git a/lib/patterns/_list.md b/lib/patterns/_list.md index 8b54056f..99a7836e 100644 --- a/lib/patterns/_list.md +++ b/lib/patterns/_list.md @@ -2,10 +2,10 @@ Done: 0, Todo: 8 DOJI - Doji Candlestick Pattern -ER - Elder Ray Pattern +*ER - Elder Ray Pattern (Bull Power, Bear Power) MARU - Marubozu Candlestick Pattern -PIV - Pivot Points -PP - Price Pivots -RPP - Rolling Pivot Points +*PIV - Pivot Points (Support 1-3, Pivot, Resistance 1-3) +*PP - Price Pivots (Support 1-3, Pivot, Resistance 1-3) +*RPP - Rolling Pivot Points (Support 1-3, Pivot, Resistance 1-3) WF - Williams Fractal ZZ - Zig Zag Pattern diff --git a/lib/statistics/_list.md b/lib/statistics/_list.md index 627de7c8..fa7932fd 100644 --- a/lib/statistics/_list.md +++ b/lib/statistics/_list.md @@ -1,8 +1,8 @@ # Statistics indicators Done: 13, Todo: 6 -BETA - Beta coefficient -CORR - Correlation Coefficient +*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 HUBER - Huber Loss @@ -13,11 +13,11 @@ HURST - Hurst Exponent ✔️ MIN - Minimum with exponential decay ✔️ MODE - Most Frequent Value ✔️ PERCENTILE - Rank Order -RSQUARED - Coefficient of Determination R-Squared +*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 -TSF - Time Series Forecast +*THEIL - Theil's U Statistics (U1, U2) +*TSF - Time Series Forecast (Forecast, Confidence Interval) ✔️ VARIANCE - Average of Squared Deviations ✔️ ZSCORE - Standardized Score diff --git a/lib/volatility/Tr.cs b/lib/volatility/Tr.cs new file mode 100644 index 00000000..6e60d80f --- /dev/null +++ b/lib/volatility/Tr.cs @@ -0,0 +1,102 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// TR: True Range +/// A basic volatility measure that represents the greatest of three price ranges: +/// current high-low, current high-previous close, or current low-previous close. +/// +/// +/// The TR calculation process: +/// 1. Calculate three differences: +/// - Current High minus Current Low +/// - |Current High minus Previous Close| +/// - |Current Low minus Previous Close| +/// 2. TR is the maximum of these three values +/// +/// Key characteristics: +/// - Basic volatility measure +/// - Accounts for gaps between trading periods +/// - Foundation for other indicators (ATR, etc.) +/// - No upper bound +/// - Always positive +/// +/// Formula: +/// TR = max(High - Low, |High - Previous Close|, |Low - Previous Close|) +/// +/// Market Applications: +/// - Volatility measurement +/// - Stop loss placement +/// - Position sizing +/// - Market analysis +/// - Risk assessment +/// +/// Sources: +/// J. Welles Wilder Jr. - Original development +/// https://www.investopedia.com/terms/t/truerange.asp +/// +/// Note: True Range accounts for gaps between periods, making it more accurate than simple high-low range +/// + +[SkipLocalsInit] +public sealed class Tr : AbstractBase +{ + private double _prevClose; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Tr() + { + WarmupPeriod = 2; // Need previous close + Name = "TR"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Tr(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return BarInput.High - BarInput.Low; + } + + // Calculate True Range + double tr = Math.Max(BarInput.High - BarInput.Low, + Math.Max(Math.Abs(BarInput.High - _prevClose), + Math.Abs(BarInput.Low - _prevClose))); + + // Store current close for next calculation + _prevClose = BarInput.Close; + + IsHot = _index >= WarmupPeriod; + return tr; + } +} diff --git a/lib/volatility/Ui.cs b/lib/volatility/Ui.cs new file mode 100644 index 00000000..cfa5f96f --- /dev/null +++ b/lib/volatility/Ui.cs @@ -0,0 +1,114 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// UI: Ulcer Index +/// A technical indicator that measures downside risk by incorporating both +/// the depth and duration of price declines over a given period. +/// +/// +/// The UI calculation process: +/// 1. Calculate percentage drawdown from recent high for each period +/// 2. Square the drawdowns to emphasize larger declines +/// 3. Calculate the average of squared drawdowns +/// 4. Take the square root of the average +/// +/// Key characteristics: +/// - Measures downside volatility +/// - Emphasizes larger drawdowns +/// - Default period is 14 days +/// - Always positive +/// - No upper bound +/// +/// Formula: +/// Drawdown = ((Close - 14-period High) / 14-period High) * 100 +/// UI = sqrt(sum(Drawdown^2) / period) +/// +/// Market Applications: +/// - Risk assessment +/// - Portfolio analysis +/// - Trading system evaluation +/// - Market timing +/// - Trend strength measurement +/// +/// Sources: +/// Peter Martin - Original development (1987) +/// https://www.investopedia.com/terms/u/ulcerindex.asp +/// +/// Note: Higher values indicate higher risk due to deeper or more frequent drawdowns +/// + +[SkipLocalsInit] +public sealed class Ui : AbstractBase +{ + private readonly int _period; + private readonly CircularBuffer _prices; + private readonly CircularBuffer _drawdowns; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ui(int period = 14) + { + _period = period; + WarmupPeriod = period; + Name = $"UI({_period})"; + _prices = new CircularBuffer(period); + _drawdowns = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ui(object source, int period = 14) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prices.Clear(); + _drawdowns.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Add current price to buffer + _prices.Add(BarInput.Close); + + // Need enough prices for calculation + if (_index <= _period) + { + return 0; + } + + // Calculate maximum price in period + double maxPrice = _prices.Max(); + + // Calculate percentage drawdown + double drawdown = Math.Abs(maxPrice) > double.Epsilon ? ((BarInput.Close - maxPrice) / maxPrice) * 100 : 0; + + // Add squared drawdown to buffer + _drawdowns.Add(drawdown * drawdown); + + // Calculate Ulcer Index + double ui = Math.Sqrt(_drawdowns.Average()); + + IsHot = _index >= WarmupPeriod; + return ui; + } +} diff --git a/lib/volatility/Vc.cs b/lib/volatility/Vc.cs new file mode 100644 index 00000000..0303fc30 --- /dev/null +++ b/lib/volatility/Vc.cs @@ -0,0 +1,164 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// VC: Volatility Cone +/// A technical indicator that analyzes volatility across different time periods +/// to identify normal ranges and extreme values. +/// +/// +/// The VC calculation process: +/// 1. Calculate volatility for the specified period +/// 2. Track mean and standard deviation of volatility +/// 3. Calculate upper and lower bounds: +/// Upper = Mean + (deviations * StdDev) +/// Lower = Mean - (deviations * StdDev) +/// +/// Key characteristics: +/// - Multi-period volatility analysis +/// - Statistical approach +/// - Default period is 20 days +/// - Returns mean and bounds +/// - Adaptive to market conditions +/// +/// Formula: +/// Volatility = StdDev(Returns) * sqrt(252) // Annualized +/// Upper = Mean(Volatility) + (deviations * StdDev(Volatility)) +/// Lower = Mean(Volatility) - (deviations * StdDev(Volatility)) +/// +/// Market Applications: +/// - Options trading +/// - Risk assessment +/// - Volatility forecasting +/// - Trading strategy development +/// - Market regime analysis +/// +/// Sources: +/// https://www.investopedia.com/terms/v/volatility-cone.asp +/// +/// Note: Returns three values: mean volatility and its upper/lower bounds +/// + +[SkipLocalsInit] +public sealed class Vc : AbstractBase +{ + private readonly int _period; + private readonly double _deviations; + private readonly CircularBuffer _returns; + private readonly CircularBuffer _volatilities; + private double _prevClose; + private double _upperBound; + private double _lowerBound; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vc(int period = 20, double deviations = 2.0) + { + _period = period; + _deviations = deviations; + WarmupPeriod = period * 2; // Need enough data for stable statistics + Name = $"VC({_period},{_deviations})"; + _returns = new CircularBuffer(period); + _volatilities = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vc(object source, int period = 20, double deviations = 2.0) : this(period, deviations) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _upperBound = 0; + _lowerBound = 0; + _returns.Clear(); + _volatilities.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private double CalculateVariance(CircularBuffer buffer) + { + if (buffer.Count == 0) return 0; + double mean = buffer.Average(); + double sumSquaredDiff = 0; + for (int i = 0; i < buffer.Count; i++) + { + double diff = buffer[i] - mean; + sumSquaredDiff += diff * diff; + } + return sumSquaredDiff / buffer.Count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate return + double ret = Math.Abs(_prevClose) > double.Epsilon ? Math.Log(BarInput.Close / _prevClose) : 0; + _returns.Add(ret); + + // Store current close for next calculation + _prevClose = BarInput.Close; + + // Need enough returns for volatility calculation + if (_index <= _period) + { + return 0; + } + + // Calculate current volatility (annualized) + double vol = Math.Sqrt(CalculateVariance(_returns)) * Math.Sqrt(252); + _volatilities.Add(vol); + + // Need enough volatilities for cone calculation + if (_index <= WarmupPeriod) + { + return vol; + } + + // Calculate mean and standard deviation of volatilities + double meanVol = _volatilities.Average(); + double stdVol = Math.Sqrt(CalculateVariance(_volatilities)); + + // Calculate bounds + _upperBound = meanVol + (_deviations * stdVol); + _lowerBound = Math.Max(0, meanVol - (_deviations * stdVol)); + + IsHot = _index >= WarmupPeriod; + return meanVol; + } + + /// + /// Gets the upper bound of the volatility cone + /// + public double UpperBound => _upperBound; + + /// + /// Gets the lower bound of the volatility cone + /// + public double LowerBound => _lowerBound; +} diff --git a/lib/volatility/Vov.cs b/lib/volatility/Vov.cs new file mode 100644 index 00000000..c840eea0 --- /dev/null +++ b/lib/volatility/Vov.cs @@ -0,0 +1,131 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// VOV: Volatility of Volatility +/// A technical indicator that measures the volatility of volatility itself, +/// providing insight into the stability of market volatility. +/// +/// +/// The VOV calculation process: +/// 1. Calculate primary volatility (e.g., using True Range) +/// 2. Calculate standard deviation of primary volatility +/// 3. Normalize result for comparison +/// +/// Key characteristics: +/// - Second-order volatility measure +/// - Default period is 20 days +/// - Always positive +/// - No upper bound +/// - Measures volatility stability +/// +/// Formula: +/// Primary Volatility = TR (True Range) +/// VOV = StdDev(Primary Volatility, period) / Average(Primary Volatility, period) +/// +/// Market Applications: +/// - Risk of risk assessment +/// - Volatility regime changes +/// - Market stability analysis +/// - Trading strategy adaptation +/// - Risk management +/// +/// Note: Higher values indicate more unstable volatility conditions +/// + +[SkipLocalsInit] +public sealed class Vov : AbstractBase +{ + private readonly int _period; + private readonly CircularBuffer _volatilities; + private double _prevClose; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vov(int period = 20) + { + _period = period; + WarmupPeriod = period + 1; // Need extra period for TR calculation + Name = $"VOV({_period})"; + _volatilities = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vov(object source, int period = 20) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _volatilities.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private double CalculateVariance(CircularBuffer buffer) + { + if (buffer.Count == 0) return 0; + double mean = buffer.Average(); + double sumSquaredDiff = 0; + for (int i = 0; i < buffer.Count; i++) + { + double diff = buffer[i] - mean; + sumSquaredDiff += diff * diff; + } + return sumSquaredDiff / buffer.Count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate True Range as primary volatility measure + double tr = Math.Max(BarInput.High - BarInput.Low, + Math.Max(Math.Abs(BarInput.High - _prevClose), + Math.Abs(BarInput.Low - _prevClose))); + + // Store current close for next calculation + _prevClose = BarInput.Close; + + // Add volatility to buffer + _volatilities.Add(tr); + + // Need enough volatilities for VOV calculation + if (_index <= _period) + { + return 0; + } + + // Calculate mean volatility + double meanVol = _volatilities.Average(); + + // Calculate VOV (normalized standard deviation) + double vov = meanVol > double.Epsilon ? Math.Sqrt(CalculateVariance(_volatilities)) / meanVol : 0; + + IsHot = _index >= WarmupPeriod; + return vov; + } +} diff --git a/lib/volatility/Vr.cs b/lib/volatility/Vr.cs new file mode 100644 index 00000000..009d6e56 --- /dev/null +++ b/lib/volatility/Vr.cs @@ -0,0 +1,135 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// VR: Volatility Ratio +/// A technical indicator that compares volatility across different time periods +/// to identify changes in market conditions. +/// +/// +/// The VR calculation process: +/// 1. Calculate short-term volatility +/// 2. Calculate long-term volatility +/// 3. Calculate ratio between them +/// +/// Key characteristics: +/// - Relative volatility measure +/// - Default periods are 10 and 20 days +/// - Values above 1 indicate increasing volatility +/// - Values below 1 indicate decreasing volatility +/// - Normalized comparison +/// +/// Formula: +/// Short Volatility = StdDev(Returns, shortPeriod) +/// Long Volatility = StdDev(Returns, longPeriod) +/// VR = Short Volatility / Long Volatility +/// +/// Market Applications: +/// - Volatility regime changes +/// - Market condition analysis +/// - Risk assessment +/// - Trading strategy adaptation +/// - Trend confirmation +/// +/// Note: Values significantly different from 1 indicate changing market conditions +/// + +[SkipLocalsInit] +public sealed class Vr : AbstractBase +{ + private readonly int _longPeriod; + private readonly CircularBuffer _shortReturns; + private readonly CircularBuffer _longReturns; + private double _prevClose; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vr(int shortPeriod = 10, int longPeriod = 20) + { + _longPeriod = longPeriod; + WarmupPeriod = longPeriod + 1; // Need one extra period for returns + Name = $"VR({shortPeriod},{_longPeriod})"; + _shortReturns = new CircularBuffer(shortPeriod); + _longReturns = new CircularBuffer(longPeriod); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vr(object source, int shortPeriod = 10, int longPeriod = 20) : this(shortPeriod, longPeriod) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _shortReturns.Clear(); + _longReturns.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private double CalculateVariance(CircularBuffer buffer) + { + if (buffer.Count == 0) return 0; + double mean = buffer.Average(); + double sumSquaredDiff = 0; + for (int i = 0; i < buffer.Count; i++) + { + double diff = buffer[i] - mean; + sumSquaredDiff += diff * diff; + } + return sumSquaredDiff / buffer.Count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate return + double ret = _prevClose > double.Epsilon ? Math.Log(BarInput.Close / _prevClose) : 0; + + // Add return to buffers + _shortReturns.Add(ret); + _longReturns.Add(ret); + + // Store current close for next calculation + _prevClose = BarInput.Close; + + // Need enough returns for both periods + if (_index <= _longPeriod) + { + return 0; + } + + // Calculate volatilities + double shortVol = Math.Sqrt(CalculateVariance(_shortReturns)); + double longVol = Math.Sqrt(CalculateVariance(_longReturns)); + + // Calculate ratio + double vr = longVol > double.Epsilon ? shortVol / longVol : 1; + + IsHot = _index >= WarmupPeriod; + return vr; + } +} diff --git a/lib/volatility/Vs.cs b/lib/volatility/Vs.cs new file mode 100644 index 00000000..aa19b409 --- /dev/null +++ b/lib/volatility/Vs.cs @@ -0,0 +1,155 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// VS: Volatility Stop +/// A technical indicator that uses volatility to determine stop levels, +/// adapting to market conditions for dynamic risk management. +/// +/// +/// The VS calculation process: +/// 1. Calculate Average True Range (ATR) +/// 2. Calculate stop levels: +/// Long Stop = Close - (multiplier * ATR) +/// Short Stop = Close + (multiplier * ATR) +/// 3. Trail stops based on price movement +/// +/// Key characteristics: +/// - Adaptive stop levels +/// - Based on ATR volatility +/// - Default period is 14 days +/// - Returns both long and short stops +/// - Trails with price movement +/// +/// Formula: +/// ATR = Average(TR, period) +/// Long Stop = Close - (multiplier * ATR) +/// Short Stop = Close + (multiplier * ATR) +/// +/// Market Applications: +/// - Stop loss placement +/// - Position management +/// - Risk control +/// - Trend following +/// - Exit strategy +/// +/// Sources: +/// Adaptation of Volatility-Based Stops concept +/// https://www.investopedia.com/terms/v/volatility-stop.asp +/// +/// Note: Returns two values: long stop and short stop levels +/// + +[SkipLocalsInit] +public sealed class Vs : AbstractBase +{ + private readonly int _period; + private readonly double _multiplier; + private readonly CircularBuffer _tr; + private double _prevClose; + private double _longStop; + private double _shortStop; + private double _prevLongStop; + private double _prevShortStop; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vs(int period = 14, double multiplier = 2.0) + { + _period = period; + _multiplier = multiplier; + WarmupPeriod = period + 1; // Need one extra period for TR + Name = $"VS({_period},{_multiplier})"; + _tr = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vs(object source, int period = 14, double multiplier = 2.0) : this(period, multiplier) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _longStop = 0; + _shortStop = 0; + _prevLongStop = 0; + _prevShortStop = 0; + _tr.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + _longStop = BarInput.Close; + _shortStop = BarInput.Close; + return 0; + } + + // Calculate True Range + double tr = Math.Max(BarInput.High - BarInput.Low, + Math.Max(Math.Abs(BarInput.High - _prevClose), + Math.Abs(BarInput.Low - _prevClose))); + + // Add TR to buffer + _tr.Add(tr); + + // Store current close for next calculation + _prevClose = BarInput.Close; + + // Need enough values for ATR calculation + if (_index <= _period) + { + return 0; + } + + // Calculate ATR + double atr = _tr.Average(); + + // Calculate initial stop levels + double potentialLongStop = BarInput.Close - (_multiplier * atr); + double potentialShortStop = BarInput.Close + (_multiplier * atr); + + // Trail stops + _longStop = BarInput.Close > _prevShortStop ? potentialLongStop : Math.Max(potentialLongStop, _prevLongStop); + _shortStop = BarInput.Close < _prevLongStop ? potentialShortStop : Math.Min(potentialShortStop, _prevShortStop); + + // Store current stops for next calculation + _prevLongStop = _longStop; + _prevShortStop = _shortStop; + + IsHot = _index >= WarmupPeriod; + return _longStop; // Return long stop as primary value + } + + /// + /// Gets the long stop level + /// + public double LongStop => _longStop; + + /// + /// Gets the short stop level + /// + public double ShortStop => _shortStop; +} diff --git a/lib/volatility/_list.md b/lib/volatility/_list.md index 93598c02..65243f2c 100644 --- a/lib/volatility/_list.md +++ b/lib/volatility/_list.md @@ -1,37 +1,38 @@ # Volatility indicators -Done: 6, Todo: 25 +Done: 11, Todo: 24 ADR - Average Daily Range AP - Andrew's Pitchfork ✔️ ATR - Average True Range ATRP - Average True Range Percent ATRS - ATR Trailing Stop -BB - Bollinger Bands® +*BB - Bollinger Bands® (Upper, Middle, Lower) CCV - Close-to-Close Volatility CE - Chandelier Exit CV - Conditional Volatility (ARCH/GARCH) CVI - Chaikin's Volatility -DC - Donchian Channels +*DC - Donchian Channels (Upper, Middle, Lower) +EWMA - Exponential Weighted Moving Average Volatility FCB - Fractal Chaos Bands GKV - Garman-Klass Volatility HLV - High-Low Volatility ✔️ HV - Historical Volatility -ICH - Ichimoku Cloud +*ICH - Ichimoku Cloud (Conversion, Base, Leading Span A, Leading Span B, Lagging Span) ✔️ JVOLTY - Jurik Volatility -KC - Keltner Channels +*KC - Keltner Channels (Upper, Middle, Lower) NATR - Normalized Average True Range PCH - Price Channel Indicator -PSAR - Parabolic Stop and Reverse +*PSAR - Parabolic Stop and Reverse (Value, Trend) PV - Parkinson Volatility RSV - Rogers-Satchell Volatility ✔️ RV - Realized Volatility ✔️ RVI - Relative Volatility Index -STARC - Starc Bands +*STARC - Starc Bands (Upper, Middle, Lower) SV - Stochastic Volatility -TR - True Range -UI - Ulcer Index -VC - Volatility Cone -VOV - Volatility of Volatility -VR - Volatility Ratio -VS - Volatility Stop +✔️ TR - True Range +✔️ UI - Ulcer Index +✔️ *VC - Volatility Cone (Mean, Upper Bound, Lower Bound) +✔️ VOV - Volatility of Volatility +✔️ VR - Volatility Ratio +✔️ *VS - Volatility Stop (Long Stop, Short Stop) YZV - Yang-Zhang Volatility diff --git a/lib/volatility/todo.md b/lib/volatility/todo.md deleted file mode 100644 index 32636c3f..00000000 --- a/lib/volatility/todo.md +++ /dev/null @@ -1,29 +0,0 @@ -# Volatility Measures - -## Single Value Input (Typically Closing Prices) - -- **Jurik Volatility (Volty)** -- **Standard Deviation** -- **RVI Relative Volatility Index** -- **CMO Chande Momentum Oscillator** -- **Historical Volatility** -- **Average True Range (ATR) (High, Low, Close)** - -- Normalized ATR -- Ulcer Index -- ARCH/GARCH Models -- Exponential Weighted Moving Average (EWMA) Volatility -- Conditional Volatility -- Volatility Ratio -- Close-to-Close Volatility -- Volatility of Volatility (VOV) -- Volatility Cone -- Bollinger Bands -- Stochastic Volatility: Typically modeled using closing prices, but can incorporate other price information -- Garman-Klass Volatility -- Rogers-Satchell Volatility -- Yang-Zhang Volatility -- Parkinson Volatility (High, Low) -- Chaikin Volatility (High, Low) -- Keltner Channels (typically Close, High, Low) -- High-Low Volatility (High, Low) diff --git a/lib/volume/Mfi.cs b/lib/volume/Mfi.cs new file mode 100644 index 00000000..2c847630 --- /dev/null +++ b/lib/volume/Mfi.cs @@ -0,0 +1,141 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// MFI: Money Flow Index +/// A volume-weighted momentum indicator that measures the inflow and outflow of money into an asset +/// over a specific period of time. It's sometimes referred to as volume-weighted RSI. +/// +/// +/// The MFI calculation process: +/// 1. Calculate Typical Price: +/// TP = (High + Low + Close) / 3 +/// 2. Calculate Raw Money Flow: +/// RMF = TP * Volume +/// 3. Determine Positive/Negative Money Flow: +/// If TP > Previous TP: Positive Money Flow +/// If TP < Previous TP: Negative Money Flow +/// 4. Calculate Money Flow Ratio: +/// MFR = (14-period Positive Money Flow Sum) / (14-period Negative Money Flow Sum) +/// 5. Calculate Money Flow Index: +/// MFI = 100 - (100 / (1 + MFR)) +/// +/// Key characteristics: +/// - Oscillates between 0 and 100 +/// - Default period is 14 days +/// - Overbought level typically at 80 +/// - Oversold level typically at 20 +/// - Volume-weighted measure +/// +/// Formula: +/// TP = (High + Low + Close) / 3 +/// RMF = TP * Volume +/// MFR = ΣPositive Money Flow / ΣNegative Money Flow +/// MFI = 100 - (100 / (1 + MFR)) +/// +/// Market Applications: +/// - Overbought/Oversold conditions +/// - Divergence analysis +/// - Trend confirmation +/// - Price reversals +/// - Volume flow analysis +/// +/// Sources: +/// Gene Quong and Avrum Soudack - Original development +/// https://www.investopedia.com/terms/m/mfi.asp +/// +/// Note: Values above 80 indicate overbought conditions, while values below 20 indicate oversold conditions +/// + +[SkipLocalsInit] +public sealed class Mfi : AbstractBase +{ + private readonly CircularBuffer _posMf; + private readonly CircularBuffer _negMf; + private double _prevTp; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Mfi(int period = 14) + { + WarmupPeriod = period + 1; // Need one extra period for previous TP + Name = $"MFI({period})"; + _posMf = new CircularBuffer(period); + _negMf = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Mfi(object source, int period = 14) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevTp = 0; + _posMf.Clear(); + _negMf.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Calculate Typical Price + double tp = (BarInput.High + BarInput.Low + BarInput.Close) / 3; + + // Skip first period to establish previous TP + if (_index == 1) + { + _prevTp = tp; + return 0; + } + + // Calculate Raw Money Flow + double rmf = tp * BarInput.Volume; + + // Determine Positive/Negative Money Flow + if (tp > _prevTp) + { + _posMf.Add(rmf); + _negMf.Add(0); + } + else if (tp < _prevTp) + { + _posMf.Add(0); + _negMf.Add(rmf); + } + else + { + _posMf.Add(0); + _negMf.Add(0); + } + + // Store current TP for next calculation + _prevTp = tp; + + // Calculate Money Flow Ratio and Index + double posMfSum = _posMf.Sum(); + double negMfSum = _negMf.Sum(); + + double mfi = Math.Abs(negMfSum) < double.Epsilon ? 100 : 100 - (100 / (1 + (posMfSum / negMfSum))); + + IsHot = _index >= WarmupPeriod; + return mfi; + } +} diff --git a/lib/volume/Nvi.cs b/lib/volume/Nvi.cs new file mode 100644 index 00000000..e0e1aaf7 --- /dev/null +++ b/lib/volume/Nvi.cs @@ -0,0 +1,114 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// NVI: Negative Volume Index +/// A cumulative indicator that focuses on days when volume decreases from the previous day. +/// It is based on the premise that smart money is active on days with lower volume. +/// +/// +/// The NVI calculation process: +/// 1. Compare current volume with previous volume +/// 2. If current volume is less than previous volume: +/// NVI = Previous NVI + (((Close - Previous Close) / Previous Close) * Previous NVI) +/// 3. If current volume is greater than or equal to previous volume: +/// NVI = Previous NVI +/// +/// Key characteristics: +/// - Cumulative indicator +/// - Only updates on lower volume days +/// - Starts at base value of 1000 +/// - Focuses on smart money activity +/// - Volume-driven measure +/// +/// Formula: +/// If Volume < Previous Volume: +/// NVI = Previous NVI + (Price % Change * Previous NVI) +/// Else: +/// NVI = Previous NVI +/// +/// Market Applications: +/// - Smart money tracking +/// - Trend identification +/// - Market timing +/// - Volume analysis +/// - Price confirmation +/// +/// Sources: +/// Paul Dysart - Original development (1930s) +/// Norman Fosback - Further development +/// https://www.investopedia.com/terms/n/nvi.asp +/// +/// Note: Rising NVI suggests smart money is buying, while falling NVI suggests smart money is selling +/// + +[SkipLocalsInit] +public sealed class Nvi : AbstractBase +{ + private double _prevClose; + private double _prevVolume; + private double _prevNvi; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Nvi() + { + WarmupPeriod = 2; // Need previous volume and close + Name = "NVI"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Nvi(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _prevVolume = 0; + _prevNvi = 1000; // Standard starting value + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous values + if (_index == 1) + { + _prevClose = BarInput.Close; + _prevVolume = BarInput.Volume; + return _prevNvi; + } + + // Calculate NVI + if (BarInput.Volume < _prevVolume) + { + double priceChange = ((BarInput.Close - _prevClose) / _prevClose); + _prevNvi += priceChange * _prevNvi; + } + + // Store current values for next calculation + _prevClose = BarInput.Close; + _prevVolume = BarInput.Volume; + + IsHot = _index >= WarmupPeriod; + return _prevNvi; + } +} diff --git a/lib/volume/Obv.cs b/lib/volume/Obv.cs new file mode 100644 index 00000000..b8189799 --- /dev/null +++ b/lib/volume/Obv.cs @@ -0,0 +1,117 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// OBV: On-Balance Volume +/// A momentum indicator that uses volume flow to predict changes in stock price. +/// It accumulates volume on up days and subtracts volume on down days. +/// +/// +/// The OBV calculation process: +/// 1. Compare current close with previous close +/// 2. If current close is higher: +/// OBV = Previous OBV + Current Volume +/// 3. If current close is lower: +/// OBV = Previous OBV - Current Volume +/// 4. If current close equals previous close: +/// OBV = Previous OBV +/// +/// Key characteristics: +/// - Cumulative indicator +/// - Volume-based momentum measure +/// - Leading indicator +/// - No upper or lower bounds +/// - Focuses on volume flow +/// +/// Formula: +/// If Close > Previous Close: +/// OBV = Previous OBV + Volume +/// If Close < Previous Close: +/// OBV = Previous OBV - Volume +/// If Close = Previous Close: +/// OBV = Previous OBV +/// +/// Market Applications: +/// - Trend confirmation +/// - Potential breakouts +/// - Divergence analysis +/// - Volume flow analysis +/// - Price movement prediction +/// +/// Sources: +/// Joe Granville - Original development (1963) +/// https://www.investopedia.com/terms/o/onbalancevolume.asp +/// +/// Note: Rising OBV suggests buying pressure, while falling OBV suggests selling pressure +/// + +[SkipLocalsInit] +public sealed class Obv : AbstractBase +{ + private double _prevClose; + private double _prevObv; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Obv() + { + WarmupPeriod = 2; // Need previous close + Name = "OBV"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Obv(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _prevObv = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate OBV + if (BarInput.Close > _prevClose) + { + _prevObv += BarInput.Volume; + } + else if (BarInput.Close < _prevClose) + { + _prevObv -= BarInput.Volume; + } + // If prices equal, OBV remains the same + + // Store current close for next calculation + _prevClose = BarInput.Close; + + IsHot = _index >= WarmupPeriod; + return _prevObv; + } +} diff --git a/lib/volume/Pvi.cs b/lib/volume/Pvi.cs new file mode 100644 index 00000000..400da515 --- /dev/null +++ b/lib/volume/Pvi.cs @@ -0,0 +1,113 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PVI: Positive Volume Index +/// A cumulative indicator that focuses on days when volume increases from the previous day. +/// It is based on the premise that the public is active on days with higher volume. +/// +/// +/// The PVI calculation process: +/// 1. Compare current volume with previous volume +/// 2. If current volume is greater than previous volume: +/// PVI = Previous PVI + (((Close - Previous Close) / Previous Close) * Previous PVI) +/// 3. If current volume is less than or equal to previous volume: +/// PVI = Previous PVI +/// +/// Key characteristics: +/// - Cumulative indicator +/// - Only updates on higher volume days +/// - Starts at base value of 1000 +/// - Focuses on public activity +/// - Volume-driven measure +/// +/// Formula: +/// If Volume > Previous Volume: +/// PVI = Previous PVI + (Price % Change * Previous PVI) +/// Else: +/// PVI = Previous PVI +/// +/// Market Applications: +/// - Public participation tracking +/// - Trend identification +/// - Market timing +/// - Volume analysis +/// - Price confirmation +/// +/// Sources: +/// Norman Fosback - Original development +/// https://www.investopedia.com/terms/p/pvi.asp +/// +/// Note: Rising PVI suggests public buying pressure, while falling PVI suggests public selling pressure +/// + +[SkipLocalsInit] +public sealed class Pvi : AbstractBase +{ + private double _prevClose; + private double _prevVolume; + private double _prevPvi; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvi() + { + WarmupPeriod = 2; // Need previous volume and close + Name = "PVI"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvi(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _prevVolume = 0; + _prevPvi = 1000; // Standard starting value + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous values + if (_index == 1) + { + _prevClose = BarInput.Close; + _prevVolume = BarInput.Volume; + return _prevPvi; + } + + // Calculate PVI + if (BarInput.Volume > _prevVolume) + { + double priceChange = ((BarInput.Close - _prevClose) / _prevClose); + _prevPvi += priceChange * _prevPvi; + } + + // Store current values for next calculation + _prevClose = BarInput.Close; + _prevVolume = BarInput.Volume; + + IsHot = _index >= WarmupPeriod; + return _prevPvi; + } +} diff --git a/lib/volume/Pvo.cs b/lib/volume/Pvo.cs new file mode 100644 index 00000000..bfce2a6f --- /dev/null +++ b/lib/volume/Pvo.cs @@ -0,0 +1,111 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PVO: Percentage Volume Oscillator +/// A momentum indicator for volume that shows the relationship between two volume moving averages +/// as a percentage. Similar to the Price Oscillator but uses volume instead of price. +/// +/// +/// The PVO calculation process: +/// 1. Calculate short-term EMA of volume +/// 2. Calculate long-term EMA of volume +/// 3. Calculate PVO: +/// PVO = ((Short EMA - Long EMA) / Long EMA) * 100 +/// +/// Key characteristics: +/// - Volume-based momentum indicator +/// - Oscillates around zero +/// - Shows volume trends +/// - Default periods are 12 and 26 days +/// - Percentage-based measure +/// +/// Formula: +/// Short EMA = EMA(Volume, shortPeriod) +/// Long EMA = EMA(Volume, longPeriod) +/// PVO = ((Short EMA - Long EMA) / Long EMA) * 100 +/// +/// Market Applications: +/// - Volume trend analysis +/// - Divergence identification +/// - Volume momentum measurement +/// - Market tops and bottoms +/// - Trading volume patterns +/// +/// Sources: +/// https://www.investopedia.com/terms/p/pvo.asp +/// +/// Note: Positive values indicate higher short-term volume, while negative values indicate higher long-term volume +/// + +[SkipLocalsInit] +public sealed class Pvo : AbstractBase +{ + private readonly int _longPeriod; + private double _shortEma; + private double _longEma; + private readonly double _shortAlpha; + private readonly double _longAlpha; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvo(int shortPeriod = 12, int longPeriod = 26) + { + _longPeriod = longPeriod; + WarmupPeriod = longPeriod; + Name = $"PVO({shortPeriod},{_longPeriod})"; + _shortAlpha = 2.0 / (shortPeriod + 1); + _longAlpha = 2.0 / (longPeriod + 1); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvo(object source, int shortPeriod = 12, int longPeriod = 26) : this(shortPeriod, longPeriod) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _shortEma = 0; + _longEma = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Initialize or update EMAs + if (_index <= _longPeriod) + { + _shortEma = BarInput.Volume; + _longEma = BarInput.Volume; + return 0; + } + + // Update EMAs + _shortEma = (_shortAlpha * BarInput.Volume) + ((1 - _shortAlpha) * _shortEma); + _longEma = (_longAlpha * BarInput.Volume) + ((1 - _longAlpha) * _longEma); + + // Calculate PVO + + double pvo = Math.Abs(_longEma) >= double.Epsilon ? ((_shortEma - _longEma) / _longEma) * 100 : 0; + + IsHot = _index >= WarmupPeriod; + return pvo; + } +} diff --git a/lib/volume/Pvol.cs b/lib/volume/Pvol.cs new file mode 100644 index 00000000..f9c6d975 --- /dev/null +++ b/lib/volume/Pvol.cs @@ -0,0 +1,108 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PVOL: Price-Volume +/// A technical indicator that measures the relationship between price and volume changes, +/// helping to identify the strength of price movements. +/// +/// +/// The PVOL calculation process: +/// 1. Calculate price change: +/// Price Change = (Close - Previous Close) / Previous Close +/// 2. Calculate volume change: +/// Volume Change = (Volume - Previous Volume) / Previous Volume +/// 3. Calculate PVOL: +/// PVOL = Price Change * Volume Change * 100 +/// +/// Key characteristics: +/// - Measures price-volume relationship +/// - Oscillates around zero +/// - Shows momentum strength +/// - Identifies volume-supported moves +/// - No specific boundaries +/// +/// Formula: +/// Price Change = (Close - Previous Close) / Previous Close +/// Volume Change = (Volume - Previous Volume) / Previous Volume +/// PVOL = Price Change * Volume Change * 100 +/// +/// Market Applications: +/// - Price movement confirmation +/// - Volume analysis +/// - Trend strength assessment +/// - Divergence identification +/// - Market momentum analysis +/// +/// Note: High positive values indicate strong upward momentum with volume support, +/// while high negative values indicate strong downward momentum with volume support +/// + +[SkipLocalsInit] +public sealed class Pvol : AbstractBase +{ + private double _prevClose; + private double _prevVolume; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvol() + { + WarmupPeriod = 2; // Need previous close and volume + Name = "PVOL"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvol(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _prevVolume = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous values + if (_index == 1) + { + _prevClose = BarInput.Close; + _prevVolume = BarInput.Volume; + return 0; + } + + // Calculate price and volume changes + double priceChange = (Math.Abs(_prevClose) >= double.Epsilon) ? (BarInput.Close - _prevClose) / _prevClose : 0; + double volumeChange = (Math.Abs(_prevVolume) >= double.Epsilon) ? (BarInput.Volume - _prevVolume) / _prevVolume : 0; + + // Store current values for next calculation + _prevClose = BarInput.Close; + _prevVolume = BarInput.Volume; + + // Calculate PVOL + double pvol = priceChange * volumeChange * 100; + + IsHot = _index >= WarmupPeriod; + return pvol; + } +} diff --git a/lib/volume/Pvr.cs b/lib/volume/Pvr.cs new file mode 100644 index 00000000..702029f7 --- /dev/null +++ b/lib/volume/Pvr.cs @@ -0,0 +1,109 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PVR: Price Volume Rank +/// A technical indicator that ranks price and volume movements to identify +/// significant market moves based on their combined strength. +/// +/// +/// The PVR calculation process: +/// 1. Calculate price change percentage: +/// Price Change = ((Close - Previous Close) / Previous Close) * 100 +/// 2. Calculate volume ratio: +/// Volume Ratio = Current Volume / Previous Volume +/// 3. Calculate PVR: +/// PVR = Price Change * Volume Ratio +/// +/// Key characteristics: +/// - Combines price and volume analysis +/// - No specific boundaries +/// - Measures movement significance +/// - Volume-weighted price change +/// - Identifies strong moves +/// +/// Formula: +/// Price Change = ((Close - Previous Close) / Previous Close) * 100 +/// Volume Ratio = Volume / Previous Volume +/// PVR = Price Change * Volume Ratio +/// +/// Market Applications: +/// - Significant move identification +/// - Volume-supported moves +/// - Trend strength analysis +/// - Breakout confirmation +/// - Market momentum measurement +/// +/// Note: Higher absolute values indicate more significant price moves with volume support +/// + +[SkipLocalsInit] +public sealed class Pvr : AbstractBase +{ + private double _prevClose; + private double _prevVolume; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvr() + { + WarmupPeriod = 2; // Need previous close and volume + Name = "PVR"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvr(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _prevVolume = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous values + if (_index == 1) + { + _prevClose = BarInput.Close; + _prevVolume = BarInput.Volume; + return 0; + } + + // Calculate price change percentage + double priceChange = (Math.Abs(_prevClose) > double.Epsilon) ? ((BarInput.Close - _prevClose) / _prevClose) * 100 : 0; + + // Calculate volume ratio + double volumeRatio = (Math.Abs(_prevVolume) > double.Epsilon) ? BarInput.Volume / _prevVolume : 1; + + // Store current values for next calculation + _prevClose = BarInput.Close; + _prevVolume = BarInput.Volume; + + // Calculate PVR + double pvr = priceChange * volumeRatio; + + IsHot = _index >= WarmupPeriod; + return pvr; + } +} diff --git a/lib/volume/Pvt.cs b/lib/volume/Pvt.cs new file mode 100644 index 00000000..150bf855 --- /dev/null +++ b/lib/volume/Pvt.cs @@ -0,0 +1,105 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PVT: Price Volume Trend +/// A momentum indicator that combines price and volume to determine the strength of a trend. +/// Similar to OBV but uses percentage price changes in its calculation. +/// +/// +/// The PVT calculation process: +/// 1. Calculate price change percentage: +/// Price Change = (Close - Previous Close) / Previous Close +/// 2. Calculate PVT: +/// PVT = Previous PVT + (Price Change * Volume) +/// +/// Key characteristics: +/// - Cumulative indicator +/// - Volume-weighted price changes +/// - No upper or lower bounds +/// - Trend strength measure +/// - More sensitive than OBV +/// +/// Formula: +/// Price Change = (Close - Previous Close) / Previous Close +/// PVT = Previous PVT + (Price Change * Volume) +/// +/// Market Applications: +/// - Trend confirmation +/// - Divergence analysis +/// - Volume-price relationships +/// - Support/resistance levels +/// - Market momentum +/// +/// Sources: +/// Norman Fosback - Original development +/// https://www.investopedia.com/terms/p/pvt.asp +/// +/// Note: Rising PVT suggests buying pressure, while falling PVT suggests selling pressure +/// + +[SkipLocalsInit] +public sealed class Pvt : AbstractBase +{ + private double _prevClose; + private double _prevPvt; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvt() + { + WarmupPeriod = 2; // Need previous close + Name = "PVT"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pvt(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _prevPvt = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate price change percentage + double priceChange = (Math.Abs(_prevClose) > double.Epsilon) ? (BarInput.Close - _prevClose) / _prevClose : 0; + + // Calculate PVT + _prevPvt += priceChange * BarInput.Volume; + + // Store current close for next calculation + _prevClose = BarInput.Close; + + IsHot = _index >= WarmupPeriod; + return _prevPvt; + } +} diff --git a/lib/volume/Tvi.cs b/lib/volume/Tvi.cs new file mode 100644 index 00000000..864dec45 --- /dev/null +++ b/lib/volume/Tvi.cs @@ -0,0 +1,112 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// TVI: Trade Volume Index +/// A technical indicator that determines whether a security is being accumulated or distributed +/// based on price changes relative to a minimum tick value. +/// +/// +/// The TVI calculation process: +/// 1. Calculate price change: +/// Price Change = Close - Previous Close +/// 2. Compare price change to minimum tick value: +/// If |Price Change| >= Minimum Tick: +/// Add/Subtract volume based on price direction +/// +/// Key characteristics: +/// - Volume-based trend indicator +/// - Uses minimum tick value +/// - Cumulative measure +/// - No upper or lower bounds +/// - Focuses on significant moves +/// +/// Formula: +/// If |Close - Previous Close| >= Minimum Tick: +/// If Close > Previous Close: +/// TVI = Previous TVI + Volume +/// If Close < Previous Close: +/// TVI = Previous TVI - Volume +/// Else: +/// TVI = Previous TVI +/// +/// Market Applications: +/// - Trend identification +/// - Volume analysis +/// - Accumulation/distribution +/// - Price movement significance +/// - Trading signal generation +/// +/// Note: Rising TVI suggests accumulation, while falling TVI suggests distribution +/// + +[SkipLocalsInit] +public sealed class Tvi : AbstractBase +{ + private readonly double _minTick; + private double _prevClose; + private double _prevTvi; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Tvi(double minTick = 0.5) + { + _minTick = minTick; + WarmupPeriod = 2; // Need previous close + Name = $"TVI({_minTick})"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Tvi(object source, double minTick = 0.5) : this(minTick) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _prevTvi = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate price change + double priceChange = BarInput.Close - _prevClose; + + // Update TVI if price change exceeds minimum tick + if (Math.Abs(priceChange) >= _minTick) + { + _prevTvi += priceChange > 0 ? BarInput.Volume : -BarInput.Volume; + } + + // Store current close for next calculation + _prevClose = BarInput.Close; + + IsHot = _index >= WarmupPeriod; + return _prevTvi; + } +} diff --git a/lib/volume/_list.md b/lib/volume/_list.md index 54149915..8fdb2f14 100644 --- a/lib/volume/_list.md +++ b/lib/volume/_list.md @@ -1,5 +1,5 @@ # Volume indicators -Done: 6, Todo: 12 +Done: 15, Todo: 3 ✔️ ADL - Chaikin Accumulation Distribution Line ✔️ ADOSC - Chaikin Accumulation Distribution Oscillator @@ -7,15 +7,16 @@ Done: 6, Todo: 12 ✔️ CMF - Chaikin Money Flow ✔️ EOM - Ease of Movement ✔️ KVO - Klinger Volume Oscillator -MFI - Money Flow Index -NVI - Negative Volume Index -OBV - On-Balance Volume -PVI - Positive Volume Index -PVOL - Price-Volume -PVO - Percentage Volume Oscillator -PVR - Price Volume Rank -PVT - Price Volume Trend -TVI - Trade Volume Index +✔️ MFI - Money Flow Index +✔️ NVI - Negative Volume Index +✔️ OBV - On-Balance Volume +✔️ PVI - Positive Volume Index +✔️ PVOL - Price-Volume +✔️ PVO - Percentage Volume Oscillator +✔️ PVR - Price Volume Rank +✔️ PVT - Price Volume Trend +✔️ TVI - Trade Volume Index +VF - Volume Force VP - Volume Profile VWAP - Volume Weighted Average Price VWMA - Volume Weighted Moving Average