diff --git a/.roo/mcp.json b/.roo/mcp.json
new file mode 100644
index 00000000..87f93eff
--- /dev/null
+++ b/.roo/mcp.json
@@ -0,0 +1,23 @@
+{
+ "mcpServers": {
+ "dotnet-semantic-mcp": {
+ "command": "dotnet-semantic-mcp",
+ "args": [],
+ "cwd": "${workspaceFolder}",
+ "alwaysAllow": [
+ "ast_map",
+ "ast_references",
+ "ast_hierarchy",
+ "ast_chunk",
+ "ast_dependencies",
+ "ast_search",
+ "ast_details",
+ "ast_attributes",
+ "ast_diagnostics",
+ "diag",
+ "map"
+ ],
+ "disabled": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/_sidebar.md b/_sidebar.md
index f8d4707b..49889b04 100644
--- a/_sidebar.md
+++ b/_sidebar.md
@@ -1,316 +1,337 @@
* **Core concepts**
- * [Architecture](docs/architecture.md)
- * [API](docs/api.md)
- * [Benchmarks](docs/benchmarks.md)
- * [Indicators](docs/indicators.md)
- * [Usage Guides](docs/usage.md)
- * [Integration](docs/integration.md)
- * [Validation](docs/validation.md)
- * [MA Qualities](docs/ma-qualities.md)
- * [Error Metrics](docs/errors.md)
+ * [Architecture](/docs/architecture.md)
+ * [API](/docs/api.md)
+ * [Benchmarks](/docs/benchmarks.md)
+ * [Indicators](/docs/indicators.md)
+ * [Usage Guides](/docs/usage.md)
+ * [Integration](/docs/integration.md)
+ * [Validation](/docs/validation.md)
+ * [MA Qualities](/docs/ma-qualities.md)
+ * [Error Metrics](/docs/errors.md)
* **Trends (FIR)**
- * [Overview](lib/trends_FIR/_index.md)
- * [ALMA - Arnaud Legoux MA](lib/trends_FIR/alma/Alma.md)
- * [BLMA - Blackman Window MA](lib/trends_FIR/blma/Blma.md)
- * [BWMA - Blackman-Harris MA](lib/trends_FIR/bwma/Bwma.md)
- * [CONV - Convolution](lib/trends_FIR/conv/Conv.md)
- * [DWMA - Double Weighted MA](lib/trends_FIR/dwma/Dwma.md)
- * [GWMA - Gaussian Weighted MA](lib/trends_FIR/gwma/Gwma.md)
- * [HAMMA - Hamming MA](lib/trends_FIR/hamma/Hamma.md)
- * [HANMA - Hanning MA](lib/trends_FIR/hanma/Hanma.md)
- * [HMA - Hull MA](lib/trends_FIR/hma/Hma.md)
- * [HWMA - Henderson Weighted MA](lib/trends_FIR/hwma/Hwma.md)
- * [LSMA - Least Squares MA](lib/trends_FIR/lsma/Lsma.md)
- * [PWMA - Pascal Weighted MA](lib/trends_FIR/pwma/Pwma.md)
- * [SGMA - Savitzky-Golay MA](lib/trends_FIR/sgma/Sgma.md)
- * [SINEMA - Sine Weighted MA](lib/trends_FIR/sinema/Sinema.md)
- * [SMA - Simple MA](lib/trends_FIR/sma/Sma.md)
- * [TRIMA - Triangular MA](lib/trends_FIR/trima/Trima.md)
- * [WMA - Weighted MA](lib/trends_FIR/wma/Wma.md)
+ * [Overview](/lib/trends_FIR/_index.md)
+ * [ALMA - Arnaud Legoux MA](/lib/trends_FIR/alma/Alma.md)
+ * [BLMA - Blackman Window MA](/lib/trends_FIR/blma/Blma.md)
+ * [BWMA - Blackman-Harris MA](/lib/trends_FIR/bwma/Bwma.md)
+ * [CONV - Convolution](/lib/trends_FIR/conv/Conv.md)
+ * [DWMA - Double Weighted MA](/lib/trends_FIR/dwma/Dwma.md)
+ * [GWMA - Gaussian Weighted MA](/lib/trends_FIR/gwma/Gwma.md)
+ * [HAMMA - Hamming MA](/lib/trends_FIR/hamma/Hamma.md)
+ * [HANMA - Hanning MA](/lib/trends_FIR/hanma/Hanma.md)
+ * [HMA - Hull MA](/lib/trends_FIR/hma/Hma.md)
+ * [HWMA - Henderson Weighted MA](/lib/trends_FIR/hwma/Hwma.md)
+ * [LSMA - Least Squares MA](/lib/trends_FIR/lsma/Lsma.md)
+ * [PWMA - Pascal Weighted MA](/lib/trends_FIR/pwma/Pwma.md)
+ * [SGMA - Savitzky-Golay MA](/lib/trends_FIR/sgma/Sgma.md)
+ * [SINEMA - Sine Weighted MA](/lib/trends_FIR/sinema/Sinema.md)
+ * [SMA - Simple MA](/lib/trends_FIR/sma/Sma.md)
+ * [TRIMA - Triangular MA](/lib/trends_FIR/trima/Trima.md)
+ * [WMA - Weighted MA](/lib/trends_FIR/wma/Wma.md)
* **Trends (IIR)**
- * [Overview](lib/trends_IIR/_index.md)
- * [DEMA - Double Exponential MA](lib/trends_IIR/dema/Dema.md)
- * [DSMA - Deviation-Scaled MA](lib/trends_IIR/dsma/Dsma.md)
- * [EMA - Exponential MA](lib/trends_IIR/ema/Ema.md)
- * [FRAMA - Fractal Adaptive MA](lib/trends_IIR/frama/Frama.md)
- * [HEMA - Hull Exponential MA](lib/trends_IIR/hema/Hema.md)
- * [HTIT - Hilbert Transform Instant Trendline](lib/trends_IIR/htit/Htit.md)
- * [JMA - Jurik MA](lib/trends_IIR/jma/Jma.md)
- * [KAMA - Kaufman Adaptive MA](lib/trends_IIR/kama/Kama.md)
- * [MAMA - MESA Adaptive MA](lib/trends_IIR/mama/Mama.md)
- * [MGDI - McGinley Dynamic](lib/trends_IIR/mgdi/Mgdi.md)
- * [MMA - Modified MA](lib/trends_IIR/mma/Mma.md)
- * [QEMA - Quadruple Exponential MA](lib/trends_IIR/qema/Qema.md)
- * [REMA - Regularized Exponential MA](lib/trends_IIR/rema/Rema.md)
- * [RGMA - Recursive Gaussian MA](lib/trends_IIR/rgma/Rgma.md)
- * [RMA - Rolling MA](lib/trends_IIR/rma/Rma.md)
- * [T3 - Tillson T3 MA](lib/trends_IIR/t3/T3.md)
- * [TEMA - Triple Exponential MA](lib/trends_IIR/tema/Tema.md)
- * [VAMA - Volatility Adjusted MA](lib/trends_IIR/vama/Vama.md)
- * [VIDYA - Variable Index Dynamic Average](lib/trends_IIR/vidya/Vidya.md)
- * [YZVAMA - Yang-Zhang Volatility Adjusted MA](lib/trends_IIR/yzvama/Yzvama.md)
- * [ZLEMA - Zero-Lag Exponential MA](lib/trends_IIR/zlema/Zlema.md)
+ * [Overview](/lib/trends_IIR/_index.md)
+ * [DEMA - Double Exponential MA](/lib/trends_IIR/dema/Dema.md)
+ * [DSMA - Deviation-Scaled MA](/lib/trends_IIR/dsma/Dsma.md)
+ * [EMA - Exponential MA](/lib/trends_IIR/ema/Ema.md)
+ * [FRAMA - Fractal Adaptive MA](/lib/trends_IIR/frama/Frama.md)
+ * [HEMA - Hull Exponential MA](/lib/trends_IIR/hema/Hema.md)
+ * [HTIT - Hilbert Transform Instant Trendline](/lib/trends_IIR/htit/Htit.md)
+ * [JMA - Jurik MA](/lib/trends_IIR/jma/Jma.md)
+ * [KAMA - Kaufman Adaptive MA](/lib/trends_IIR/kama/Kama.md)
+ * [MAMA - MESA Adaptive MA](/lib/trends_IIR/mama/Mama.md)
+ * [MGDI - McGinley Dynamic](/lib/trends_IIR/mgdi/Mgdi.md)
+ * [MMA - Modified MA](/lib/trends_IIR/mma/Mma.md)
+ * [QEMA - Quadruple Exponential MA](/lib/trends_IIR/qema/Qema.md)
+ * [REMA - Regularized Exponential MA](/lib/trends_IIR/rema/Rema.md)
+ * [RGMA - Recursive Gaussian MA](/lib/trends_IIR/rgma/Rgma.md)
+ * [RMA - Rolling MA](/lib/trends_IIR/rma/Rma.md)
+ * [T3 - Tillson T3 MA](/lib/trends_IIR/t3/T3.md)
+ * [TEMA - Triple Exponential MA](/lib/trends_IIR/tema/Tema.md)
+ * [VAMA - Volatility Adjusted MA](/lib/trends_IIR/vama/Vama.md)
+ * [VIDYA - Variable Index Dynamic Average](/lib/trends_IIR/vidya/Vidya.md)
+ * [YZVAMA - Yang-Zhang Volatility Adjusted MA](/lib/trends_IIR/yzvama/Yzvama.md)
+ * [ZLDEMA - Zero-Lag Double Exponential MA](/lib/trends_IIR/zldema/Zldema.md)
+ * [ZLEMA - Zero-Lag Exponential MA](/lib/trends_IIR/zlema/Zlema.md)
+ * [ZLTEMA - Zero-Lag Triple Exponential MA](/lib/trends_IIR/zltema/Zltema.md)
* **Filters**
- * [Overview](lib/filters/_index.md)
- * [BESSEL - Bessel Filter](lib/filters/bessel/Bessel.md)
- * [BILATERAL - Bilateral Filter](lib/filters/bilateral/Bilateral.md)
- * [BPF - Bandpass Filter](lib/filters/bpf/Bpf.md)
- * [BUTTER - Butterworth Filter](lib/filters/butter/Butter.md)
- * [CHEBY1 - Chebyshev Type I](lib/filters/cheby1/Cheby1.md)
- * [CHEBY2 - Chebyshev Type II](lib/filters/cheby2/Cheby2.md)
- * [ELLIPTIC - Elliptic Filter](lib/filters/elliptic/Elliptic.md)
- * [GAUSS - Gaussian Filter](lib/filters/gauss/Gauss.md)
- * [HANN - Hann Filter](lib/filters/hann/Hann.md)
- * [HP - Hodrick-Prescott Filter](lib/filters/hp/Hp.md)
- * [HPF - High Pass Filter](lib/filters/hpf/Hpf.md)
- * [KALMAN - Kalman Filter](lib/filters/kalman/Kalman.md)
- * [LOESS - LOESS Smoothing](lib/filters/loess/Loess.md)
- * [NOTCH - Notch Filter](lib/filters/notch/Notch.md)
- * [SGF - Savitzky-Golay Filter](lib/filters/sgf/Sgf.md)
- * [SSF - Ehlers Super Smooth Filter](lib/filters/ssf/Ssf.md)
- * [USF - Ehlers Ultimate Smoother Filter](lib/filters/usf/Usf.md)
- * [WIENER - Wiener Filter](lib/filters/wiener/Wiener.md)
+ * [Overview](/lib/filters/_index.md)
+ * [BESSEL - Bessel Filter](/lib/filters/bessel/Bessel.md)
+ * [BILATERAL - Bilateral Filter](/lib/filters/bilateral/Bilateral.md)
+ * [BPF - Bandpass Filter](/lib/filters/bpf/Bpf.md)
+ * [BUTTER - Butterworth Filter](/lib/filters/butter/Butter.md)
+ * [CHEBY1 - Chebyshev Type I](/lib/filters/cheby1/Cheby1.md)
+ * [CHEBY2 - Chebyshev Type II](/lib/filters/cheby2/Cheby2.md)
+ * [ELLIPTIC - Elliptic Filter](/lib/filters/elliptic/Elliptic.md)
+ * [GAUSS - Gaussian Filter](/lib/filters/gauss/Gauss.md)
+ * [HANN - Hann Filter](/lib/filters/hann/Hann.md)
+ * [HP - Hodrick-Prescott Filter](/lib/filters/hp/Hp.md)
+ * [HPF - High Pass Filter](/lib/filters/hpf/Hpf.md)
+ * [KALMAN - Kalman Filter](/lib/filters/kalman/Kalman.md)
+ * [LOESS - LOESS Smoothing](/lib/filters/loess/Loess.md)
+ * [NOTCH - Notch Filter](/lib/filters/notch/Notch.md)
+ * [SGF - Savitzky-Golay Filter](/lib/filters/sgf/Sgf.md)
+ * [SSF - Ehlers Super Smooth Filter](/lib/filters/ssf/Ssf.md)
+ * [USF - Ehlers Ultimate Smoother Filter](/lib/filters/usf/Usf.md)
+ * [WIENER - Wiener Filter](/lib/filters/wiener/Wiener.md)
* **Dynamics**
- * [Overview](lib/dynamics/_index.md)
- * [ADX - Average Directional Index](lib/dynamics/adx/Adx.md)
- * [ADXR - Average Directional Movement Rating](lib/dynamics/adxr/Adxr.md)
- * [ALLIGATOR - Williams Alligator](lib/dynamics/alligator/Alligator.md)
- * [AMAT - Archer Moving Averages Trends](lib/dynamics/amat/Amat.md)
- * [AROON - Aroon](lib/dynamics/aroon/Aroon.md)
- * [AROONOSC - Aroon Oscillator](lib/dynamics/aroonosc/AroonOsc.md)
- * [CHOP - Choppiness Index](lib/dynamics/chop/Chop.md)
- * [DMX - Jurik Directional Movement Index](lib/dynamics/dmx/Dmx.md)
- * [DX - Directional Movement Index](lib/dynamics/dx/Dx.md)
- * [HT_TRENDMODE - Hilbert Transform Trend Mode](lib/dynamics/ht_trendmode/HtTrendmode.md)
- * [ICHIMOKU - Ichimoku Cloud](lib/dynamics/ichimoku/Ichimoku.md)
- * [IMI - Intraday Momentum Index](lib/dynamics/imi/Imi.md)
- * [QSTICK - Qstick Indicator](lib/dynamics/qstick/Qstick.md)
- * [SUPER - SuperTrend](lib/dynamics/super/Super.md)
- * [TTM - TTM Trend](lib/dynamics/ttm/Ttm.md)
- * [VORTEX - Vortex Indicator](lib/dynamics/vortex/Vortex.md)
+ * [Overview](/lib/dynamics/_index.md)
+ * [ADX - Average Directional Index](/lib/dynamics/adx/Adx.md)
+ * [ADXR - Average Directional Movement Rating](/lib/dynamics/adxr/Adxr.md)
+ * [ALLIGATOR - Williams Alligator](/lib/dynamics/alligator/Alligator.md)
+ * [AMAT - Archer Moving Averages Trends](/lib/dynamics/amat/Amat.md)
+ * [AROON - Aroon](/lib/dynamics/aroon/Aroon.md)
+ * [AROONOSC - Aroon Oscillator](/lib/dynamics/aroonosc/AroonOsc.md)
+ * [CHOP - Choppiness Index](/lib/dynamics/chop/Chop.md)
+ * [DMX - Jurik Directional Movement Index](/lib/dynamics/dmx/Dmx.md)
+ * [DX - Directional Movement Index](/lib/dynamics/dx/Dx.md)
+ * [HT_TRENDMODE - Hilbert Transform Trend Mode](/lib/dynamics/ht_trendmode/HtTrendmode.md)
+ * [ICHIMOKU - Ichimoku Cloud](/lib/dynamics/ichimoku/Ichimoku.cs)
+ * [IMI - Intraday Momentum Index](/lib/dynamics/imi/Imi.cs)
+ * [QSTICK - Qstick Indicator](/lib/dynamics/qstick/Qstick.md)
+ * [SUPER - SuperTrend](/lib/dynamics/super/Super.md)
+ * [TTM_SQUEEZE - TTM Squeeze](/lib/dynamics/ttm_squeeze/TtmSqueeze.md)
+ * [TTM_TREND - TTM Trend](/lib/dynamics/ttm_trend/TtmTrend.md)
+ * [VORTEX - Vortex Indicator](/lib/dynamics/vortex/Vortex.md)
* **Oscillators**
- * [Overview](lib/oscillators/_index.md)
- * [AC - Acceleration Oscillator](lib/oscillators/ac/Ac.md)
- * [AO - Awesome Oscillator](lib/oscillators/ao/Ao.md)
- * [APO - Absolute Price Oscillator](lib/oscillators/apo/Apo.md)
- * [BBB - Bollinger %B](lib/oscillators/bbb/Bbb.md)
- * [BBS - Bollinger Band Squeeze](lib/oscillators/bbs/Bbs.md)
- * [CFO - Chande Forecast Oscillator](lib/oscillators/cfo/Cfo.md)
- * [DPO - Detrended Price Oscillator](lib/oscillators/dpo/Dpo.md)
- * [FISHER - Fisher Transform](lib/oscillators/fisher/Fisher.md)
- * [INERTIA - Inertia](lib/oscillators/inertia/Inertia.md)
- * [KDJ - KDJ Indicator](lib/oscillators/kdj/Kdj.md)
- * [PGO - Pretty Good Oscillator](lib/oscillators/pgo/Pgo.md)
- * [SMI - Stochastic Momentum Index](lib/oscillators/smi/Smi.md)
- * [STOCH - Stochastic Oscillator](lib/oscillators/stoch/Stoch.md)
- * [STOCHF - Stochastic Fast](lib/oscillators/stochf/Stochf.md)
- * [STOCHRSI - Stochastic RSI](lib/oscillators/stochrsi/Stochrsi.md)
- * [TRIX - Triple Exponential Average](lib/oscillators/trix/Trix.md)
- * [ULTOSC - Ultimate Oscillator](lib/oscillators/ultosc/Ultosc.md)
- * [WILLR - Williams %R](lib/oscillators/willr/Willr.md)
+ * [Overview](/lib/oscillators/_index.md)
+ * [AC - Acceleration Oscillator](/lib/oscillators/ac/Ac.md)
+ * [AO - Awesome Oscillator](/lib/oscillators/ao/Ao.md)
+ * [APO - Absolute Price Oscillator](/lib/oscillators/apo/Apo.md)
+ * [BBB - Bollinger %B](/lib/oscillators/bbb/Bbb.md)
+ * [BBS - Bollinger Band Squeeze](/lib/oscillators/bbs/Bbs.md)
+ * [CFO - Chande Forecast Oscillator](/lib/oscillators/cfo/Cfo.md)
+ * [DPO - Detrended Price Oscillator](/lib/oscillators/dpo/Dpo.md)
+ * [FISHER - Fisher Transform](/lib/oscillators/fisher/Fisher.md)
+ * [INERTIA - Inertia](/lib/oscillators/inertia/Inertia.md)
+ * [KDJ - KDJ Indicator](/lib/oscillators/kdj/Kdj.md)
+ * [PGO - Pretty Good Oscillator](/lib/oscillators/pgo/Pgo.md)
+ * [SMI - Stochastic Momentum Index](/lib/oscillators/smi/Smi.md)
+ * [STOCH - Stochastic Oscillator](/lib/oscillators/stoch/Stoch.md)
+ * [STOCHF - Stochastic Fast](/lib/oscillators/stochf/Stochf.md)
+ * [STOCHRSI - Stochastic RSI](/lib/oscillators/stochrsi/Stochrsi.md)
+ * [TRIX - Triple Exponential Average](/lib/oscillators/trix/Trix.md)
+ * [TTM_WAVE - TTM Wave](/lib/oscillators/ttm_wave/TtmWave.md)
+ * [ULTOSC - Ultimate Oscillator](/lib/oscillators/ultosc/Ultosc.md)
+ * [WILLR - Williams %R](/lib/oscillators/willr/Willr.md)
* **Momentum**
- * [Overview](lib/momentum/_index.md)
- * [APO - Absolute Price Oscillator](lib/momentum/apo/Apo.md)
- * [BOP - Balance of Power](lib/momentum/bop/Bop.md)
- * [CCI - Commodity Channel Index](lib/momentum/cci/Cci.md)
- * [CFB - Jurik Composite Fractal Behavior](lib/momentum/cfb/Cfb.md)
- * [CMO - Chande Momentum Oscillator](lib/momentum/cmo/Cmo.md)
- * [MACD - Moving Average Convergence Divergence](lib/momentum/macd/Macd.md)
- * [MOM - Momentum](lib/momentum/mom/Mom.md)
- * [PMO - Price Momentum Oscillator](lib/momentum/pmo/Pmo.md)
- * [PPO - Percentage Price Oscillator](lib/momentum/ppo/Ppo.md)
- * [PRS - Price Relative Strength](lib/momentum/prs/Prs.md)
- * [ROC - Rate of Change](lib/momentum/roc/Roc.md)
- * [ROCP - Rate of Change Percentage](lib/momentum/rocp/Rocp.md)
- * [ROCR - Rate of Change Ratio](lib/momentum/rocr/Rocr.md)
- * [RSI - Relative Strength Index](lib/momentum/rsi/Rsi.md)
- * [RSX - Jurik Relative Strength X](lib/momentum/rsx/Rsx.md)
- * [TSI - True Strength Index](lib/momentum/tsi/Tsi.md)
- * [VEL - Jurik Velocity](lib/momentum/vel/Vel.md)
+ * [Overview](/lib/momentum/_index.md)
+ * [APO - Absolute Price Oscillator](/lib/momentum/apo/Apo.md)
+ * [BOP - Balance of Power](/lib/momentum/bop/Bop.md)
+ * [CCI - Commodity Channel Index](/lib/momentum/cci/Cci.md)
+ * [CFB - Jurik Composite Fractal Behavior](/lib/momentum/cfb/Cfb.md)
+ * [CMO - Chande Momentum Oscillator](/lib/momentum/cmo/Cmo.md)
+ * [MACD - Moving Average Convergence Divergence](/lib/momentum/macd/Macd.md)
+ * [MOM - Momentum](/lib/momentum/mom/Mom.md)
+ * [PMO - Price Momentum Oscillator](/lib/momentum/pmo/Pmo.md)
+ * [PPO - Percentage Price Oscillator](/lib/momentum/ppo/Ppo.md)
+ * [PRS - Price Relative Strength](/lib/momentum/prs/Prs.md)
+ * [ROC - Rate of Change](/lib/momentum/roc/Roc.md)
+ * [ROCP - Rate of Change Percentage](/lib/momentum/rocp/Rocp.md)
+ * [ROCR - Rate of Change Ratio](/lib/momentum/rocr/Rocr.md)
+ * [RSI - Relative Strength Index](/lib/momentum/rsi/Rsi.md)
+ * [RSX - Jurik Relative Strength X](/lib/momentum/rsx/Rsx.md)
+ * [TSI - True Strength Index](/lib/momentum/tsi/Tsi.md)
+ * [VEL - Jurik Velocity](/lib/momentum/vel/Vel.md)
* **Volatility**
- * [Overview](lib/volatility/_index.md)
- * [ADR - Average Daily Range](lib/volatility/adr/Adr.md)
- * [ATR - Average True Range](lib/volatility/atr/Atr.md)
- * [ATRN - ATR Normalized](lib/volatility/atrn/Atrn.md)
- * [ATRP - ATR Percent](lib/volatility/atrp/Atrp.md)
- * [BBW - Bollinger Band Width](lib/volatility/bbw/Bbw.md)
- * [BBWN - Bollinger Band Width Normalized](lib/volatility/bbwn/Bbwn.md)
- * [BBWP - Bollinger Band Width Percentile](lib/volatility/bbwp/Bbwp.md)
- * [CCV - Close-to-Close Volatility](lib/volatility/ccv/Ccv.md)
- * [CV - Conditional Volatility](lib/volatility/cv/Cv.md)
- * [CVI - Chaikin's Volatility](lib/volatility/cvi/Cvi.md)
- * [EWMA - Exponential Weighted MA Volatility](lib/volatility/ewma/Ewma.md)
- * [GKV - Garman-Klass Volatility](lib/volatility/gkv/Gkv.md)
- * [HLV - High-Low Volatility](lib/volatility/hlv/Hlv.md)
- * [HV - Historical Volatility](lib/volatility/hv/Hv.md)
- * [JVOLTY - Jurik Volatility](lib/volatility/jvolty/Jvolty.md)
- * [JVOLTYN - Jurik Volatility Normalized](lib/volatility/jvoltyn/Jvoltyn.md)
- * [MASSI - Mass Index](lib/volatility/massi/Massi.md)
- * [NATR - Normalized ATR](lib/volatility/natr/Natr.md)
- * [PV - Parkinson Volatility](lib/volatility/pv/Pv.md)
- * [RSV - Rogers-Satchell Volatility](lib/volatility/rsv/Rsv.md)
- * [RV - Realized Volatility](lib/volatility/rv/Rv.md)
- * [RVI - Relative Volatility Index](lib/volatility/rvi/Rvi.md)
- * [TR - True Range](lib/volatility/tr/Tr.md)
- * [UI - Ulcer Index](lib/volatility/ui/Ui.md)
- * [VOV - Volatility of Volatility](lib/volatility/vov/Vov.md)
- * [VR - Volatility Ratio](lib/volatility/vr/Vr.md)
- * [YZV - Yang-Zhang Volatility](lib/volatility/yzv/Yzv.md)
+ * [Overview](/lib/volatility/_index.md)
+ * [ADR - Average Daily Range](/lib/volatility/adr/Adr.md)
+ * [ATR - Average True Range](/lib/volatility/atr/Atr.md)
+ * [ATRN - ATR Normalized](/lib/volatility/atrn/Atrn.md)
+ * [ATRP - ATR Percent](/lib/volatility/atrp/Atrp.md)
+ * [BBW - Bollinger Band Width](/lib/volatility/bbw/Bbw.md)
+ * [BBWN - Bollinger Band Width Normalized](/lib/volatility/bbwn/Bbwn.md)
+ * [BBWP - Bollinger Band Width Percentile](/lib/volatility/bbwp/Bbwp.md)
+ * [CCV - Close-to-Close Volatility](/lib/volatility/ccv/Ccv.md)
+ * [CV - Conditional Volatility](/lib/volatility/cv/Cv.md)
+ * [CVI - Chaikin's Volatility](/lib/volatility/cvi/Cvi.md)
+ * [EWMA - Exponential Weighted MA Volatility](/lib/volatility/ewma/Ewma.md)
+ * [GKV - Garman-Klass Volatility](/lib/volatility/gkv/Gkv.md)
+ * [HLV - High-Low Volatility](/lib/volatility/hlv/Hlv.md)
+ * [HV - Historical Volatility](/lib/volatility/hv/Hv.md)
+ * [JVOLTY - Jurik Volatility](/lib/volatility/jvolty/Jvolty.md)
+ * [JVOLTYN - Jurik Volatility Normalized](/lib/volatility/jvoltyn/Jvoltyn.md)
+ * [MASSI - Mass Index](/lib/volatility/massi/Massi.md)
+ * [NATR - Normalized ATR](/lib/volatility/natr/Natr.md)
+ * [RSV - Rogers-Satchell Volatility](/lib/volatility/rsv/Rsv.md)
+ * [RV - Realized Volatility](/lib/volatility/rv/Rv.md)
+ * [RVI - Relative Volatility Index](/lib/volatility/rvi/Rvi.md)
+ * [TR - True Range](/lib/volatility/tr/Tr.md)
+ * [UI - Ulcer Index](/lib/volatility/ui/Ui.md)
+ * [VOV - Volatility of Volatility](/lib/volatility/vov/Vov.md)
+ * [VR - Volatility Ratio](/lib/volatility/vr/Vr.md)
+ * [YZV - Yang-Zhang Volatility](/lib/volatility/yzv/Yzv.md)
* **Volume**
- * [Overview](lib/volume/_index.md)
- * [ADL - Accumulation/Distribution Line](lib/volume/adl/Adl.md)
- * [ADOSC - Chaikin A/D Oscillator](lib/volume/adosc/Adosc.md)
- * [AOBV - Archer On-Balance Volume](lib/volume/aobv/Aobv.md)
- * [CMF - Chaikin Money Flow](lib/volume/cmf/Cmf.md)
- * [EFI - Elder's Force Index](lib/volume/efi/Efi.md)
- * [EOM - Ease of Movement](lib/volume/eom/Eom.md)
- * [III - Intraday Intensity Index](lib/volume/iii/Iii.md)
- * [KVO - Klinger Volume Oscillator](lib/volume/kvo/Kvo.md)
- * [MFI - Money Flow Index](lib/volume/mfi/Mfi.md)
- * [NVI - Negative Volume Index](lib/volume/nvi/Nvi.md)
- * [OBV - On Balance Volume](lib/volume/obv/Obv.md)
- * [PVD - Price Volume Divergence](lib/volume/pvd/Pvd.md)
- * [PVI - Positive Volume Index](lib/volume/pvi/Pvi.md)
- * [PVO - Percentage Volume Oscillator](lib/volume/pvo/Pvo.md)
- * [PVR - Price Volume Rank](lib/volume/pvr/Pvr.md)
- * [PVT - Price Volume Trend](lib/volume/pvt/Pvt.md)
- * [TVI - Trade Volume Index](lib/volume/tvi/Tvi.md)
- * [TWAP - Time Weighted Average Price](lib/volume/twap/Twap.md)
- * [VA - Volume Accumulation](lib/volume/va/Va.md)
- * [VF - Volume Force](lib/volume/vf/Vf.md)
- * [VO - Volume Oscillator](lib/volume/vo/Vo.md)
- * [VROC - Volume Rate of Change](lib/volume/vroc/Vroc.md)
- * [VWAD - Volume Weighted A/D](lib/volume/vwad/Vwad.md)
- * [VWAP - Volume Weighted Average Price](lib/volume/vwap/Vwap.md)
- * [VWMA - Volume Weighted MA](lib/volume/vwma/Vwma.md)
- * [WAD - Williams A/D](lib/volume/wad/Wad.md)
+ * [Overview](/lib/volume/_index.md)
+ * [ADL - Accumulation/Distribution Line](/lib/volume/adl/Adl.md)
+ * [ADOSC - Chaikin A/D Oscillator](/lib/volume/adosc/Adosc.md)
+ * [AOBV - Archer On-Balance Volume](/lib/volume/aobv/Aobv.md)
+ * [CMF - Chaikin Money Flow](/lib/volume/cmf/Cmf.md)
+ * [EFI - Elder's Force Index](/lib/volume/efi/Efi.md)
+ * [EOM - Ease of Movement](/lib/volume/eom/Eom.md)
+ * [III - Intraday Intensity Index](/lib/volume/iii/Iii.md)
+ * [KVO - Klinger Volume Oscillator](/lib/volume/kvo/Kvo.md)
+ * [MFI - Money Flow Index](/lib/volume/mfi/Mfi.md)
+ * [NVI - Negative Volume Index](/lib/volume/nvi/Nvi.md)
+ * [OBV - On Balance Volume](/lib/volume/obv/Obv.md)
+ * [PVD - Price Volume Divergence](/lib/volume/pvd/Pvd.md)
+ * [PVI - Positive Volume Index](/lib/volume/pvi/Pvi.md)
+ * [PVO - Percentage Volume Oscillator](/lib/volume/pvo/Pvo.md)
+ * [PVR - Price Volume Rank](/lib/volume/pvr/Pvr.md)
+ * [PVT - Price Volume Trend](/lib/volume/pvt/Pvt.md)
+ * [TVI - Trade Volume Index](/lib/volume/tvi/Tvi.md)
+ * [TWAP - Time Weighted Average Price](/lib/volume/twap/Twap.md)
+ * [VA - Volume Accumulation](/lib/volume/va/Va.md)
+ * [VF - Volume Force](/lib/volume/vf/Vf.md)
+ * [VO - Volume Oscillator](/lib/volume/vo/Vo.md)
+ * [VROC - Volume Rate of Change](/lib/volume/vroc/Vroc.md)
+ * [VWAD - Volume Weighted A/D](/lib/volume/vwad/Vwad.md)
+ * [VWAP - Volume Weighted Average Price](/lib/volume/vwap/Vwap.md)
+ * [VWMA - Volume Weighted MA](/lib/volume/vwma/Vwma.md)
+ * [WAD - Williams A/D](/lib/volume/wad/Wad.md)
* **Channels**
- * [Overview](lib/channels/_index.md)
- * [ABBER - Aberration Bands](lib/channels/abber/abber.md)
- * [ACCBANDS - Acceleration Bands](lib/channels/accbands/accbands.md)
- * [APCHANNEL - Andrews' Pitchfork](lib/channels/apchannel/Apchannel.md)
- * [APZ - Adaptive Price Zone](lib/channels/apz/apz.md)
- * [ATRBANDS - ATR Bands](lib/channels/atrbands/Atrbands.md)
- * [BBANDS - Bollinger Bands](lib/channels/bbands/Bbands.md)
- * [DCHANNEL - Donchian Channels](lib/channels/dchannel/Dchannel.md)
- * [DECAYCHANNEL - Decay Min-Max Channel](lib/channels/decaychannel/decaychannel.md)
- * [FCB - Fractal Chaos Bands](lib/channels/fcb/Fcb.md)
- * [JBANDS - Jurik Volatility Bands](lib/channels/jbands/Jbands.md)
- * [KCHANNEL - Keltner Channel](lib/channels/kchannel/Kchannel.md)
- * [MAENV - Moving Average Envelope](lib/channels/maenv/Maenv.md)
- * [MMCHANNEL - Min-Max Channel](lib/channels/mmchannel/Mmchannel.md)
- * [PCHANNEL - Price Channel](lib/channels/pchannel/Pchannel.md)
- * [REGCHANNEL - Regression Channels](lib/channels/regchannel/Regchannel.md)
- * [SDCHANNEL - Standard Deviation Channel](lib/channels/sdchannel/Sdchannel.md)
- * [STARCHANNEL - Stoller Average Range Channel](lib/channels/starchannel/Starchannel.md)
- * [STBANDS - Super Trend Bands](lib/channels/stbands/Stbands.md)
- * [UBANDS - Ultimate Bands](lib/channels/ubands/Ubands.md)
- * [UCHANNEL - Ultimate Channel](lib/channels/uchannel/Uchannel.md)
- * [VWAPBANDS - VWAP Bands](lib/channels/vwapbands/Vwapbands.md)
- * [VWAPSD - VWAP with Standard Deviation Bands](lib/channels/vwapsd/Vwapsd.md)
+ * [Overview](/lib/channels/_index.md)
+ * [ABBER - Aberration Bands](/lib/channels/abber/abber.md)
+ * [ACCBANDS - Acceleration Bands](/lib/channels/accbands/accbands.md)
+ * [APCHANNEL - Andrews' Pitchfork](/lib/channels/apchannel/Apchannel.md)
+ * [APZ - Adaptive Price Zone](/lib/channels/apz/apz.md)
+ * [ATRBANDS - ATR Bands](/lib/channels/atrbands/Atrbands.md)
+ * [BBANDS - Bollinger Bands](/lib/channels/bbands/Bbands.md)
+ * [DCHANNEL - Donchian Channels](/lib/channels/dchannel/Dchannel.md)
+ * [DECAYCHANNEL - Decay Min-Max Channel](/lib/channels/decaychannel/decaychannel.md)
+ * [FCB - Fractal Chaos Bands](/lib/channels/fcb/Fcb.md)
+ * [JBANDS - Jurik Volatility Bands](/lib/channels/jbands/Jbands.md)
+ * [KCHANNEL - Keltner Channel](/lib/channels/kchannel/Kchannel.md)
+ * [MAENV - Moving Average Envelope](/lib/channels/maenv/Maenv.md)
+ * [MMCHANNEL - Min-Max Channel](/lib/channels/mmchannel/Mmchannel.md)
+ * [PCHANNEL - Price Channel](/lib/channels/pchannel/Pchannel.md)
+ * [REGCHANNEL - Regression Channels](/lib/channels/regchannel/Regchannel.md)
+ * [SDCHANNEL - Standard Deviation Channel](/lib/channels/sdchannel/Sdchannel.md)
+ * [STARCHANNEL - Stoller Average Range Channel](/lib/channels/starchannel/Starchannel.md)
+ * [STBANDS - Super Trend Bands](/lib/channels/stbands/Stbands.md)
+ * [TTM_LRC - TTM Linear Regression Channel](/lib/channels/ttm_lrc/TtmLrc.md)
+ * [UBANDS - Ultimate Bands](/lib/channels/ubands/Ubands.md)
+ * [UCHANNEL - Ultimate Channel](/lib/channels/uchannel/Uchannel.md)
+ * [VWAPBANDS - VWAP Bands](/lib/channels/vwapbands/Vwapbands.md)
+ * [VWAPSD - VWAP with Standard Deviation Bands](/lib/channels/vwapsd/Vwapsd.md)
* **Statistics**
- * [Overview](lib/statistics/_index.md)
- * [BETA - Beta Coefficient](lib/statistics/beta/Beta.md)
- * [BIAS - Bias](lib/statistics/bias/Bias.md)
- * [CMA - Cumulative MA](lib/statistics/cma/Cma.md)
- * [COINTEGRATION - Cointegration](lib/statistics/cointegration/Cointegration.md)
- * [CORRELATION - Correlation](lib/statistics/correlation/Correlation.md)
- * [COVARIANCE - Covariance](lib/statistics/covariance/Covariance.md)
- * [ENTROPY - Shannon Entropy](lib/statistics/entropy/Entropy.md)
- * [GEOMEAN - Geometric Mean](lib/statistics/geomean/Geomean.md)
- * [GRANGER - Granger Causality](lib/statistics/granger/Granger.md)
- * [HARMEAN - Harmonic Mean](lib/statistics/harmean/Harmean.md)
- * [HURST - Hurst Exponent](lib/statistics/hurst/Hurst.md)
- * [IQR - Interquartile Range](lib/statistics/iqr/Iqr.md)
- * [JB - Jarque-Bera Test](lib/statistics/jb/Jb.md)
- * [KENDALL - Kendall Rank Correlation](lib/statistics/kendall/Kendall.md)
- * [KURTOSIS - Kurtosis](lib/statistics/kurtosis/Kurtosis.md)
- * [LINREG - Linear Regression Curve](lib/statistics/linreg/LinReg.md)
- * [MEDIAN - Rolling Median](lib/statistics/median/Median.md)
- * [MODE - Mode](lib/statistics/mode/Mode.md)
- * [PERCENTILE - Percentile](lib/statistics/percentile/Percentile.md)
- * [QUANTILE - Quantile](lib/statistics/quantile/Quantile.md)
- * [SKEW - Skewness](lib/statistics/skew/Skew.md)
- * [SPEARMAN - Spearman Rank Correlation](lib/statistics/spearman/Spearman.md)
- * [STDDEV - Standard Deviation](lib/statistics/stddev/StdDev.md)
- * [SUM - Rolling Sum](lib/statistics/sum/Sum.md)
- * [THEIL - Theil Index](lib/statistics/theil/Theil.md)
- * [VARIANCE - Population and Sample Variance](lib/statistics/variance/Variance.md)
- * [ZSCORE - Z-score](lib/statistics/zscore/Zscore.md)
- * [ZTEST - Z-Test](lib/statistics/ztest/Ztest.md)
+ * [Overview](/lib/statistics/_index.md)
+ * [ACF - Autocorrelation Function](/lib/statistics/acf/Acf.md)
+ * [BETA - Beta Coefficient](/lib/statistics/beta/Beta.md)
+ * [BIAS - Bias](/lib/statistics/bias/Bias.md)
+ * [CMA - Cumulative MA](/lib/statistics/cma/Cma.md)
+ * [COINTEGRATION - Cointegration](/lib/statistics/cointegration/Cointegration.md)
+ * [CORRELATION - Correlation](/lib/statistics/correlation/Correlation.md)
+ * [COVARIANCE - Covariance](/lib/statistics/covariance/Covariance.md)
+ * [ENTROPY - Shannon Entropy](/lib/statistics/entropy/Entropy.md)
+ * [GEOMEAN - Geometric Mean](/lib/statistics/geomean/Geomean.md)
+ * [GRANGER - Granger Causality](/lib/statistics/granger/Granger.md)
+ * [HARMEAN - Harmonic Mean](/lib/statistics/harmean/Harmean.md)
+ * [HURST - Hurst Exponent](/lib/statistics/hurst/Hurst.md)
+ * [IQR - Interquartile Range](/lib/statistics/iqr/Iqr.md)
+ * [JB - Jarque-Bera Test](/lib/statistics/jb/Jb.md)
+ * [KENDALL - Kendall Rank Correlation](/lib/statistics/kendall/Kendall.md)
+ * [KURTOSIS - Kurtosis](/lib/statistics/kurtosis/Kurtosis.md)
+ * [LINREG - Linear Regression Curve](/lib/statistics/linreg/LinReg.md)
+ * [MEDIAN - Rolling Median](/lib/statistics/median/Median.md)
+ * [PACF - Partial Autocorrelation Function](/lib/statistics/pacf/Pacf.md)
+ * [MODE - Mode](/lib/statistics/mode/Mode.md)
+ * [PERCENTILE - Percentile](/lib/statistics/percentile/Percentile.md)
+ * [QUANTILE - Quantile](/lib/statistics/quantile/Quantile.md)
+ * [SKEW - Skewness](/lib/statistics/skew/Skew.md)
+ * [SPEARMAN - Spearman Rank Correlation](/lib/statistics/spearman/Spearman.md)
+ * [STDDEV - Standard Deviation](/lib/statistics/stddev/StdDev.md)
+ * [SUM - Rolling Sum](/lib/statistics/sum/Sum.md)
+ * [THEIL - Theil Index](/lib/statistics/theil/Theil.md)
+ * [VARIANCE - Population and Sample Variance](/lib/statistics/variance/Variance.md)
+ * [ZSCORE - Z-score](/lib/statistics/zscore/Zscore.md)
+ * [ZTEST - Z-Test](/lib/statistics/ztest/Ztest.md)
* **Numerics**
- * [Overview](lib/numerics/_index.md)
+ * [Overview](/lib/numerics/_index.md)
+ * [ACCEL - Acceleration](/lib/numerics/accel/Accel.md)
+ * [CHANGE - Percentage Change](/lib/numerics/change/Change.md)
+ * [EXPTRANS - Exponential Transform](/lib/numerics/exptrans/Exptrans.md)
+ * [HIGHEST - Rolling Maximum](/lib/numerics/highest/Highest.md)
+ * [JERK - Jerk](/lib/numerics/jerk/Jerk.md)
+ * [LINEARTRANS - Linear Transform](/lib/numerics/lineartrans/Lineartrans.md)
+ * [LOGTRANS - Logarithmic Transform](/lib/numerics/logtrans/Logtrans.md)
+ * [LOWEST - Rolling Minimum](/lib/numerics/lowest/Lowest.md)
+ * [MIDPOINT - Midrange](/lib/numerics/midpoint/Midpoint.md)
+ * [NORMALIZE - Min-Max Normalization](/lib/numerics/normalize/Normalize.md)
+ * [RELU - Rectified Linear Unit](/lib/numerics/relu/Relu.md)
+ * [SIGMOID - Logistic Function](/lib/numerics/sigmoid/Sigmoid.md)
+ * [SLOPE - Rate of Change](/lib/numerics/slope/Slope.md)
+ * [SQRTTRANS - Square Root Transform](/lib/numerics/sqrttrans/Sqrttrans.md)
+ * [STANDARDIZE - Z-Score Normalization](/lib/numerics/standardize/Standardize.cs)
* **Errors**
- * [Overview](lib/errors/_index.md)
- * [HUBER - Huber Loss](lib/errors/huber/Huber.md)
- * [LOGCOSH - Log-Cosh Loss](lib/errors/logcosh/LogCosh.md)
- * [MAE - Mean Absolute Error](lib/errors/mae/Mae.md)
- * [MAAPE - Mean Arctangent Absolute Percentage Error](lib/errors/maape/Maape.md)
- * [MAPD - Mean Absolute Percentage Deviation](lib/errors/mapd/Mapd.md)
- * [MAPE - Mean Absolute Percentage Error](lib/errors/mape/Mape.md)
- * [MASE - Mean Absolute Scaled Error](lib/errors/mase/Mase.md)
- * [MDAE - Median Absolute Error](lib/errors/mdae/Mdae.md)
- * [MDAPE - Median Absolute Percentage Error](lib/errors/mdape/Mdape.md)
- * [ME - Mean Error](lib/errors/me/Me.md)
- * [MPE - Mean Percentage Error](lib/errors/mpe/Mpe.md)
- * [MRAE - Mean Relative Absolute Error](lib/errors/mrae/Mrae.md)
- * [MSE - Mean Squared Error](lib/errors/mse/Mse.md)
- * [MSLE - Mean Squared Logarithmic Error](lib/errors/msle/Msle.md)
- * [PSEUDOHUBER - Pseudo-Huber Loss](lib/errors/pseudohuber/PseudoHuber.md)
- * [QUANTILE - Quantile Loss](lib/errors/quantile/QuantileLoss.md)
- * [RAE - Relative Absolute Error](lib/errors/rae/Rae.md)
- * [RMSE - Root Mean Squared Error](lib/errors/rmse/Rmse.md)
- * [RMSLE - Root Mean Squared Logarithmic Error](lib/errors/rmsle/Rmsle.md)
- * [RSE - Relative Squared Error](lib/errors/rse/Rse.md)
- * [RSQUARED - Coefficient of Determination](lib/errors/rsquared/Rsquared.md)
- * [SMAPE - Symmetric Mean Absolute Percentage Error](lib/errors/smape/Smape.md)
- * [THEILU - Theil's U Statistic](lib/errors/theilu/TheilU.md)
- * [TUKEY - Tukey Biweight Loss](lib/errors/tukey/TukeyBiweight.md)
- * [WMAPE - Weighted Mean Absolute Percentage Error](lib/errors/wmape/Wmape.md)
+ * [Overview](/lib/errors/_index.md)
+ * [HUBER - Huber Loss](/lib/errors/huber/Huber.md)
+ * [LOGCOSH - Log-Cosh Loss](/lib/errors/logcosh/LogCosh.md)
+ * [MAE - Mean Absolute Error](/lib/errors/mae/Mae.md)
+ * [MAAPE - Mean Arctangent Absolute Percentage Error](/lib/errors/maape/Maape.md)
+ * [MAPD - Mean Absolute Percentage Deviation](/lib/errors/mapd/Mapd.md)
+ * [MAPE - Mean Absolute Percentage Error](/lib/errors/mape/Mape.md)
+ * [MASE - Mean Absolute Scaled Error](/lib/errors/mase/Mase.md)
+ * [MDAE - Median Absolute Error](/lib/errors/mdae/Mdae.md)
+ * [MDAPE - Median Absolute Percentage Error](/lib/errors/mdape/Mdape.md)
+ * [ME - Mean Error](/lib/errors/me/Me.md)
+ * [MPE - Mean Percentage Error](/lib/errors/mpe/Mpe.md)
+ * [MRAE - Mean Relative Absolute Error](/lib/errors/mrae/Mrae.md)
+ * [MSE - Mean Squared Error](/lib/errors/mse/Mse.md)
+ * [MSLE - Mean Squared Logarithmic Error](/lib/errors/msle/Msle.md)
+ * [PSEUDOHUBER - Pseudo-Huber Loss](/lib/errors/pseudohuber/PseudoHuber.md)
+ * [QUANTILE - Quantile Loss](/lib/errors/quantile/QuantileLoss.md)
+ * [RAE - Relative Absolute Error](/lib/errors/rae/Rae.md)
+ * [RMSE - Root Mean Squared Error](/lib/errors/rmse/Rmse.md)
+ * [RMSLE - Root Mean Squared Logarithmic Error](/lib/errors/rmsle/Rmsle.md)
+ * [RSE - Relative Squared Error](/lib/errors/rse/Rse.md)
+ * [RSQUARED - Coefficient of Determination](/lib/errors/rsquared/Rsquared.md)
+ * [SMAPE - Symmetric Mean Absolute Percentage Error](/lib/errors/smape/Smape.md)
+ * [THEILU - Theil's U Statistic](/lib/errors/theilu/TheilU.md)
+ * [TUKEY - Tukey Biweight Loss](/lib/errors/tukey/TukeyBiweight.md)
+ * [WMAPE - Weighted Mean Absolute Percentage Error](/lib/errors/wmape/Wmape.md)
+ * [WRMSE - Weighted RMSE](/lib/errors/wrmse/Wrmse.md)
* **Forecasts**
- * [Overview](lib/forecasts/_index.md)
- * [AFIRMA - Adaptive FIR MA](lib/forecasts/afirma/Afirma.md)
- * [MLP - Multilayer Perceptron](lib/forecasts/mlp/Mlp.md)
+ * [Overview](/lib/forecasts/_index.md)
+ * [AFIRMA - Adaptive FIR MA](/lib/forecasts/afirma/Afirma.md)
* **Cycles**
- * [Overview](lib/cycles/_index.md)
- * [CG - Center of Gravity](lib/cycles/cg/Cg.md)
- * [DSP - Detrended Synthetic Price](lib/cycles/dsp/Dsp.md)
- * [EACP - Ehlers Autocorrelation Periodogram](lib/cycles/eacp/Eacp.md)
- * [EBSW - Ehlers Even Better Sinewave](lib/cycles/ebsw/Ebsw.md)
- * [HOMOD - Homodyne Discriminator](lib/cycles/homod/Homod.md)
- * [HT_DCPERIOD - Hilbert Transform Dominant Cycle Period](lib/cycles/ht_dcperiod/HtDcperiod.md)
- * [HT_DCPHASE - Hilbert Transform Dominant Cycle Phase](lib/cycles/ht_dcphase/HtDcphase.md)
- * [HT_PHASOR - Hilbert Transform Phasor](lib/cycles/ht_phasor/HtPhasor.md)
- * [HT_SINE - Hilbert Transform SineWave](lib/cycles/ht_sine/HtSine.md)
- * [LUNAR - Lunar Phase](lib/cycles/lunar/Lunar.md)
- * [PHASOR - Phasor Analysis](lib/cycles/phasor/Phasor.md)
- * [SINE - Sine Wave](lib/cycles/sine/Sine.md)
- * [SOLAR - Solar Activity Cycle](lib/cycles/solar/Solar.md)
- * [SSFDSP - SSF-Based Detrended Synthetic Price](lib/cycles/ssfdsp/Ssfdsp.md)
- * [STC - Schaff Trend Cycle](lib/cycles/stc/Stc.md)
+ * [Overview](/lib/cycles/_index.md)
+ * [CG - Center of Gravity](/lib/cycles/cg/Cg.md)
+ * [DSP - Detrended Synthetic Price](/lib/cycles/dsp/Dsp.md)
+ * [EACP - Ehlers Autocorrelation Periodogram](/lib/cycles/eacp/Eacp.md)
+ * [EBSW - Ehlers Even Better Sinewave](/lib/cycles/ebsw/Ebsw.md)
+ * [HOMOD - Homodyne Discriminator](/lib/cycles/homod/Homod.md)
+ * [HT_DCPERIOD - Hilbert Transform Dominant Cycle Period](/lib/cycles/ht_dcperiod/HtDcperiod.md)
+ * [HT_DCPHASE - Hilbert Transform Dominant Cycle Phase](/lib/cycles/ht_dcphase/HtDcphase.md)
+ * [HT_PHASOR - Hilbert Transform Phasor](/lib/cycles/ht_phasor/HtPhasor.md)
+ * [HT_SINE - Hilbert Transform SineWave](/lib/cycles/ht_sine/HtSine.md)
+ * [LUNAR - Lunar Phase](/lib/cycles/lunar/Lunar.md)
+ * [SINE - Sine Wave](/lib/cycles/sine/Sine.md)
+ * [SOLAR - Solar Activity Cycle](/lib/cycles/solar/Solar.md)
+ * [SSFDSP - SSF-Based Detrended Synthetic Price](/lib/cycles/ssfdsp/Ssfdsp.md)
+ * [STC - Schaff Trend Cycle](/lib/cycles/stc/Stc.md)
* **Reversals**
- * [Overview](lib/reversals/_index.md)
- * [FRACTALS - Williams Fractals](lib/reversals/fractals/Fractals.md)
- * [PIVOT - Pivot Points](lib/reversals/pivot/Pivot.md)
- * [PIVOTCAM - Camarilla Pivot Points](lib/reversals/pivotcam/Pivotcam.md)
- * [PIVOTDEM - DeMark Pivot Points](lib/reversals/pivotdem/Pivotdem.md)
- * [PIVOTEXT - Extended Traditional Pivots](lib/reversals/pivotext/Pivotext.md)
- * [PIVOTFIB - Fibonacci Pivot Points](lib/reversals/pivotfib/Pivotfib.md)
- * [PIVOTWOOD - Woodie's Pivot Points](lib/reversals/pivotwood/Pivotwood.md)
- * [PSAR - Parabolic Stop And Reverse](lib/reversals/psar/Psar.md)
- * [SWINGS - Swing High/Low Detection](lib/reversals/swings/Swings.md)
+ * [Overview](/lib/reversals/_index.md)
+ * [FRACTALS - Williams Fractals](/lib/reversals/fractals/Fractals.md)
+ * [PIVOT - Pivot Points](/lib/reversals/pivot/Pivot.md)
+ * [PIVOTCAM - Camarilla Pivot Points](/lib/reversals/pivotcam/Pivotcam.md)
+ * [PIVOTDEM - DeMark Pivot Points](/lib/reversals/pivotdem/Pivotdem.md)
+ * [PIVOTEXT - Extended Traditional Pivots](/lib/reversals/pivotext/Pivotext.md)
+ * [PIVOTFIB - Fibonacci Pivot Points](/lib/reversals/pivotfib/Pivotfib.md)
+ * [PIVOTWOOD - Woodie's Pivot Points](/lib/reversals/pivotwood/Pivotwood.md)
+ * [PSAR - Parabolic Stop And Reverse](/lib/reversals/psar/Psar.md)
+ * [SWINGS - Swing High/Low Detection](/lib/reversals/swings/Swings.md)
+ * [TTM_SCALPER - TTM Scalper Alert](/lib/reversals/ttm_scalper/TtmScalper.md)
diff --git a/docs/indicators.md b/docs/indicators.md
index ae344770..3c942c56 100644
--- a/docs/indicators.md
+++ b/docs/indicators.md
@@ -132,7 +132,11 @@ Rate of change and velocity measurements. First derivatives of price.
| [**BOP**](../lib/momentum/bop/Bop.md) | Balance of Power | Close position in range |
| [**CFB**](../lib/momentum/cfb/Cfb.md) | Composite Fractal Behavior | Jurik fractal momentum |
| [**ROC**](../lib/momentum/roc/Roc.md) | Rate of Change | Absolute price change over N periods |
+| [**ROCP**](../lib/momentum/rocp/Rocp.md) | Rate of Change Percentage | Percentage price change over N periods |
+| [**ROCR**](../lib/momentum/rocr/Rocr.md) | Rate of Change Ratio | Price ratio over N periods |
+| [**PRS**](../lib/momentum/prs/Prs.md) | Price Relative Strength | Dual-input ratio comparison |
| [**RSX**](../lib/momentum/rsx/Rsx.md) | Jurik RSX | Smoothed RSI variant |
+| [**TSI**](../lib/momentum/tsi/Tsi.md) | True Strength Index | Double-smoothed momentum oscillator |
| [**VEL**](../lib/momentum/vel/Vel.md) | Jurik Velocity | Adaptive velocity |
### Volatility
diff --git a/index.html b/index.html
index 465255dd..4cff8eb3 100644
--- a/index.html
+++ b/index.html
@@ -174,10 +174,10 @@
name: 'QuanTAlib',
repo: 'https://github.com/mihakralj/QuanTAlib',
loadSidebar: true,
- subMaxLevel: 2,
+ subMaxLevel: 0,
auto2top: true,
homepage: 'README.md',
- relativePath: false,
+ relativePath: true,
}
diff --git a/lib/_index.md b/lib/_index.md
index 29058949..73404371 100644
--- a/lib/_index.md
+++ b/lib/_index.md
@@ -301,7 +301,11 @@
| [TRIX](oscillators/trix/Trix.md) | Triple Exponential Average | Oscillators |
| TSF | Time Series Forecast | Statistics |
| [TSI](momentum/tsi/Tsi.md) | True Strength Index | Momentum |
-| [TTM](dynamics/ttm/Ttm.md) | TTM Trend | Dynamics |
+| [TTM_LRC](channels/ttm_lrc/TtmLrc.md) | TTM Linear Regression Channel | Channels |
+| [TTM_SCALPER](reversals/ttm_scalper/TtmScalper.md) | TTM Scalper Alert | Reversals |
+| [TTM_SQUEEZE](dynamics/ttm_squeeze/TtmSqueeze.md) | TTM Squeeze | Dynamics |
+| [TTM_TREND](dynamics/ttm_trend/TtmTrend.md) | TTM Trend | Dynamics |
+| [TTM_WAVE](oscillators/ttm_wave/TtmWave.md) | TTM Wave | Oscillators |
| [TUKEY](errors/tukey/Tukey.md) | Tukey Biweight Loss | Errors |
| [TVI](volume/tvi/Tvi.md) | Trade Volume Index | Volume |
| [TWAP](volume/twap/Twap.md) | Time Weighted Average Price | Volume |
diff --git a/lib/channels/abber/Abber.cs b/lib/channels/abber/Abber.cs
index 0205d777..0117422e 100644
--- a/lib/channels/abber/Abber.cs
+++ b/lib/channels/abber/Abber.cs
@@ -30,13 +30,15 @@ namespace QuanTAlib;
/// Pine Script implementation: https://github.com/mihakralj/pinescript/blob/main/indicators/channels/abber.pine
///
[SkipLocalsInit]
-public sealed class Abber : ITValuePublisher
+public sealed class Abber : ITValuePublisher, IDisposable
{
private readonly int _period;
private readonly double _multiplier;
private readonly RingBuffer _sourceBuffer;
private readonly RingBuffer _deviationBuffer;
private readonly TValuePublishedHandler _handler;
+ private ITValuePublisher? _source;
+ private bool _disposed;
private const int ResyncInterval = 1000;
@@ -116,8 +118,9 @@ public sealed class Abber : ITValuePublisher
///
public Abber(TSeries source, int period, double multiplier = 2.0) : this(period, multiplier)
{
+ _source = source ?? throw new ArgumentNullException(nameof(source));
Prime(source);
- source.Pub += _handler;
+ _source.Pub += _handler;
}
///
@@ -125,7 +128,8 @@ public sealed class Abber : ITValuePublisher
///
public Abber(ITValuePublisher source, int period, double multiplier = 2.0) : this(period, multiplier)
{
- source.Pub += _handler;
+ _source = source ?? throw new ArgumentNullException(nameof(source));
+ _source.Pub += _handler;
}
private void HandleValue(object? sender, in TValueEventArgs e) => Update(e.Value, e.IsNew);
@@ -673,4 +677,21 @@ public sealed class Abber : ITValuePublisher
var results = abber.Update(source);
return (results, abber);
}
+
+ ///
+ /// Disposes the Abber instance, unsubscribing from the source publisher.
+ /// This method is idempotent.
+ ///
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ if (_source != null)
+ {
+ _source.Pub -= _handler;
+ _source = null;
+ }
+ _disposed = true;
+ }
+ }
}
diff --git a/lib/channels/apchannel/apchannel.Tests.cs b/lib/channels/apchannel/apchannel.Tests.cs
index 764b02a2..55a501de 100644
--- a/lib/channels/apchannel/apchannel.Tests.cs
+++ b/lib/channels/apchannel/apchannel.Tests.cs
@@ -347,22 +347,22 @@ public class ApchannelTests
// Alpha must be > 0 and <= 1
Assert.Throws(() =>
- Apchannel.Calculate(high, low, upperBand, lowerBand, 0.0));
+ Apchannel.Batch(high, low, upperBand, lowerBand, 0.0));
Assert.Throws(() =>
- Apchannel.Calculate(high, low, upperBand, lowerBand, 1.5));
+ Apchannel.Batch(high, low, upperBand, lowerBand, 1.5));
// Arrays must be same length
double[] wrongSizeLow = new double[3];
Assert.Throws(() =>
- Apchannel.Calculate(high, wrongSizeLow, upperBand, lowerBand, 0.2));
+ Apchannel.Batch(high, wrongSizeLow, upperBand, lowerBand, 0.2));
double[] wrongSizeUpper = new double[3];
Assert.Throws(() =>
- Apchannel.Calculate(high, low, wrongSizeUpper, lowerBand, 0.2));
+ Apchannel.Batch(high, low, wrongSizeUpper, lowerBand, 0.2));
double[] wrongSizeLower = new double[3];
Assert.Throws(() =>
- Apchannel.Calculate(high, low, upperBand, wrongSizeLower, 0.2));
+ Apchannel.Batch(high, low, upperBand, wrongSizeLower, 0.2));
}
[Fact]
@@ -377,7 +377,7 @@ public class ApchannelTests
double[] lowerBandSpan = new double[100];
// Calculate using span
- Apchannel.Calculate(high, low, upperBandSpan, lowerBandSpan, 0.2);
+ Apchannel.Batch(high, low, upperBandSpan, lowerBandSpan, 0.2);
// Calculate iteratively
var apc = new Apchannel(0.2);
@@ -407,7 +407,7 @@ public class ApchannelTests
double[] upperBand = new double[5];
double[] lowerBand = new double[5];
- Apchannel.Calculate(high, low, upperBand, lowerBand, 0.2);
+ Apchannel.Batch(high, low, upperBand, lowerBand, 0.2);
foreach (var val in upperBand)
{
@@ -437,7 +437,7 @@ public class ApchannelTests
}
// Warm up
- Apchannel.Calculate(high, low, upperBand, lowerBand, 0.2);
+ Apchannel.Batch(high, low, upperBand, lowerBand, 0.2);
// Verify method completes without OOM or stack overflow
Assert.True(double.IsFinite(upperBand[^1]));
diff --git a/lib/channels/apchannel/apchannel.Validation.Tests.cs b/lib/channels/apchannel/apchannel.Validation.Tests.cs
index 7ed971dc..60af7a5a 100644
--- a/lib/channels/apchannel/apchannel.Validation.Tests.cs
+++ b/lib/channels/apchannel/apchannel.Validation.Tests.cs
@@ -66,7 +66,7 @@ public sealed class ApchannelValidationTests : IDisposable
double[] spanUpper = new double[bars.Count];
double[] spanLower = new double[bars.Count];
- Apchannel.Calculate(high, low, spanUpper, spanLower, alpha);
+ Apchannel.Batch(high, low, spanUpper, spanLower, alpha);
// 3. Batch Mode (Calculate)
var (batchResults, _) = Apchannel.Calculate(bars, alpha);
@@ -156,7 +156,7 @@ public sealed class ApchannelValidationTests : IDisposable
double[] apchannelUpper = new double[high.Length];
double[] apchannelLower = new double[low.Length];
- Apchannel.Calculate(high, low, apchannelUpper, apchannelLower, alpha);
+ Apchannel.Batch(high, low, apchannelUpper, apchannelLower, alpha);
// Calculate using Skender EMA for comparison
var skenderQuotesForHigh = bars.Select(b => new Quote
@@ -333,7 +333,7 @@ public sealed class ApchannelValidationTests : IDisposable
double[] low = bars.Select(b => b.Low).ToArray();
double[] spanUpper = new double[size];
double[] spanLower = new double[size];
- Apchannel.Calculate(high, low, spanUpper, spanLower, alpha);
+ Apchannel.Batch(high, low, spanUpper, spanLower, alpha);
// Compare last values
Assert.Equal(streamingApc.UpperBand, spanUpper[^1], ValidationHelper.SkenderTolerance);
diff --git a/lib/channels/apchannel/apchannel.cs b/lib/channels/apchannel/apchannel.cs
index d70e302d..576e00cc 100644
--- a/lib/channels/apchannel/apchannel.cs
+++ b/lib/channels/apchannel/apchannel.cs
@@ -283,7 +283,7 @@ public sealed class Apchannel : AbstractBase
/// Calculates the Adaptive Price Channel using span-based batch processing.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Calculate(
+ public static void Batch(
ReadOnlySpan sourceHigh,
ReadOnlySpan sourceLow,
Span upperBand,
diff --git a/lib/channels/bbands/Bbands.Tests.cs b/lib/channels/bbands/Bbands.Tests.cs
index 002a346f..c91ac4d1 100644
--- a/lib/channels/bbands/Bbands.Tests.cs
+++ b/lib/channels/bbands/Bbands.Tests.cs
@@ -214,7 +214,7 @@ public class BbandsTests
TSeries source = bars.Close;
// Act
- TSeries result = Bbands.Calculate(source, period: 5, multiplier: 2.0);
+ TSeries result = Bbands.Batch(source, period: 5, multiplier: 2.0);
// Assert
Assert.Equal(source.Count, result.Count);
@@ -236,9 +236,9 @@ public class BbandsTests
double[] middleArray = new double[source.Count];
double[] upperArray = new double[source.Count];
double[] lowerArray = new double[source.Count];
- Bbands.Calculate(sourceArray.AsSpan(), middleArray, upperArray, lowerArray, period, multiplier);
+ Bbands.Batch(sourceArray.AsSpan(), middleArray, upperArray, lowerArray, period, multiplier);
- TSeries seriesResult = Bbands.Calculate(source, period, multiplier);
+ TSeries seriesResult = Bbands.Batch(source, period, multiplier);
// Assert - Compare last 10 values
for (int i = source.Count - 10; i < source.Count; i++)
@@ -258,7 +258,7 @@ public class BbandsTests
// Act & Assert
ArgumentException exception = Assert.Throws(
- () => Bbands.Calculate(sourceArr.AsSpan(), middleArr.AsSpan(), upperArr.AsSpan(), lowerArr.AsSpan()));
+ () => Bbands.Batch(sourceArr.AsSpan(), middleArr.AsSpan(), upperArr.AsSpan(), lowerArr.AsSpan()));
Assert.Equal("source", exception.ParamName);
}
@@ -305,14 +305,14 @@ public class BbandsTests
}
// Batch
- TSeries batchResult = Bbands.Calculate(source, period, multiplier);
+ TSeries batchResult = Bbands.Batch(source, period, multiplier);
// Span - Copy arrays before using to avoid ref local lambda issue
double[] sourceArray = source.Values.ToArray();
double[] middleArray = new double[source.Count];
double[] upperArray = new double[source.Count];
double[] lowerArray = new double[source.Count];
- Bbands.Calculate(sourceArray.AsSpan(), middleArray, upperArray, lowerArray, period, multiplier);
+ Bbands.Batch(sourceArray.AsSpan(), middleArray, upperArray, lowerArray, period, multiplier);
// Assert - Compare last 50 values (streaming only has last value)
Assert.Equal(batchResult[^1].Value, streamingBbands.Middle.Value, precision: 8);
diff --git a/lib/channels/bbands/Bbands.Validation.Tests.cs b/lib/channels/bbands/Bbands.Validation.Tests.cs
index 40b21a33..e35fc625 100644
--- a/lib/channels/bbands/Bbands.Validation.Tests.cs
+++ b/lib/channels/bbands/Bbands.Validation.Tests.cs
@@ -100,7 +100,7 @@ public sealed class BbandsValidationTests : IDisposable
double[] qMiddle = new double[sourceData.Length];
double[] qUpper = new double[sourceData.Length];
double[] qLower = new double[sourceData.Length];
- Bbands.Calculate(sourceData.AsSpan(), qMiddle.AsSpan(), qUpper.AsSpan(), qLower.AsSpan(), period, multiplier);
+ Bbands.Batch(sourceData.AsSpan(), qMiddle.AsSpan(), qUpper.AsSpan(), qLower.AsSpan(), period, multiplier);
// Calculate Skender Bollinger Bands
var sResult = _testData.SkenderQuotes.GetBollingerBands(period, multiplier).ToList();
@@ -215,7 +215,7 @@ public sealed class BbandsValidationTests : IDisposable
double[] qMiddle = new double[sourceData.Length];
double[] qUpper = new double[sourceData.Length];
double[] qLower = new double[sourceData.Length];
- Bbands.Calculate(sourceData.AsSpan(), qMiddle.AsSpan(), qUpper.AsSpan(), qLower.AsSpan(), period, multiplier);
+ Bbands.Batch(sourceData.AsSpan(), qMiddle.AsSpan(), qUpper.AsSpan(), qLower.AsSpan(), period, multiplier);
// Calculate TA-Lib Bollinger Bands
var retCode = Functions.Bbands(
@@ -321,7 +321,7 @@ public sealed class BbandsValidationTests : IDisposable
double[] qMiddle = new double[sourceData.Length];
double[] qUpper = new double[sourceData.Length];
double[] qLower = new double[sourceData.Length];
- Bbands.Calculate(sourceData.AsSpan(), qMiddle.AsSpan(), qUpper.AsSpan(), qLower.AsSpan(), period, multiplier);
+ Bbands.Batch(sourceData.AsSpan(), qMiddle.AsSpan(), qUpper.AsSpan(), qLower.AsSpan(), period, multiplier);
// Calculate Tulip Bollinger Bands
var bbandsIndicator = Tulip.Indicators.bbands;
diff --git a/lib/channels/bbands/Bbands.cs b/lib/channels/bbands/Bbands.cs
index 235d8eba..9dcd434b 100644
--- a/lib/channels/bbands/Bbands.cs
+++ b/lib/channels/bbands/Bbands.cs
@@ -181,7 +181,7 @@ public sealed class Bbands : AbstractBase
Span upperSpan = upperRented.AsSpan(0, len);
Span lowerSpan = lowerRented.AsSpan(0, len);
- Calculate(sourceSpan, middleSpan, upperSpan, lowerSpan, _period, _multiplier);
+ Batch(sourceSpan, middleSpan, upperSpan, lowerSpan, _period, _multiplier);
for (int i = 0; i < len; i++)
{
@@ -230,7 +230,7 @@ public sealed class Bbands : AbstractBase
///
/// Calculates Bollinger Bands for the entire series and returns the middle band series.
///
- public static TSeries Calculate(TSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
+ public static TSeries Batch(TSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
{
Bbands bbands = new(period, multiplier);
return bbands.Update(source);
@@ -239,7 +239,7 @@ public sealed class Bbands : AbstractBase
///
/// Calculates Bollinger Bands across all input values using SIMD-optimized operations where possible.
///
- public static void Calculate(
+ public static void Batch(
ReadOnlySpan source,
Span middle,
Span upper,
@@ -360,4 +360,12 @@ public sealed class Bbands : AbstractBase
}
}
}
+
+ public static (TSeries Results, Bbands Indicator) Calculate(TSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
+ {
+ var indicator = new Bbands(period, multiplier);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
}
diff --git a/lib/channels/dchannel/Dchannel.cs b/lib/channels/dchannel/Dchannel.cs
index 8984757c..64631797 100644
--- a/lib/channels/dchannel/Dchannel.cs
+++ b/lib/channels/dchannel/Dchannel.cs
@@ -269,8 +269,8 @@ public sealed class Dchannel : ITValuePublisher
try
{
- QuanTAlib.Highest.Calculate(high, top.AsSpan(0, len), period);
- QuanTAlib.Lowest.Calculate(low, bot.AsSpan(0, len), period);
+ QuanTAlib.Highest.Batch(high, top.AsSpan(0, len), period);
+ QuanTAlib.Lowest.Batch(low, bot.AsSpan(0, len), period);
for (int i = 0; i < len; i++)
{
diff --git a/lib/channels/decaychannel/Decaychannel.cs b/lib/channels/decaychannel/Decaychannel.cs
index dd4607d3..16528f31 100644
--- a/lib/channels/decaychannel/Decaychannel.cs
+++ b/lib/channels/decaychannel/Decaychannel.cs
@@ -428,8 +428,8 @@ public sealed class Decaychannel : ITValuePublisher
try
{
- QuanTAlib.Highest.Calculate(high, rawMaxArr.AsSpan(0, len), period);
- QuanTAlib.Lowest.Calculate(low, rawMinArr.AsSpan(0, len), period);
+ QuanTAlib.Highest.Batch(high, rawMaxArr.AsSpan(0, len), period);
+ QuanTAlib.Lowest.Batch(low, rawMinArr.AsSpan(0, len), period);
double currentMax = double.NaN;
double currentMin = double.NaN;
diff --git a/lib/channels/jbands/Jbands.Tests.cs b/lib/channels/jbands/Jbands.Tests.cs
index 4ad4b017..941462cb 100644
--- a/lib/channels/jbands/Jbands.Tests.cs
+++ b/lib/channels/jbands/Jbands.Tests.cs
@@ -202,11 +202,11 @@ public class JbandsTests
double[] shortOut = new double[2];
Assert.Throws(() =>
- Jbands.Calculate(source.AsSpan(), shortOut.AsSpan(), upper.AsSpan(), lower.AsSpan(), 14));
+ Jbands.Batch(source.AsSpan(), shortOut.AsSpan(), upper.AsSpan(), lower.AsSpan(), 14));
Assert.Throws(() =>
- Jbands.Calculate(source.AsSpan(), middle.AsSpan(), shortOut.AsSpan(), lower.AsSpan(), 14));
+ Jbands.Batch(source.AsSpan(), middle.AsSpan(), shortOut.AsSpan(), lower.AsSpan(), 14));
Assert.Throws(() =>
- Jbands.Calculate(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), shortOut.AsSpan(), 14));
+ Jbands.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), shortOut.AsSpan(), 14));
}
[Fact]
@@ -223,7 +223,7 @@ public class JbandsTests
double[] upper = new double[100];
double[] lower = new double[100];
- Jbands.Calculate(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 14);
+ Jbands.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 14);
var jStream = new Jbands(14);
for (int i = 0; i < source.Length; i++)
diff --git a/lib/channels/jbands/Jbands.Validation.Tests.cs b/lib/channels/jbands/Jbands.Validation.Tests.cs
index 595e677e..8f9f68d3 100644
--- a/lib/channels/jbands/Jbands.Validation.Tests.cs
+++ b/lib/channels/jbands/Jbands.Validation.Tests.cs
@@ -103,7 +103,7 @@ public class JbandsValidationTests
double[] upper = new double[200];
double[] lower = new double[200];
- Jbands.Calculate(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 14);
+ Jbands.Batch(source.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 14);
var jStream = new Jbands(14);
for (int i = 0; i < source.Length; i++)
@@ -144,7 +144,7 @@ public class JbandsValidationTests
double[] middleSpan = new double[150];
double[] upperSpan = new double[150];
double[] lowerSpan = new double[150];
- Jbands.Calculate(rawValues.AsSpan(), middleSpan.AsSpan(), upperSpan.AsSpan(), lowerSpan.AsSpan(), 14, 25, 0.45);
+ Jbands.Batch(rawValues.AsSpan(), middleSpan.AsSpan(), upperSpan.AsSpan(), lowerSpan.AsSpan(), 14, 25, 0.45);
// Mode 4: Event-based
var jEvent = new Jbands(14, 25, 0.45);
@@ -240,7 +240,7 @@ public class JbandsValidationTests
double[] middle = new double[50];
double[] upper = new double[50];
double[] lower = new double[50];
- Jbands.Calculate(rawValues.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 14);
+ Jbands.Batch(rawValues.AsSpan(), middle.AsSpan(), upper.AsSpan(), lower.AsSpan(), 14);
// After warmup, values should be stable and match
for (int i = warmupPeriod; i < rawValues.Length; i++)
diff --git a/lib/channels/jbands/Jbands.cs b/lib/channels/jbands/Jbands.cs
index d2e5c280..f0e2d485 100644
--- a/lib/channels/jbands/Jbands.cs
+++ b/lib/channels/jbands/Jbands.cs
@@ -386,7 +386,7 @@ public sealed class Jbands : ITValuePublisher, IDisposable
return jbands.Update(source);
}
- public static void Calculate(
+ public static void Batch(
ReadOnlySpan source,
Span middle,
Span upper,
diff --git a/lib/channels/mmchannel/Mmchannel.cs b/lib/channels/mmchannel/Mmchannel.cs
index 1814422d..c7de7538 100644
--- a/lib/channels/mmchannel/Mmchannel.cs
+++ b/lib/channels/mmchannel/Mmchannel.cs
@@ -252,8 +252,8 @@ public sealed class Mmchannel : ITValuePublisher
return;
}
- Highest.Calculate(high, upper, period);
- Lowest.Calculate(low, lower, period);
+ Highest.Batch(high, upper, period);
+ Lowest.Batch(low, lower, period);
}
public static (TSeries Upper, TSeries Lower) Batch(TBarSeries source, int period)
diff --git a/lib/channels/pchannel/Pchannel.cs b/lib/channels/pchannel/Pchannel.cs
index 92bc7ea2..9911679b 100644
--- a/lib/channels/pchannel/Pchannel.cs
+++ b/lib/channels/pchannel/Pchannel.cs
@@ -376,8 +376,8 @@ public sealed class Pchannel : ITValuePublisher
try
{
- Highest.Calculate(high, top.AsSpan(0, len), period);
- Lowest.Calculate(low, bot.AsSpan(0, len), period);
+ Highest.Batch(high, top.AsSpan(0, len), period);
+ Lowest.Batch(low, bot.AsSpan(0, len), period);
for (int i = 0; i < len; i++)
{
diff --git a/lib/channels/stbands/Stbands.Tests.cs b/lib/channels/stbands/Stbands.Tests.cs
index c832b37a..e725727b 100644
--- a/lib/channels/stbands/Stbands.Tests.cs
+++ b/lib/channels/stbands/Stbands.Tests.cs
@@ -279,7 +279,7 @@ public class StbandsTests
TBarSeries bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
// Act
- TSeries result = Stbands.Calculate(bars, period: 5, multiplier: 2.0);
+ TSeries result = Stbands.Batch(bars, period: 5, multiplier: 2.0);
// Assert
Assert.Equal(bars.Count, result.Count);
@@ -302,7 +302,7 @@ public class StbandsTests
double[] trend = new double[bars.Count];
// Act
- Stbands.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(), upper.AsSpan(), lower.AsSpan(), trend.AsSpan(), period, multiplier);
+ Stbands.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(), upper.AsSpan(), lower.AsSpan(), trend.AsSpan(), period, multiplier);
// Assert
for (int i = 0; i < bars.Count; i++)
@@ -327,7 +327,7 @@ public class StbandsTests
// Act & Assert
ArgumentException exception = Assert.Throws(
- () => Stbands.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(), upper.AsSpan(), lower.AsSpan(), trend.AsSpan()));
+ () => Stbands.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(), upper.AsSpan(), lower.AsSpan(), trend.AsSpan()));
Assert.Equal("high", exception.ParamName);
}
@@ -348,7 +348,7 @@ public class StbandsTests
}
// Batch
- TSeries batchResult = Stbands.Calculate(bars, period, multiplier);
+ TSeries batchResult = Stbands.Batch(bars, period, multiplier);
// Assert - Last values should match
Assert.Equal(batchResult[^1].Value, streamingStbands.Last.Value, precision: 8);
@@ -377,7 +377,7 @@ public class StbandsTests
double[] upper = new double[bars.Count];
double[] lower = new double[bars.Count];
double[] trend = new double[bars.Count];
- Stbands.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(), upper.AsSpan(), lower.AsSpan(), trend.AsSpan(), period, multiplier);
+ Stbands.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(), upper.AsSpan(), lower.AsSpan(), trend.AsSpan(), period, multiplier);
// Assert - Last values should match
Assert.Equal(upper[^1], streamingStbands.Upper.Value, precision: 8);
diff --git a/lib/channels/stbands/Stbands.Validation.Tests.cs b/lib/channels/stbands/Stbands.Validation.Tests.cs
index 0e213371..e796525d 100644
--- a/lib/channels/stbands/Stbands.Validation.Tests.cs
+++ b/lib/channels/stbands/Stbands.Validation.Tests.cs
@@ -69,7 +69,7 @@ public sealed class StbandsValidationTests : IDisposable
}
// Batch mode
- var batchResult = Stbands.Calculate(bars, period, multiplier);
+ var batchResult = Stbands.Batch(bars, period, multiplier);
// Compare last 100 values
int compareCount = Math.Min(100, bars.Count - period);
@@ -118,7 +118,7 @@ public sealed class StbandsValidationTests : IDisposable
double[] spanLower = new double[bars.Count];
double[] spanTrend = new double[bars.Count];
- Stbands.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(),
+ Stbands.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(),
spanUpper.AsSpan(), spanLower.AsSpan(), spanTrend.AsSpan(), period, multiplier);
// Compare last 100 values
@@ -155,7 +155,7 @@ public sealed class StbandsValidationTests : IDisposable
double[] lower = new double[bars.Count];
double[] trend = new double[bars.Count];
- Stbands.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(),
+ Stbands.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(),
upper.AsSpan(), lower.AsSpan(), trend.AsSpan(), period, multiplier);
// Verify Upper >= Lower for all points
@@ -192,7 +192,7 @@ public sealed class StbandsValidationTests : IDisposable
double[] lower = new double[bars.Count];
double[] trend = new double[bars.Count];
- Stbands.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(),
+ Stbands.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(),
upper.AsSpan(), lower.AsSpan(), trend.AsSpan(), period, multiplier);
int trendChanges = 0;
diff --git a/lib/channels/stbands/Stbands.cs b/lib/channels/stbands/Stbands.cs
index 385582f1..24da1669 100644
--- a/lib/channels/stbands/Stbands.cs
+++ b/lib/channels/stbands/Stbands.cs
@@ -286,7 +286,7 @@ public sealed class Stbands : AbstractBase
///
/// Calculates Super Trend Bands for the entire bar series.
///
- public static TSeries Calculate(TBarSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
+ public static TSeries Batch(TBarSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
{
Stbands stbands = new(period, multiplier);
return stbands.Update(source);
@@ -295,7 +295,7 @@ public sealed class Stbands : AbstractBase
///
/// Calculates Super Trend Bands across OHLC data using spans.
///
- public static void Calculate(
+ public static void Batch(
ReadOnlySpan high,
ReadOnlySpan low,
ReadOnlySpan close,
@@ -399,4 +399,12 @@ public sealed class Stbands : AbstractBase
prevClose = c;
}
}
+
+ public static (TSeries Results, Stbands Indicator) Calculate(TBarSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
+ {
+ var indicator = new Stbands(period, multiplier);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
}
diff --git a/lib/channels/ubands/Ubands.Tests.cs b/lib/channels/ubands/Ubands.Tests.cs
index b3f43631..cfefebbf 100644
--- a/lib/channels/ubands/Ubands.Tests.cs
+++ b/lib/channels/ubands/Ubands.Tests.cs
@@ -285,7 +285,7 @@ public class UbandsTests
TSeries series = bars.Close;
// 1. Batch Mode
- var batchResult = Ubands.Calculate(series, period, multiplier);
+ var batchResult = Ubands.Batch(series, period, multiplier);
double batchLast = batchResult.Last.Value;
// 2. Span Mode
@@ -293,7 +293,7 @@ public class UbandsTests
double[] spanUpper = new double[source.Length];
double[] spanMiddle = new double[source.Length];
double[] spanLower = new double[source.Length];
- Ubands.Calculate(source.AsSpan(), spanUpper.AsSpan(), spanMiddle.AsSpan(), spanLower.AsSpan(), period, multiplier);
+ Ubands.Batch(source.AsSpan(), spanUpper.AsSpan(), spanMiddle.AsSpan(), spanLower.AsSpan(), period, multiplier);
double spanLast = spanMiddle[^1];
// 3. Streaming Mode
@@ -319,13 +319,13 @@ public class UbandsTests
// Period must be >= 1
Assert.Throws(() =>
- Ubands.Calculate(source.AsSpan(), upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), 0));
+ Ubands.Batch(source.AsSpan(), upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), 0));
Assert.Throws(() =>
- Ubands.Calculate(source.AsSpan(), upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), -1));
+ Ubands.Batch(source.AsSpan(), upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), -1));
// All arrays must be same length
Assert.Throws(() =>
- Ubands.Calculate(source.AsSpan(), wrongSize.AsSpan(), middle.AsSpan(), lower.AsSpan(), 3));
+ Ubands.Batch(source.AsSpan(), wrongSize.AsSpan(), middle.AsSpan(), lower.AsSpan(), 3));
}
[Fact]
@@ -336,7 +336,7 @@ public class UbandsTests
double[] middle = new double[5];
double[] lower = new double[5];
- Ubands.Calculate(source.AsSpan(), upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), 3, 1.0);
+ Ubands.Batch(source.AsSpan(), upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), 3, 1.0);
foreach (var val in middle)
{
@@ -404,7 +404,7 @@ public class UbandsTests
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
TSeries series = bars.Close;
- var result = Ubands.Calculate(series, 10, 1.0);
+ var result = Ubands.Batch(series, 10, 1.0);
Assert.Equal(50, result.Count);
Assert.True(double.IsFinite(result.Last.Value));
diff --git a/lib/channels/ubands/Ubands.Validation.Tests.cs b/lib/channels/ubands/Ubands.Validation.Tests.cs
index 414734d8..7b2bd7a3 100644
--- a/lib/channels/ubands/Ubands.Validation.Tests.cs
+++ b/lib/channels/ubands/Ubands.Validation.Tests.cs
@@ -71,7 +71,7 @@ public sealed class UbandsValidationTests : IDisposable
}
// Batch mode
- var batchResult = Ubands.Calculate(series, period, multiplier);
+ var batchResult = Ubands.Batch(series, period, multiplier);
// Compare last 100 values
int compareCount = Math.Min(100, series.Count - period);
@@ -119,7 +119,7 @@ public sealed class UbandsValidationTests : IDisposable
double[] spanMiddle = new double[series.Count];
double[] spanLower = new double[series.Count];
- Ubands.Calculate(source.AsSpan(), spanUpper.AsSpan(), spanMiddle.AsSpan(),
+ Ubands.Batch(source.AsSpan(), spanUpper.AsSpan(), spanMiddle.AsSpan(),
spanLower.AsSpan(), period, multiplier);
// Compare last 100 values
diff --git a/lib/channels/ubands/Ubands.cs b/lib/channels/ubands/Ubands.cs
index 828c87a4..2ee43905 100644
--- a/lib/channels/ubands/Ubands.cs
+++ b/lib/channels/ubands/Ubands.cs
@@ -268,7 +268,7 @@ public sealed class Ubands : AbstractBase
///
/// Calculates Ultimate Bands for the entire series.
///
- public static TSeries Calculate(TSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
+ public static TSeries Batch(TSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
{
Ubands ubands = new(period, multiplier);
return ubands.Update(source);
@@ -277,7 +277,7 @@ public sealed class Ubands : AbstractBase
///
/// Calculates Ultimate Bands across data using spans.
///
- public static void Calculate(
+ public static void Batch(
ReadOnlySpan source,
Span upper,
Span middle,
@@ -397,4 +397,12 @@ public sealed class Ubands : AbstractBase
lower[i] = usf - bandOffset;
}
}
-}
\ No newline at end of file
+
+ public static (TSeries Results, Ubands Indicator) Calculate(TSeries source, int period = DefaultPeriod, double multiplier = DefaultMultiplier)
+ {
+ var indicator = new Ubands(period, multiplier);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
+}
diff --git a/lib/channels/uchannel/Uchannel.Tests.cs b/lib/channels/uchannel/Uchannel.Tests.cs
index fbc8cd5b..59a1ffab 100644
--- a/lib/channels/uchannel/Uchannel.Tests.cs
+++ b/lib/channels/uchannel/Uchannel.Tests.cs
@@ -306,7 +306,7 @@ public class UchannelTests
double[] spanUpper = new double[highArr.Length];
double[] spanMiddle = new double[highArr.Length];
double[] spanLower = new double[highArr.Length];
- Uchannel.Calculate(highArr.AsSpan(), lowArr.AsSpan(), closeArr.AsSpan(),
+ Uchannel.Batch(highArr.AsSpan(), lowArr.AsSpan(), closeArr.AsSpan(),
spanUpper.AsSpan(), spanMiddle.AsSpan(), spanLower.AsSpan(),
strPeriod, centerPeriod, multiplier);
double spanLast = spanMiddle[^1];
@@ -336,15 +336,15 @@ public class UchannelTests
// Period must be >= 1
Assert.Throws(() =>
- Uchannel.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(),
+ Uchannel.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(),
upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), 0));
Assert.Throws(() =>
- Uchannel.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(),
+ Uchannel.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(),
upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), -1));
// All arrays must be same length
Assert.Throws(() =>
- Uchannel.Calculate(high.AsSpan(), wrongSize.AsSpan(), close.AsSpan(),
+ Uchannel.Batch(high.AsSpan(), wrongSize.AsSpan(), close.AsSpan(),
upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), 3));
}
@@ -358,7 +358,7 @@ public class UchannelTests
double[] middle = new double[5];
double[] lower = new double[5];
- Uchannel.Calculate(high.AsSpan(), low.AsSpan(), close.AsSpan(),
+ Uchannel.Batch(high.AsSpan(), low.AsSpan(), close.AsSpan(),
upper.AsSpan(), middle.AsSpan(), lower.AsSpan(), 3, 3, 1.0);
foreach (var val in middle)
diff --git a/lib/channels/uchannel/Uchannel.Validation.Tests.cs b/lib/channels/uchannel/Uchannel.Validation.Tests.cs
index 42cf6e51..a3413bc1 100644
--- a/lib/channels/uchannel/Uchannel.Validation.Tests.cs
+++ b/lib/channels/uchannel/Uchannel.Validation.Tests.cs
@@ -70,7 +70,7 @@ public class UchannelValidationTests
double[] spanUpper = new double[highArr.Length];
double[] spanMiddle = new double[highArr.Length];
double[] spanLower = new double[highArr.Length];
- Uchannel.Calculate(highArr.AsSpan(), lowArr.AsSpan(), closeArr.AsSpan(),
+ Uchannel.Batch(highArr.AsSpan(), lowArr.AsSpan(), closeArr.AsSpan(),
spanUpper.AsSpan(), spanMiddle.AsSpan(), spanLower.AsSpan(),
DefaultStrPeriod, DefaultCenterPeriod, DefaultMultiplier);
diff --git a/lib/channels/uchannel/Uchannel.cs b/lib/channels/uchannel/Uchannel.cs
index d7226b01..b4232058 100644
--- a/lib/channels/uchannel/Uchannel.cs
+++ b/lib/channels/uchannel/Uchannel.cs
@@ -377,7 +377,7 @@ public sealed class Uchannel : AbstractBase
/// Period for STR smoothing.
/// Period for centerline smoothing.
/// Band multiplier.
- public static void Calculate(
+ public static void Batch(
ReadOnlySpan high,
ReadOnlySpan low,
ReadOnlySpan close,
diff --git a/lib/channels/vwapbands/Vwapbands.Tests.cs b/lib/channels/vwapbands/Vwapbands.Tests.cs
index f71adb1a..13dde4ad 100644
--- a/lib/channels/vwapbands/Vwapbands.Tests.cs
+++ b/lib/channels/vwapbands/Vwapbands.Tests.cs
@@ -446,12 +446,12 @@ public class VwapbandsTests
// Multiplier must be >= MinMultiplier
Assert.Throws(() =>
- Vwapbands.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapbands.Batch(price.AsSpan(), volume.AsSpan(),
upper1.AsSpan(), lower1.AsSpan(), upper2.AsSpan(), lower2.AsSpan(), vwap.AsSpan(), stdDev.AsSpan(), 0));
// All arrays must be same length
Assert.Throws(() =>
- Vwapbands.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapbands.Batch(price.AsSpan(), volume.AsSpan(),
wrongSize.AsSpan(), lower1.AsSpan(), upper2.AsSpan(), lower2.AsSpan(), vwap.AsSpan(), stdDev.AsSpan(), 1.0));
}
@@ -467,7 +467,7 @@ public class VwapbandsTests
double[] vwap = new double[5];
double[] stdDev = new double[5];
- Vwapbands.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapbands.Batch(price.AsSpan(), volume.AsSpan(),
upper1.AsSpan(), lower1.AsSpan(), upper2.AsSpan(), lower2.AsSpan(), vwap.AsSpan(), stdDev.AsSpan(), 1.0);
foreach (var val in vwap)
diff --git a/lib/channels/vwapbands/Vwapbands.Validation.Tests.cs b/lib/channels/vwapbands/Vwapbands.Validation.Tests.cs
index e071ab89..69e2cb3e 100644
--- a/lib/channels/vwapbands/Vwapbands.Validation.Tests.cs
+++ b/lib/channels/vwapbands/Vwapbands.Validation.Tests.cs
@@ -126,7 +126,7 @@ public sealed class VwapbandsValidationTests : IDisposable
double[] spanLower2 = new double[bars.Count];
double[] spanStdDev = new double[bars.Count];
- Vwapbands.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapbands.Batch(price.AsSpan(), volume.AsSpan(),
spanUpper1.AsSpan(), spanLower1.AsSpan(),
spanUpper2.AsSpan(), spanLower2.AsSpan(),
spanVwap.AsSpan(), spanStdDev.AsSpan(), multiplier);
diff --git a/lib/channels/vwapbands/Vwapbands.cs b/lib/channels/vwapbands/Vwapbands.cs
index 7954ccc5..4af73f48 100644
--- a/lib/channels/vwapbands/Vwapbands.cs
+++ b/lib/channels/vwapbands/Vwapbands.cs
@@ -364,7 +364,7 @@ public sealed class Vwapbands : AbstractBase
/// Output span for VWAP values
/// Output span for standard deviation values
/// Band multiplier (default 1.0)
- public static void Calculate(
+ public static void Batch(
ReadOnlySpan price,
ReadOnlySpan volume,
Span upper1,
diff --git a/lib/channels/vwapsd/Vwapsd.Tests.cs b/lib/channels/vwapsd/Vwapsd.Tests.cs
index a1d8c10b..d6710686 100644
--- a/lib/channels/vwapsd/Vwapsd.Tests.cs
+++ b/lib/channels/vwapsd/Vwapsd.Tests.cs
@@ -472,17 +472,17 @@ public class VwapsdTests
// NumDevs must be >= MinNumDevs
Assert.Throws(() =>
- Vwapsd.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapsd.Batch(price.AsSpan(), volume.AsSpan(),
upper.AsSpan(), lower.AsSpan(), vwap.AsSpan(), stdDev.AsSpan(), 0));
// NumDevs must be <= MaxNumDevs
Assert.Throws(() =>
- Vwapsd.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapsd.Batch(price.AsSpan(), volume.AsSpan(),
upper.AsSpan(), lower.AsSpan(), vwap.AsSpan(), stdDev.AsSpan(), 6.0));
// All arrays must be same length
Assert.Throws(() =>
- Vwapsd.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapsd.Batch(price.AsSpan(), volume.AsSpan(),
wrongSize.AsSpan(), lower.AsSpan(), vwap.AsSpan(), stdDev.AsSpan(), 1.0));
}
@@ -496,7 +496,7 @@ public class VwapsdTests
double[] vwap = new double[5];
double[] stdDev = new double[5];
- Vwapsd.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapsd.Batch(price.AsSpan(), volume.AsSpan(),
upper.AsSpan(), lower.AsSpan(), vwap.AsSpan(), stdDev.AsSpan(), 1.0);
foreach (var val in vwap)
diff --git a/lib/channels/vwapsd/Vwapsd.Validation.Tests.cs b/lib/channels/vwapsd/Vwapsd.Validation.Tests.cs
index 3eba528e..ca8cc8d3 100644
--- a/lib/channels/vwapsd/Vwapsd.Validation.Tests.cs
+++ b/lib/channels/vwapsd/Vwapsd.Validation.Tests.cs
@@ -120,7 +120,7 @@ public sealed class VwapsdValidationTests : IDisposable
double[] spanLower = new double[bars.Count];
double[] spanStdDev = new double[bars.Count];
- Vwapsd.Calculate(price.AsSpan(), volume.AsSpan(),
+ Vwapsd.Batch(price.AsSpan(), volume.AsSpan(),
spanUpper.AsSpan(), spanLower.AsSpan(),
spanVwap.AsSpan(), spanStdDev.AsSpan(), numDevs);
diff --git a/lib/channels/vwapsd/Vwapsd.cs b/lib/channels/vwapsd/Vwapsd.cs
index 043363e5..7e40d2c4 100644
--- a/lib/channels/vwapsd/Vwapsd.cs
+++ b/lib/channels/vwapsd/Vwapsd.cs
@@ -344,7 +344,7 @@ public sealed class Vwapsd : AbstractBase
/// Output span for VWAP values
/// Output span for standard deviation values
/// Number of standard deviations for bands (default 2.0)
- public static void Calculate(
+ public static void Batch(
ReadOnlySpan price,
ReadOnlySpan volume,
Span upper,
diff --git a/lib/cycles/cg/Cg.Tests.cs b/lib/cycles/cg/Cg.Tests.cs
index 8289d989..513e7501 100644
--- a/lib/cycles/cg/Cg.Tests.cs
+++ b/lib/cycles/cg/Cg.Tests.cs
@@ -425,7 +425,7 @@ public class CgTests
var batchResult = batchIndicator.Update(tSeries);
// Mode 3: Static Calculate
- var staticResult = Cg.Calculate(tSeries, period);
+ var staticResult = Cg.Batch(tSeries, period);
// Mode 4: Span-based Batch
double[] sourceArray = new double[dataLen];
diff --git a/lib/cycles/cg/Cg.Validation.Tests.cs b/lib/cycles/cg/Cg.Validation.Tests.cs
index 326790bb..0c34df56 100644
--- a/lib/cycles/cg/Cg.Validation.Tests.cs
+++ b/lib/cycles/cg/Cg.Validation.Tests.cs
@@ -219,7 +219,7 @@ public class CgValidationTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Cg.Calculate(tSeries, period);
+ var batch = Cg.Batch(tSeries, period);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
@@ -241,7 +241,7 @@ public class CgValidationTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var tSeriesResult = Cg.Calculate(tSeries, period);
+ var tSeriesResult = Cg.Batch(tSeries, period);
// Span approach
double[] source = new double[dataLen];
diff --git a/lib/cycles/cg/Cg.cs b/lib/cycles/cg/Cg.cs
index 3bfbd012..7cba9311 100644
--- a/lib/cycles/cg/Cg.cs
+++ b/lib/cycles/cg/Cg.cs
@@ -213,7 +213,7 @@ public sealed class Cg : AbstractBase
///
/// Calculates CG for a time series.
///
- public static TSeries Calculate(TSeries source, int period = 10)
+ public static TSeries Batch(TSeries source, int period = 10)
{
var cg = new Cg(period);
return cg.Update(source);
@@ -244,6 +244,13 @@ public sealed class Cg : AbstractBase
CalculateScalarCore(source, output, period);
}
+ public static (TSeries Results, Cg Indicator) Calculate(TSeries source, int period = 10)
+ {
+ var indicator = new Cg(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CalculateScalarCore(ReadOnlySpan source, Span output, int period)
{
diff --git a/lib/cycles/dsp/Dsp.Tests.cs b/lib/cycles/dsp/Dsp.Tests.cs
index 1251d712..2d10ec94 100644
--- a/lib/cycles/dsp/Dsp.Tests.cs
+++ b/lib/cycles/dsp/Dsp.Tests.cs
@@ -298,7 +298,7 @@ public class DspTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Dsp.Calculate(tSeries, period);
+ var batch = Dsp.Batch(tSeries, period);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
diff --git a/lib/cycles/dsp/Dsp.Validation.Tests.cs b/lib/cycles/dsp/Dsp.Validation.Tests.cs
index 50902004..317114b3 100644
--- a/lib/cycles/dsp/Dsp.Validation.Tests.cs
+++ b/lib/cycles/dsp/Dsp.Validation.Tests.cs
@@ -205,7 +205,7 @@ public class DspValidationTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Dsp.Calculate(tSeries, period);
+ var batch = Dsp.Batch(tSeries, period);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
@@ -227,7 +227,7 @@ public class DspValidationTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var tSeriesResult = Dsp.Calculate(tSeries, period);
+ var tSeriesResult = Dsp.Batch(tSeries, period);
// Span approach
double[] source = new double[dataLen];
diff --git a/lib/cycles/dsp/Dsp.cs b/lib/cycles/dsp/Dsp.cs
index 27e72274..19939b7a 100644
--- a/lib/cycles/dsp/Dsp.cs
+++ b/lib/cycles/dsp/Dsp.cs
@@ -210,7 +210,7 @@ public sealed class Dsp : AbstractBase
///
/// Calculates DSP for a time series.
///
- public static TSeries Calculate(TSeries source, int period = 40)
+ public static TSeries Batch(TSeries source, int period = 40)
{
var dsp = new Dsp(period);
return dsp.Update(source);
@@ -290,4 +290,11 @@ public sealed class Dsp : AbstractBase
output[i] = emaFast - emaSlow;
}
}
+
+ public static (TSeries Results, Dsp Indicator) Calculate(TSeries source, int period = 40)
+ {
+ var indicator = new Dsp(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
\ No newline at end of file
diff --git a/lib/cycles/eacp/Eacp.Tests.cs b/lib/cycles/eacp/Eacp.Tests.cs
index 538e21d4..15c7c172 100644
--- a/lib/cycles/eacp/Eacp.Tests.cs
+++ b/lib/cycles/eacp/Eacp.Tests.cs
@@ -321,7 +321,7 @@ public class EacpTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Eacp.Calculate(tSeries, minPeriod, maxPeriod);
+ var batch = Eacp.Batch(tSeries, minPeriod, maxPeriod);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
diff --git a/lib/cycles/eacp/Eacp.Validation.Tests.cs b/lib/cycles/eacp/Eacp.Validation.Tests.cs
index 63596874..e2badd10 100644
--- a/lib/cycles/eacp/Eacp.Validation.Tests.cs
+++ b/lib/cycles/eacp/Eacp.Validation.Tests.cs
@@ -196,7 +196,7 @@ public class EacpValidationTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Eacp.Calculate(tSeries, minPeriod, maxPeriod);
+ var batch = Eacp.Batch(tSeries, minPeriod, maxPeriod);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
@@ -219,7 +219,7 @@ public class EacpValidationTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var tSeriesResult = Eacp.Calculate(tSeries, minPeriod, maxPeriod);
+ var tSeriesResult = Eacp.Batch(tSeries, minPeriod, maxPeriod);
// Span approach
double[] source = new double[dataLen];
diff --git a/lib/cycles/eacp/Eacp.cs b/lib/cycles/eacp/Eacp.cs
index b9c426b9..94aa44db 100644
--- a/lib/cycles/eacp/Eacp.cs
+++ b/lib/cycles/eacp/Eacp.cs
@@ -405,7 +405,7 @@ public sealed class Eacp : AbstractBase
///
/// Calculates EACP for a time series.
///
- public static TSeries Calculate(TSeries source, int minPeriod = 8, int maxPeriod = 48,
+ public static TSeries Batch(TSeries source, int minPeriod = 8, int maxPeriod = 48,
int avgLength = 3, bool enhance = true)
{
var eacp = new Eacp(minPeriod, maxPeriod, avgLength, enhance);
@@ -447,4 +447,11 @@ public sealed class Eacp : AbstractBase
output[i] = result.Value;
}
}
+
+ public static (TSeries Results, Eacp Indicator) Calculate(TSeries source, int minPeriod = 8, int maxPeriod = 48, int avgLength = 3, bool enhance = true)
+ {
+ var indicator = new Eacp(minPeriod, maxPeriod, avgLength, enhance);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
\ No newline at end of file
diff --git a/lib/cycles/ebsw/Ebsw.Tests.cs b/lib/cycles/ebsw/Ebsw.Tests.cs
index 357e72f9..451bbf91 100644
--- a/lib/cycles/ebsw/Ebsw.Tests.cs
+++ b/lib/cycles/ebsw/Ebsw.Tests.cs
@@ -320,7 +320,7 @@ public class EbswTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Ebsw.Calculate(tSeries, hpLength, ssfLength);
+ var batch = Ebsw.Batch(tSeries, hpLength, ssfLength);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
diff --git a/lib/cycles/ebsw/Ebsw.Validation.Tests.cs b/lib/cycles/ebsw/Ebsw.Validation.Tests.cs
index 253401b7..89387074 100644
--- a/lib/cycles/ebsw/Ebsw.Validation.Tests.cs
+++ b/lib/cycles/ebsw/Ebsw.Validation.Tests.cs
@@ -296,7 +296,7 @@ public class EbswValidationTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Ebsw.Calculate(tSeries, hpLength, ssfLength);
+ var batch = Ebsw.Batch(tSeries, hpLength, ssfLength);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
@@ -319,7 +319,7 @@ public class EbswValidationTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var tSeriesResult = Ebsw.Calculate(tSeries, hpLength, ssfLength);
+ var tSeriesResult = Ebsw.Batch(tSeries, hpLength, ssfLength);
// Span approach
double[] source = new double[dataLen];
diff --git a/lib/cycles/ebsw/Ebsw.cs b/lib/cycles/ebsw/Ebsw.cs
index 47602f79..bbfce1a4 100644
--- a/lib/cycles/ebsw/Ebsw.cs
+++ b/lib/cycles/ebsw/Ebsw.cs
@@ -246,7 +246,7 @@ public sealed class Ebsw : AbstractBase
///
/// Calculates EBSW for a time series.
///
- public static TSeries Calculate(TSeries source, int hpLength = 40, int ssfLength = 10)
+ public static TSeries Batch(TSeries source, int hpLength = 40, int ssfLength = 10)
{
var ebsw = new Ebsw(hpLength, ssfLength);
return ebsw.Update(source);
@@ -337,4 +337,11 @@ public sealed class Ebsw : AbstractBase
filt1 = filt0;
}
}
+
+ public static (TSeries Results, Ebsw Indicator) Calculate(TSeries source, int hpLength = 40, int ssfLength = 10)
+ {
+ var indicator = new Ebsw(hpLength, ssfLength);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
\ No newline at end of file
diff --git a/lib/cycles/homod/Homod.Tests.cs b/lib/cycles/homod/Homod.Tests.cs
index 55c3b7a2..5c55be22 100644
--- a/lib/cycles/homod/Homod.Tests.cs
+++ b/lib/cycles/homod/Homod.Tests.cs
@@ -297,7 +297,7 @@ public class HomodTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Homod.Calculate(tSeries, minPeriod, maxPeriod);
+ var batch = Homod.Batch(tSeries, minPeriod, maxPeriod);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
diff --git a/lib/cycles/homod/Homod.Validation.Tests.cs b/lib/cycles/homod/Homod.Validation.Tests.cs
index bf4494f1..31b7b506 100644
--- a/lib/cycles/homod/Homod.Validation.Tests.cs
+++ b/lib/cycles/homod/Homod.Validation.Tests.cs
@@ -160,7 +160,7 @@ public class HomodValidationTests
{
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var tSeriesResult = Homod.Calculate(tSeries, 6, 50);
+ var tSeriesResult = Homod.Batch(tSeries, 6, 50);
// Compare all values
for (int i = 0; i < bars.Count; i++)
diff --git a/lib/cycles/homod/Homod.cs b/lib/cycles/homod/Homod.cs
index 82bebea3..c440fe55 100644
--- a/lib/cycles/homod/Homod.cs
+++ b/lib/cycles/homod/Homod.cs
@@ -381,7 +381,7 @@ public sealed class Homod : AbstractBase
///
/// Calculates Homodyne Discriminator for a time series.
///
- public static TSeries Calculate(TSeries source, double minPeriod = 6.0, double maxPeriod = 50.0)
+ public static TSeries Batch(TSeries source, double minPeriod = 6.0, double maxPeriod = 50.0)
{
var homod = new Homod(minPeriod, maxPeriod);
return homod.Update(source);
@@ -421,4 +421,11 @@ public sealed class Homod : AbstractBase
output[i] = result.Value;
}
}
+
+ public static (TSeries Results, Homod Indicator) Calculate(TSeries source, double minPeriod = 6.0, double maxPeriod = 50.0)
+ {
+ var indicator = new Homod(minPeriod, maxPeriod);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
\ No newline at end of file
diff --git a/lib/cycles/ht_dcperiod/HtDcperiod.cs b/lib/cycles/ht_dcperiod/HtDcperiod.cs
index 86c6d712..8794e1e9 100644
--- a/lib/cycles/ht_dcperiod/HtDcperiod.cs
+++ b/lib/cycles/ht_dcperiod/HtDcperiod.cs
@@ -402,7 +402,7 @@ public sealed class HtDcperiod : AbstractBase
}
}
- public static void Calculate(ReadOnlySpan source, Span output)
+ public static void Batch(ReadOnlySpan source, Span output)
{
if (output.Length < source.Length)
{
@@ -416,9 +416,16 @@ public sealed class HtDcperiod : AbstractBase
}
}
- public static TSeries Calculate(TSeries source)
+ public static TSeries Batch(TSeries source)
{
var ht = new HtDcperiod();
return ht.Update(source);
}
+
+ public static (TSeries Results, HtDcperiod Indicator) Calculate(TSeries source)
+ {
+ var indicator = new HtDcperiod();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/cycles/ht_dcphase/HtDcphase.cs b/lib/cycles/ht_dcphase/HtDcphase.cs
index ffca1f14..f2baf2b9 100644
--- a/lib/cycles/ht_dcphase/HtDcphase.cs
+++ b/lib/cycles/ht_dcphase/HtDcphase.cs
@@ -472,7 +472,7 @@ public sealed class HtDcphase : AbstractBase
}
}
- public static void Calculate(ReadOnlySpan source, Span output)
+ public static void Batch(ReadOnlySpan source, Span output)
{
if (output.Length < source.Length)
{
@@ -486,9 +486,16 @@ public sealed class HtDcphase : AbstractBase
}
}
- public static TSeries Calculate(TSeries source)
+ public static TSeries Batch(TSeries source)
{
var ht = new HtDcphase();
return ht.Update(source);
}
+
+ public static (TSeries Results, HtDcphase Indicator) Calculate(TSeries source)
+ {
+ var indicator = new HtDcphase();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/cycles/ht_phasor/HtPhasor.cs b/lib/cycles/ht_phasor/HtPhasor.cs
index 514531ff..af742d91 100644
--- a/lib/cycles/ht_phasor/HtPhasor.cs
+++ b/lib/cycles/ht_phasor/HtPhasor.cs
@@ -415,7 +415,7 @@ public sealed class HtPhasor : AbstractBase
///
/// Calculates HT_PHASOR for a time series.
///
- public static TSeries Calculate(TSeries source)
+ public static TSeries Batch(TSeries source)
{
var htPhasor = new HtPhasor();
return htPhasor.Update(source);
@@ -453,4 +453,11 @@ public sealed class HtPhasor : AbstractBase
quadrature[i] = htPhasor.Quadrature;
}
}
-}
+
+ public static (TSeries Results, HtPhasor Indicator) Calculate(TSeries source)
+ {
+ var indicator = new HtPhasor();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/cycles/ht_sine/HtSine.Tests.cs b/lib/cycles/ht_sine/HtSine.Tests.cs
index 3080080f..0370f729 100644
--- a/lib/cycles/ht_sine/HtSine.Tests.cs
+++ b/lib/cycles/ht_sine/HtSine.Tests.cs
@@ -300,7 +300,7 @@ public class HtSineTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = HtSine.Calculate(tSeries);
+ var batch = HtSine.Batch(tSeries);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
diff --git a/lib/cycles/ht_sine/HtSine.cs b/lib/cycles/ht_sine/HtSine.cs
index e43c28bb..c44262f1 100644
--- a/lib/cycles/ht_sine/HtSine.cs
+++ b/lib/cycles/ht_sine/HtSine.cs
@@ -496,7 +496,7 @@ public sealed class HtSine : AbstractBase
///
/// Calculates HT_SINE for a time series.
///
- public static TSeries Calculate(TSeries source)
+ public static TSeries Batch(TSeries source)
{
var htSine = new HtSine();
return htSine.Update(source);
@@ -534,4 +534,11 @@ public sealed class HtSine : AbstractBase
leadSine[i] = htSine.LeadSine;
}
}
+
+ public static (TSeries Results, HtSine Indicator) Calculate(TSeries source)
+ {
+ var indicator = new HtSine();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
\ No newline at end of file
diff --git a/lib/cycles/lunar/Lunar.Tests.cs b/lib/cycles/lunar/Lunar.Tests.cs
index bd27c34a..b1768131 100644
--- a/lib/cycles/lunar/Lunar.Tests.cs
+++ b/lib/cycles/lunar/Lunar.Tests.cs
@@ -171,7 +171,7 @@ public class LunarTests
series.Add(new TValue(startDate.AddDays(i), 100.0 + i));
}
- var result = Lunar.Calculate(series);
+ var result = Lunar.Batch(series);
Assert.Equal(30, result.Count);
diff --git a/lib/cycles/lunar/Lunar.cs b/lib/cycles/lunar/Lunar.cs
index 850e1f5a..509e3f0d 100644
--- a/lib/cycles/lunar/Lunar.cs
+++ b/lib/cycles/lunar/Lunar.cs
@@ -91,7 +91,7 @@ public sealed class Lunar : AbstractBase
///
/// Creates a new Lunar indicator and calculates phases for the source series.
///
- public static TSeries Calculate(TSeries source)
+ public static TSeries Batch(TSeries source)
{
var lunar = new Lunar();
return lunar.Update(source);
@@ -114,6 +114,13 @@ public sealed class Lunar : AbstractBase
}
}
+ public static (TSeries Results, Lunar Indicator) Calculate(TSeries source)
+ {
+ var indicator = new Lunar();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
///
/// Calculates lunar phase for a specific DateTime.
///
diff --git a/lib/cycles/sine/Sine.Tests.cs b/lib/cycles/sine/Sine.Tests.cs
index 63dd6fcc..eb3c1e12 100644
--- a/lib/cycles/sine/Sine.Tests.cs
+++ b/lib/cycles/sine/Sine.Tests.cs
@@ -185,7 +185,7 @@ public class SineTests
series.Add(new TValue(bar.Time, bar.Close));
}
- var result = Sine.Calculate(series);
+ var result = Sine.Batch(series);
Assert.Equal(100, result.Count);
}
@@ -201,7 +201,7 @@ public class SineTests
series.Add(new TValue(bar.Time, bar.Close));
}
- var result = Sine.Calculate(series, hpPeriod: 20, ssfPeriod: 5);
+ var result = Sine.Batch(series, hpPeriod: 20, ssfPeriod: 5);
Assert.Equal(100, result.Count);
}
@@ -252,7 +252,7 @@ public class SineTests
}
// Batch calculation
- var batchResult = Sine.Calculate(series);
+ var batchResult = Sine.Batch(series);
// Compare last 100 values (after warmup)
for (int i = 100; i < 200; i++)
diff --git a/lib/cycles/sine/Sine.cs b/lib/cycles/sine/Sine.cs
index 699b3069..bfd4387d 100644
--- a/lib/cycles/sine/Sine.cs
+++ b/lib/cycles/sine/Sine.cs
@@ -215,12 +215,19 @@ public sealed class Sine : AbstractBase
///
/// Creates a new Sine indicator and calculates for the source series.
///
- public static TSeries Calculate(TSeries source, int hpPeriod = 40, int ssfPeriod = 10)
+ public static TSeries Batch(TSeries source, int hpPeriod = 40, int ssfPeriod = 10)
{
var sine = new Sine(hpPeriod, ssfPeriod);
return sine.Update(source);
}
+ public static (TSeries Results, Sine Indicator) Calculate(TSeries source, int hpPeriod = 40, int ssfPeriod = 10)
+ {
+ var indicator = new Sine(hpPeriod, ssfPeriod);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
public override void Reset()
{
_srcBuffer.Clear();
@@ -240,4 +247,4 @@ public sealed class Sine : AbstractBase
Update(new TValue(baseTime + (interval * i), source[i]), true);
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/cycles/solar/Solar.Tests.cs b/lib/cycles/solar/Solar.Tests.cs
index 78a0b876..7ede4003 100644
--- a/lib/cycles/solar/Solar.Tests.cs
+++ b/lib/cycles/solar/Solar.Tests.cs
@@ -160,7 +160,7 @@ public class SolarTests
series.Add(new TValue(startDate.AddDays(i), 100.0 + i));
}
- var result = Solar.Calculate(series);
+ var result = Solar.Batch(series);
Assert.Equal(30, result.Count);
diff --git a/lib/cycles/solar/Solar.cs b/lib/cycles/solar/Solar.cs
index 976e822a..65258cab 100644
--- a/lib/cycles/solar/Solar.cs
+++ b/lib/cycles/solar/Solar.cs
@@ -91,7 +91,7 @@ public sealed class Solar : AbstractBase
///
/// Creates a new Solar indicator and calculates cycles for the source series.
///
- public static TSeries Calculate(TSeries source)
+ public static TSeries Batch(TSeries source)
{
var solar = new Solar();
return solar.Update(source);
@@ -114,6 +114,13 @@ public sealed class Solar : AbstractBase
}
}
+ public static (TSeries Results, Solar Indicator) Calculate(TSeries source)
+ {
+ var indicator = new Solar();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
///
/// Calculates solar cycle for a specific DateTime.
///
diff --git a/lib/cycles/ssfdsp/Ssfdsp.Tests.cs b/lib/cycles/ssfdsp/Ssfdsp.Tests.cs
index 27392ae2..b03ea65f 100644
--- a/lib/cycles/ssfdsp/Ssfdsp.Tests.cs
+++ b/lib/cycles/ssfdsp/Ssfdsp.Tests.cs
@@ -307,7 +307,7 @@ public class SsfdspTests
tSeries.Add(new TValue(bar.Time, bar.Close));
}
- var batch = Ssfdsp.Calculate(tSeries, period);
+ var batch = Ssfdsp.Batch(tSeries, period);
// Compare last values
Assert.Equal(batch[^1].Value, streaming.Last.Value, Tolerance);
diff --git a/lib/cycles/ssfdsp/Ssfdsp.Validation.Tests.cs b/lib/cycles/ssfdsp/Ssfdsp.Validation.Tests.cs
index 59d5249f..e7493b48 100644
--- a/lib/cycles/ssfdsp/Ssfdsp.Validation.Tests.cs
+++ b/lib/cycles/ssfdsp/Ssfdsp.Validation.Tests.cs
@@ -292,7 +292,7 @@ public class SsfdspValidationTests
}
// TSeries Calculate
- var tsResult = Ssfdsp.Calculate(tSeries, period);
+ var tsResult = Ssfdsp.Batch(tSeries, period);
// Streaming
var streaming = new Ssfdsp(period);
diff --git a/lib/cycles/ssfdsp/Ssfdsp.cs b/lib/cycles/ssfdsp/Ssfdsp.cs
index ca0e6f74..3744d9a5 100644
--- a/lib/cycles/ssfdsp/Ssfdsp.cs
+++ b/lib/cycles/ssfdsp/Ssfdsp.cs
@@ -229,7 +229,7 @@ public sealed class Ssfdsp : AbstractBase
///
/// Calculates SSF-DSP for a time series.
///
- public static TSeries Calculate(TSeries source, int period = 40)
+ public static TSeries Batch(TSeries source, int period = 40)
{
var ssfdsp = new Ssfdsp(period);
return ssfdsp.Update(source);
@@ -328,4 +328,11 @@ public sealed class Ssfdsp : AbstractBase
output[i] = ssfFast - ssfSlow;
}
}
+
+ public static (TSeries Results, Ssfdsp Indicator) Calculate(TSeries source, int period = 40)
+ {
+ var indicator = new Ssfdsp(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
\ No newline at end of file
diff --git a/lib/cycles/stc/Stc.Tests.cs b/lib/cycles/stc/Stc.Tests.cs
index ee380946..7391d39f 100644
--- a/lib/cycles/stc/Stc.Tests.cs
+++ b/lib/cycles/stc/Stc.Tests.cs
@@ -93,7 +93,7 @@ public class StcTests
var batchStc = CreateDefaultStc();
var tseriesResult = batchStc.Update(series);
- Stc.Calculate(input.AsSpan(), output.AsSpan(), kPeriod: CycleLength, dPeriod: CycleLength, fastLength: FastLength, slowLength: SlowLength, smoothing: StcSmoothing.Sigmoid);
+ Stc.Batch(input.AsSpan(), output.AsSpan(), kPeriod: CycleLength, dPeriod: CycleLength, fastLength: FastLength, slowLength: SlowLength, smoothing: StcSmoothing.Sigmoid);
// Compare last value
Assert.Equal(tseriesResult.Last.Value, output[^1], 1e-9);
diff --git a/lib/cycles/stc/Stc.cs b/lib/cycles/stc/Stc.cs
index 6dee398c..69610864 100644
--- a/lib/cycles/stc/Stc.cs
+++ b/lib/cycles/stc/Stc.cs
@@ -473,7 +473,7 @@ public sealed class Stc : AbstractBase
///
/// Static convenience method that creates a new Stc instance and processes the entire series.
///
- public static TSeries Calculate(TSeries source, int kPeriod = 10, int dPeriod = 3, int fastLength = 23, int slowLength = 50, StcSmoothing smoothing = StcSmoothing.Ema)
+ public static TSeries Batch(TSeries source, int kPeriod = 10, int dPeriod = 3, int fastLength = 23, int slowLength = 50, StcSmoothing smoothing = StcSmoothing.Ema)
{
var indicator = new Stc(kPeriod, dPeriod, fastLength, slowLength, smoothing);
return indicator.Update(source);
@@ -483,7 +483,7 @@ public sealed class Stc : AbstractBase
// replicate the full STC state machine inline for zero-allocation performance.
// The sequential MACD→Stoch1→Stoch2→Smoothing pipeline cannot be decomposed
// without introducing heap allocations or sacrificing inlining opportunities.
- public static void Calculate(ReadOnlySpan source, Span output,
+ public static void Batch(ReadOnlySpan source, Span output,
int kPeriod = 10, int dPeriod = 3, int fastLength = 23, int slowLength = 50, StcSmoothing smoothing = StcSmoothing.Ema)
{
if (source.Length != output.Length)
@@ -665,4 +665,11 @@ public sealed class Stc : AbstractBase
}
}
}
-}
+
+ public static (TSeries Results, Stc Indicator) Calculate(TSeries source, int kPeriod = 10, int dPeriod = 3, int fastLength = 23, int slowLength = 50, StcSmoothing smoothing = StcSmoothing.Ema)
+ {
+ var indicator = new Stc(kPeriod, dPeriod, fastLength, slowLength, smoothing);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/dynamics/_index.md b/lib/dynamics/_index.md
index 299ff55f..976b28fa 100644
--- a/lib/dynamics/_index.md
+++ b/lib/dynamics/_index.md
@@ -18,11 +18,10 @@ Dynamics indicators measure trend strength, speed, and direction. Unlike momentu
| [DMX](dmx/Dmx.md) | Jurik DMX | Smoothed bipolar DMI using Jurik smoothing. Low noise. |
| [DX](dx/Dx.md) | Directional Movement Index | Raw directional strength. Unsmoothed ADX component. |
| [HT_TRENDMODE](ht_trendmode/Ht_trendmode.md) | HT Trend vs Cycle | Ehlers Hilbert Transform. Binary trend/cycle mode detection. |
-| [ICHIMOKU](ichimoku/Ichimoku.md) | Ichimoku Cloud | Five-line system. Cloud defines support/resistance zones. |
-| [IMI](imi/Imi.md) | Intraday Momentum Index | RSI variant using open-close range. Intraday overbought/oversold. |
+| [ICHIMOKU](ichimoku/Ichimoku.cs) | Ichimoku Cloud | Five-line system. Cloud defines support/resistance zones. |
+| [IMI](imi/Imi.cs) | Intraday Momentum Index | RSI variant using open-close range. Intraday overbought/oversold. |
| [QSTICK](qstick/Qstick.md) | Qstick | MA of (Close - Open). Positive = buying pressure. |
-| SAR | Stop and Reverse | |
| [SUPER](super/Super.md) | SuperTrend | ATR-based trailing stop. Flips on breakout. Color-coded direction. |
-| [TTM](ttm/Ttm.md) | TTM Trend | Fast 6-period EMA. Color-coded trend from John Carter. |
+| [TTM_TREND](ttm_trend/TtmTrend.md) | TTM Trend | Fast 6-period EMA. Color-coded trend from John Carter. |
| [TTM_SQUEEZE](ttm_squeeze/TtmSqueeze.md) | TTM Squeeze | BB inside KC squeeze detection with linear regression momentum. John Carter. |
| [VORTEX](vortex/Vortex.md) | Vortex Indicator | VI+ and VI- measure positive/negative trend movement. |
diff --git a/lib/dynamics/adx/Adx.cs b/lib/dynamics/adx/Adx.cs
index ba2b16fb..39bcad5a 100644
--- a/lib/dynamics/adx/Adx.cs
+++ b/lib/dynamics/adx/Adx.cs
@@ -348,7 +348,7 @@ public sealed class Adx : ITValuePublisher
var v = new double[len];
// Use the static Calculate method for performance
- Calculate(source.High.Values, source.Low.Values, source.Close.Values, _period, v);
+ Batch(source.High.Values, source.Low.Values, source.Close.Values, _period, v);
// Create lists for TSeries - use collection expression directly
var tList = new List(len);
@@ -405,7 +405,7 @@ public sealed class Adx : ITValuePublisher
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Calculate(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close, int period, Span destination)
+ public static void Batch(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close, int period, Span destination)
{
int len = high.Length;
if (len < period * 2)
@@ -490,7 +490,7 @@ public sealed class Adx : ITValuePublisher
var len = source.Count;
var v = new double[len];
- Calculate(source.High.Values, source.Low.Values, source.Close.Values, period, v);
+ Batch(source.High.Values, source.Low.Values, source.Close.Values, period, v);
var tList = new List(len);
var times = source.Open.Times;
@@ -501,4 +501,11 @@ public sealed class Adx : ITValuePublisher
return new TSeries(tList, [.. v]);
}
+
+ public static (TSeries Results, Adx Indicator) Calculate(TBarSeries source, int period)
+ {
+ var indicator = new Adx(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/dynamics/adxr/Adxr.cs b/lib/dynamics/adxr/Adxr.cs
index 2c3813da..d576b585 100644
--- a/lib/dynamics/adxr/Adxr.cs
+++ b/lib/dynamics/adxr/Adxr.cs
@@ -132,7 +132,7 @@ public sealed class Adxr : ITValuePublisher
int len = source.Count;
var v = new double[len];
- Calculate(source.High.Values, source.Low.Values, source.Close.Values, _period, v);
+ Batch(source.High.Values, source.Low.Values, source.Close.Values, _period, v);
var tList = new List(len);
var vList = new List(v);
@@ -153,7 +153,7 @@ public sealed class Adxr : ITValuePublisher
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Calculate(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close, int period, Span destination)
+ public static void Batch(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close, int period, Span destination)
{
int len = high.Length;
if (len == 0 || len != low.Length || len != close.Length || len != destination.Length)
@@ -180,7 +180,7 @@ public sealed class Adxr : ITValuePublisher
try
{
- Adx.Calculate(high, low, close, period, adxSpan);
+ Adx.Batch(high, low, close, period, adxSpan);
destination.Clear();
@@ -223,7 +223,7 @@ public sealed class Adxr : ITValuePublisher
int len = source.Count;
var v = new double[len];
- Calculate(source.High.Values, source.Low.Values, source.Close.Values, period, v);
+ Batch(source.High.Values, source.Low.Values, source.Close.Values, period, v);
var tList = new List(len);
var times = source.Open.Times;
@@ -234,4 +234,11 @@ public sealed class Adxr : ITValuePublisher
return new TSeries(tList, [.. v]);
}
+
+ public static (TSeries Results, Adxr Indicator) Calculate(TBarSeries source, int period)
+ {
+ var indicator = new Adxr(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/dynamics/alligator/Alligator.cs b/lib/dynamics/alligator/Alligator.cs
index bd6ebfbf..e906cfc3 100644
--- a/lib/dynamics/alligator/Alligator.cs
+++ b/lib/dynamics/alligator/Alligator.cs
@@ -311,6 +311,14 @@ public sealed class Alligator : ITValuePublisher
return alligator.Update(source);
}
+ public static (TSeries Results, Alligator Indicator) Calculate(TBarSeries source)
+ {
+ var indicator = new Alligator();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
+
///
/// Gets the Jaw period value.
///
diff --git a/lib/dynamics/amat/Amat.Tests.cs b/lib/dynamics/amat/Amat.Tests.cs
index 1020e365..661198b8 100644
--- a/lib/dynamics/amat/Amat.Tests.cs
+++ b/lib/dynamics/amat/Amat.Tests.cs
@@ -328,7 +328,7 @@ public class AmatTests
var tValues = _testData.Values.ToArray();
var spanInput = new ReadOnlySpan(tValues);
var spanOutput = new double[tValues.Length];
- Amat.Calculate(spanInput, spanOutput, fastPeriod, slowPeriod);
+ Amat.Batch(spanInput, spanOutput, fastPeriod, slowPeriod);
double spanResult = spanOutput[^1];
// 3. Streaming Mode (instance, one value at a time)
@@ -363,13 +363,13 @@ public class AmatTests
double[] wrongSize = new double[3];
Assert.Throws(() =>
- Amat.Calculate(source.AsSpan(), wrongSize.AsSpan(), strength.AsSpan(), 5, 10));
+ Amat.Batch(source.AsSpan(), wrongSize.AsSpan(), strength.AsSpan(), 5, 10));
Assert.Throws(() =>
- Amat.Calculate(source.AsSpan(), trend.AsSpan(), wrongSize.AsSpan(), 5, 10));
+ Amat.Batch(source.AsSpan(), trend.AsSpan(), wrongSize.AsSpan(), 5, 10));
Assert.Throws(() =>
- Amat.Calculate(source.AsSpan(), trend.AsSpan(), strength.AsSpan(), 0, 10));
+ Amat.Batch(source.AsSpan(), trend.AsSpan(), strength.AsSpan(), 0, 10));
Assert.Throws(() =>
- Amat.Calculate(source.AsSpan(), trend.AsSpan(), strength.AsSpan(), 10, 5)); // fast >= slow
+ Amat.Batch(source.AsSpan(), trend.AsSpan(), strength.AsSpan(), 10, 5)); // fast >= slow
}
[Fact]
@@ -379,7 +379,7 @@ public class AmatTests
double[] trend = new double[source.Length];
var tseriesResult = Amat.Batch(_testData, 10, 30);
- Amat.Calculate(source.AsSpan(), trend.AsSpan(), 10, 30);
+ Amat.Batch(source.AsSpan(), trend.AsSpan(), 10, 30);
// Since trend values are discrete (-1, 0, 1), check after warmup where
// both methods should converge. Early values may differ due to EMA initialization.
@@ -404,7 +404,7 @@ public class AmatTests
double[] trend = new double[10];
double[] strength = new double[10];
- Amat.Calculate(source.AsSpan(), trend.AsSpan(), strength.AsSpan(), 3, 5);
+ Amat.Batch(source.AsSpan(), trend.AsSpan(), strength.AsSpan(), 3, 5);
foreach (var val in trend)
{
diff --git a/lib/dynamics/amat/Amat.Validation.Tests.cs b/lib/dynamics/amat/Amat.Validation.Tests.cs
index e863dfc3..2f956b92 100644
--- a/lib/dynamics/amat/Amat.Validation.Tests.cs
+++ b/lib/dynamics/amat/Amat.Validation.Tests.cs
@@ -351,7 +351,7 @@ public sealed class AmatValidationTests : IDisposable
double[] sourceData = _testData.RawData.ToArray();
double[] spanTrend = new double[sourceData.Length];
double[] spanStrength = new double[sourceData.Length];
- Amat.Calculate(sourceData, spanTrend, spanStrength, fastPeriod, slowPeriod);
+ Amat.Batch(sourceData, spanTrend, spanStrength, fastPeriod, slowPeriod);
// Compare trend values (after warmup period)
int warmup = slowPeriod * 2; // Allow extra warmup for convergence
diff --git a/lib/dynamics/amat/Amat.cs b/lib/dynamics/amat/Amat.cs
index 7dcf571f..031b31cd 100644
--- a/lib/dynamics/amat/Amat.cs
+++ b/lib/dynamics/amat/Amat.cs
@@ -391,7 +391,7 @@ public sealed class Amat : ITValuePublisher, IDisposable
/// Fast EMA period
/// Slow EMA period
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- public static void Calculate(ReadOnlySpan source, Span trend, Span strength,
+ public static void Batch(ReadOnlySpan source, Span trend, Span strength,
int fastPeriod = 10, int slowPeriod = 50)
{
if (source.Length != trend.Length)
@@ -501,7 +501,7 @@ public sealed class Amat : ITValuePublisher, IDisposable
/// Fast EMA period
/// Slow EMA period
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- public static void Calculate(ReadOnlySpan source, Span trend,
+ public static void Batch(ReadOnlySpan source, Span trend,
int fastPeriod = 10, int slowPeriod = 50)
{
if (source.Length != trend.Length)
@@ -585,6 +585,19 @@ public sealed class Amat : ITValuePublisher, IDisposable
}
}
+ ///
+ /// Calculates AMAT for the entire series using a new instance.
+ ///
+ /// Input series
+ /// Fast EMA period
+ /// Slow EMA period
+ /// AMAT trend series
+ public static TSeries Batch(TSeries source, int fastPeriod = 10, int slowPeriod = 50)
+ {
+ var amat = new Amat(fastPeriod, slowPeriod);
+ return amat.Update(source);
+ }
+
///
/// Runs a high-performance batch calculation on history and returns
/// a "Hot" Amat instance ready to process the next tick immediately.
@@ -600,16 +613,4 @@ public sealed class Amat : ITValuePublisher, IDisposable
return (results, amat);
}
- ///
- /// Calculates AMAT for the entire series using a new instance.
- ///
- /// Input series
- /// Fast EMA period
- /// Slow EMA period
- /// AMAT trend series
- public static TSeries Batch(TSeries source, int fastPeriod = 10, int slowPeriod = 50)
- {
- var amat = new Amat(fastPeriod, slowPeriod);
- return amat.Update(source);
- }
}
diff --git a/lib/dynamics/aroon/Aroon.cs b/lib/dynamics/aroon/Aroon.cs
index e34b8a3d..fc21dc7c 100644
--- a/lib/dynamics/aroon/Aroon.cs
+++ b/lib/dynamics/aroon/Aroon.cs
@@ -165,7 +165,7 @@ public sealed class Aroon : ITValuePublisher
int len = source.Count;
var v = new double[len];
- Calculate(source.High.Values, source.Low.Values, _period, v);
+ Batch(source.High.Values, source.Low.Values, _period, v);
var tList = new List(len);
var vList = new List(v);
@@ -193,7 +193,7 @@ public sealed class Aroon : ITValuePublisher
/// Lookback period
/// Output oscillator values (Up - Down)
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- public static void Calculate(ReadOnlySpan high, ReadOnlySpan low, int period, Span destination)
+ public static void Batch(ReadOnlySpan high, ReadOnlySpan low, int period, Span destination)
{
int len = high.Length;
if (len == 0 || len != low.Length || len != destination.Length || period <= 0)
@@ -293,7 +293,7 @@ public sealed class Aroon : ITValuePublisher
int len = source.Count;
var v = new double[len];
- Calculate(source.High.Values, source.Low.Values, period, v);
+ Batch(source.High.Values, source.Low.Values, period, v);
var tList = new List(len);
var times = source.Open.Times;
@@ -304,4 +304,11 @@ public sealed class Aroon : ITValuePublisher
return new TSeries(tList, [.. v]);
}
+
+ public static (TSeries Results, Aroon Indicator) Calculate(TBarSeries source, int period)
+ {
+ var indicator = new Aroon(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/dynamics/aroonosc/AroonOsc.cs b/lib/dynamics/aroonosc/AroonOsc.cs
index 931c409c..97c4f081 100644
--- a/lib/dynamics/aroonosc/AroonOsc.cs
+++ b/lib/dynamics/aroonosc/AroonOsc.cs
@@ -150,7 +150,7 @@ public sealed class AroonOsc : ITValuePublisher
int len = source.Count;
var v = new double[len];
- Calculate(source.High.Values, source.Low.Values, period: _period, destination: v);
+ Batch(source.High.Values, source.Low.Values, period: _period, destination: v);
var tList = new List(len);
var vList = new List(v);
@@ -177,10 +177,10 @@ public sealed class AroonOsc : ITValuePublisher
/// Lookback period
/// Output oscillator values (Up - Down)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Calculate(ReadOnlySpan high, ReadOnlySpan low, int period, Span destination)
+ public static void Batch(ReadOnlySpan high, ReadOnlySpan low, int period, Span destination)
{
// Delegate to Aroon's O(n) monotonic deque implementation
- Aroon.Calculate(high, low, period, destination);
+ Aroon.Batch(high, low, period, destination);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -194,7 +194,7 @@ public sealed class AroonOsc : ITValuePublisher
int len = source.Count;
var v = new double[len];
- Calculate(source.High.Values, source.Low.Values, period, v);
+ Batch(source.High.Values, source.Low.Values, period, v);
var tList = new List(len);
var times = source.Open.Times;
@@ -205,4 +205,11 @@ public sealed class AroonOsc : ITValuePublisher
return new TSeries(tList, [.. v]);
}
+
+ public static (TSeries Results, AroonOsc Indicator) Calculate(TBarSeries source, int period)
+ {
+ var indicator = new AroonOsc(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/dynamics/chop/Chop.cs b/lib/dynamics/chop/Chop.cs
index 1ac1ae8f..10cff82f 100644
--- a/lib/dynamics/chop/Chop.cs
+++ b/lib/dynamics/chop/Chop.cs
@@ -256,4 +256,12 @@ public sealed class Chop : ITValuePublisher
var indicator = new Chop(period);
return indicator.Update(source);
}
+
+ public static (TSeries Results, Chop Indicator) Calculate(TBarSeries source)
+ {
+ var indicator = new Chop();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
}
diff --git a/lib/dynamics/dmx/Dmx.cs b/lib/dynamics/dmx/Dmx.cs
index e504b315..075166d6 100644
--- a/lib/dynamics/dmx/Dmx.cs
+++ b/lib/dynamics/dmx/Dmx.cs
@@ -166,7 +166,7 @@ public sealed class Dmx : ITValuePublisher
var vSpan = CollectionsMarshal.AsSpan(v);
// Span-based batch calculation
- Calculate(source.High.Values, source.Low.Values, source.Close.Values, _period, vSpan);
+ Batch(source.High.Values, source.Low.Values, source.Close.Values, _period, vSpan);
source.Close.Times.CopyTo(tSpan);
// Restore streaming state by replaying only tail bars (JMA needs ~2*period for full warmup)
@@ -182,7 +182,7 @@ public sealed class Dmx : ITValuePublisher
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Calculate(ReadOnlySpan high,
+ public static void Batch(ReadOnlySpan high,
ReadOnlySpan low,
ReadOnlySpan close,
int period,
@@ -271,9 +271,9 @@ public sealed class Dmx : ITValuePublisher
tr[i] = trRaw;
}
- Jma.Calculate(dmPlus, dmPlusSmooth, period);
- Jma.Calculate(dmMinus, dmMinusSmooth, period);
- Jma.Calculate(tr, trSmooth, period);
+ Jma.Batch(dmPlus, dmPlusSmooth, period);
+ Jma.Batch(dmMinus, dmMinusSmooth, period);
+ Jma.Batch(tr, trSmooth, period);
for (int i = 0; i < len; i++)
{
@@ -304,4 +304,11 @@ public sealed class Dmx : ITValuePublisher
var dmx = new Dmx(period);
return dmx.Update(source);
}
+
+ public static (TSeries Results, Dmx Indicator) Calculate(TBarSeries source, int period = 14)
+ {
+ var indicator = new Dmx(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/dynamics/dx/Dx.Validation.Tests.cs b/lib/dynamics/dx/Dx.Validation.Tests.cs
index 9e730d94..796fd006 100644
--- a/lib/dynamics/dx/Dx.Validation.Tests.cs
+++ b/lib/dynamics/dx/Dx.Validation.Tests.cs
@@ -26,44 +26,6 @@ public sealed class DxValidationTests : IDisposable
_data.Dispose();
}
- ///
- /// Validates DX against TA-Lib. Our DX uses the standard formula:
- /// DX = 100 × |+DI - -DI| / (+DI + -DI)
- /// This matches the Wilder/industry standard formula.
- ///
- /// NOTE: TA-Lib's DX function produces different results than computing DX
- /// from their standalone PlusDI/MinusDI functions. Our implementation matches:
- /// - TA-Lib's individual +DI and -DI (verified in DiPlus_MatchesTalib, DiMinus_MatchesTalib)
- /// - Tulip's DX (verified in MatchesTulip)
- /// - Skender's DI values (verified in MatchesSkender_DiValues)
- ///
- /// The discrepancy appears to be in TA-Lib's DX function itself, possibly due to
- /// internal rounding or unstable period handling that differs from the standalone DI functions.
- ///
- [Fact(Skip = "TA-Lib DX function differs from standard; we match TA-Lib's PlusDI/MinusDI and Tulip")]
- public void MatchesTalib()
- {
- var dx = new Dx(14);
- var results = new List();
-
- for (int i = 0; i < _data.Bars.Count; i++)
- {
- var res = dx.Update(_data.Bars[i]);
- results.Add(res.Value);
- }
-
- double[] hData = _data.Bars.High.Select(x => x.Value).ToArray();
- double[] lData = _data.Bars.Low.Select(x => x.Value).ToArray();
- double[] cData = _data.Bars.Close.Select(x => x.Value).ToArray();
- double[] outReal = new double[_data.Bars.Count];
-
- var retCode = Functions.Dx(hData, lData, cData, 0..^0, outReal, out var outRange, 14);
- Assert.Equal(Core.RetCode.Success, retCode);
-
- int lookback = Functions.DxLookback(14);
- ValidationHelper.VerifyData(results, outReal, outRange, lookback);
- }
-
[Fact]
public void MatchesTulip()
{
diff --git a/lib/dynamics/dx/Dx.cs b/lib/dynamics/dx/Dx.cs
index 1fb8a3cf..95444fad 100644
--- a/lib/dynamics/dx/Dx.cs
+++ b/lib/dynamics/dx/Dx.cs
@@ -299,7 +299,7 @@ public sealed class Dx : ITValuePublisher
var v = new double[len];
// Use the static Calculate method for performance
- Calculate(source.High.Values, source.Low.Values, source.Close.Values, _period, v);
+ Batch(source.High.Values, source.Low.Values, source.Close.Values, _period, v);
// Create lists for TSeries
var tList = new List(len);
@@ -356,7 +356,7 @@ public sealed class Dx : ITValuePublisher
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Calculate(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close, int period, Span destination)
+ public static void Batch(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close, int period, Span destination)
{
int len = high.Length;
if (len < period + 1)
@@ -419,7 +419,7 @@ public sealed class Dx : ITValuePublisher
var len = source.Count;
var v = new double[len];
- Calculate(source.High.Values, source.Low.Values, source.Close.Values, period, v);
+ Batch(source.High.Values, source.Low.Values, source.Close.Values, period, v);
var tList = new List(len);
var times = source.Open.Times;
@@ -430,4 +430,11 @@ public sealed class Dx : ITValuePublisher
return new TSeries(tList, [.. v]);
}
+
+ public static (TSeries Results, Dx Indicator) Calculate(TBarSeries source, int period = 14)
+ {
+ var indicator = new Dx(period);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/dynamics/ht_trendmode/HtTrendmode.Tests.cs b/lib/dynamics/ht_trendmode/HtTrendmode.Tests.cs
index e7f9ec50..48844ec6 100644
--- a/lib/dynamics/ht_trendmode/HtTrendmode.Tests.cs
+++ b/lib/dynamics/ht_trendmode/HtTrendmode.Tests.cs
@@ -221,7 +221,7 @@ public class HtTrendmodeTests
input[i] = 100.0 + Math.Sin(i * 0.15) * 8;
}
- HtTrendmode.Calculate(input.AsSpan(), output.AsSpan());
+ HtTrendmode.Batch(input.AsSpan(), output.AsSpan());
// After warmup, all values should be 0 or 1
for (int i = 40; i < output.Length; i++)
@@ -241,7 +241,7 @@ public class HtTrendmodeTests
series.Add(DateTime.UtcNow.AddMinutes(i), 100.0 + Math.Sin(i * 0.25) * 12);
}
- var result = HtTrendmode.Calculate(series);
+ var result = HtTrendmode.Batch(series);
Assert.Equal(100, result.Count);
}
diff --git a/lib/dynamics/ht_trendmode/HtTrendmode.Validation.Tests.cs b/lib/dynamics/ht_trendmode/HtTrendmode.Validation.Tests.cs
index b16e4bbf..fd8557ae 100644
--- a/lib/dynamics/ht_trendmode/HtTrendmode.Validation.Tests.cs
+++ b/lib/dynamics/ht_trendmode/HtTrendmode.Validation.Tests.cs
@@ -102,7 +102,7 @@ public sealed class HtTrendmodeValidationTests : IDisposable
}
// Act - Batch
- var batchResult = HtTrendmode.Calculate(series);
+ var batchResult = HtTrendmode.Batch(series);
// Assert
Assert.Equal(streamingResults.Count, batchResult.Count);
diff --git a/lib/dynamics/ht_trendmode/HtTrendmode.cs b/lib/dynamics/ht_trendmode/HtTrendmode.cs
index 491adcbf..e4a6ad23 100644
--- a/lib/dynamics/ht_trendmode/HtTrendmode.cs
+++ b/lib/dynamics/ht_trendmode/HtTrendmode.cs
@@ -587,7 +587,7 @@ public sealed class HtTrendmode : AbstractBase
}
}
- public static void Calculate(ReadOnlySpan source, Span output)
+ public static void Batch(ReadOnlySpan source, Span output)
{
if (output.Length < source.Length)
{
@@ -601,9 +601,16 @@ public sealed class HtTrendmode : AbstractBase
}
}
- public static TSeries Calculate(TSeries source)
+ public static TSeries Batch(TSeries source)
{
var ht = new HtTrendmode();
return ht.Update(source);
}
+
+ public static (TSeries Results, HtTrendmode Indicator) Calculate(TSeries source)
+ {
+ var indicator = new HtTrendmode();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/dynamics/qstick/Qstick.Quantower.Tests.cs b/lib/dynamics/qstick/Qstick.Quantower.Tests.cs
new file mode 100644
index 00000000..ce343d84
--- /dev/null
+++ b/lib/dynamics/qstick/Qstick.Quantower.Tests.cs
@@ -0,0 +1,144 @@
+using TradingPlatform.BusinessLayer;
+using Xunit;
+
+namespace QuanTAlib.Tests;
+
+public class QstickIndicatorTests
+{
+ [Fact]
+ public void Constructor_CreatesValidIndicator()
+ {
+ var indicator = new QstickIndicator();
+ Assert.NotNull(indicator);
+ Assert.Equal("Qstick Indicator", indicator.Name);
+ }
+
+ [Fact]
+ public void DefaultPeriod_Is14()
+ {
+ var indicator = new QstickIndicator();
+ Assert.Equal(14, indicator.Period);
+ }
+
+ [Fact]
+ public void DefaultMaType_IsSMA()
+ {
+ var indicator = new QstickIndicator();
+ Assert.Equal("SMA", indicator.MaType);
+ }
+
+ [Fact]
+ public void ShortName_IncludesParameters()
+ {
+ var indicator = new QstickIndicator { Period = 20, MaType = "EMA" };
+ Assert.Equal("QSTICK(20,EMA)", indicator.ShortName);
+ }
+
+ [Fact]
+ public void MinHistoryDepths_EqualsZero()
+ {
+ var indicator = new QstickIndicator { Period = 10 };
+ Assert.Equal(0, QstickIndicator.MinHistoryDepths);
+ IWatchlistIndicator watchlistIndicator = indicator;
+ Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
+ }
+
+ [Fact]
+ public void CalculationIntegration_ProducesCorrectValues()
+ {
+ var qstickCore = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ // Simulate bar data
+ var bar1 = new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000);
+ var bar2 = new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000);
+ var bar3 = new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 106.0, 1000);
+
+ qstickCore.Update(bar1);
+ qstickCore.Update(bar2);
+ var result = qstickCore.Update(bar3);
+
+ // SMA of (5, 3, 6) = 14/3 ≈ 4.667
+ Assert.Equal(14.0 / 3.0, result.Value, 10);
+ }
+
+ [Fact]
+ public void EmaMode_CalculatesCorrectly()
+ {
+ var qstickCore = new Qstick(3, useEma: true);
+ var time = DateTime.UtcNow;
+
+ // Bar 1: diff = 5
+ qstickCore.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+
+ // Bar 2: diff = -3, EMA with alpha = 0.5
+ var result = qstickCore.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 97.0, 1000));
+
+ // EMA = 0.5 * -3 + 0.5 * 5 = 1.0
+ Assert.Equal(1.0, result.Value, 10);
+ }
+
+ [Fact]
+ public void BullishBars_ProducePositiveQstick()
+ {
+ var qstick = new Qstick(5);
+ var time = DateTime.UtcNow;
+
+ // All bullish bars (close > open)
+ for (int i = 0; i < 5; i++)
+ {
+ qstick.Update(new TBar(time.AddMinutes(i).Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ }
+
+ Assert.True(qstick.Last.Value > 0);
+ Assert.Equal(5.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void BearishBars_ProduceNegativeQstick()
+ {
+ var qstick = new Qstick(5);
+ var time = DateTime.UtcNow;
+
+ // All bearish bars (close < open)
+ for (int i = 0; i < 5; i++)
+ {
+ qstick.Update(new TBar(time.AddMinutes(i).Ticks, 100.0, 105.0, 90.0, 95.0, 1000));
+ }
+
+ Assert.True(qstick.Last.Value < 0);
+ Assert.Equal(-5.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void DojiBars_ProduceZeroQstick()
+ {
+ var qstick = new Qstick(5);
+ var time = DateTime.UtcNow;
+
+ // All doji bars (close = open)
+ for (int i = 0; i < 5; i++)
+ {
+ qstick.Update(new TBar(time.AddMinutes(i).Ticks, 100.0, 105.0, 95.0, 100.0, 1000));
+ }
+
+ Assert.Equal(0.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void CoreIndicator_ResetsCorrectly()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+
+ Assert.NotEqual(default, qstick.Last);
+
+ qstick.Reset();
+
+ Assert.False(qstick.IsHot);
+ Assert.Equal(default, qstick.Last);
+ }
+}
diff --git a/lib/dynamics/qstick/Qstick.Quantower.cs b/lib/dynamics/qstick/Qstick.Quantower.cs
new file mode 100644
index 00000000..eb610beb
--- /dev/null
+++ b/lib/dynamics/qstick/Qstick.Quantower.cs
@@ -0,0 +1,58 @@
+using System.Drawing;
+using System.Runtime.CompilerServices;
+using TradingPlatform.BusinessLayer;
+
+namespace QuanTAlib;
+
+[SkipLocalsInit]
+public sealed class QstickIndicator : Indicator, IWatchlistIndicator
+{
+ [InputParameter("Period", sortIndex: 0, 1, 1000, 1, 0)]
+ public int Period { get; set; } = 14;
+
+ [InputParameter("MA Type", sortIndex: 1, variants: new object[] { "SMA", "SMA", "EMA", "EMA" })]
+ public string MaType { get; set; } = "SMA";
+
+ [InputParameter("Show cold values", sortIndex: 21)]
+ public bool ShowColdValues { get; set; } = true;
+
+ public override string ShortName => $"QSTICK({Period},{MaType})";
+
+ public static int MinHistoryDepths => 0;
+ int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
+
+ private Qstick _indicator = null!;
+ private readonly LineSeries _series;
+
+ public QstickIndicator()
+ {
+ Name = "Qstick Indicator";
+ Description = "Measures average candlestick body direction by calculating the moving average of close minus open.";
+ _series = new LineSeries("Qstick", Color.Yellow, 2, LineStyle.Solid);
+ AddLineSeries(_series);
+ SeparateWindow = true;
+ }
+
+ protected override void OnInit()
+ {
+ bool useEma = string.Equals(MaType, "EMA", StringComparison.Ordinal);
+ _indicator = new Qstick(Period, useEma);
+ AddLineLevel(0, "Zero", Color.Gray, 1, LineStyle.Dash);
+ base.OnInit();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void OnUpdate(UpdateArgs args)
+ {
+ bool isNew = args.IsNewBar();
+ var bar = this.GetInputBar(args);
+ var result = _indicator.Update(bar, isNew);
+ _series.SetValue(result.Value, _indicator.IsHot, ShowColdValues);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void OnPaintChart(PaintChartEventArgs args)
+ {
+ base.OnPaintChart(args);
+ }
+}
diff --git a/lib/dynamics/qstick/Qstick.Tests.cs b/lib/dynamics/qstick/Qstick.Tests.cs
new file mode 100644
index 00000000..8072e4ab
--- /dev/null
+++ b/lib/dynamics/qstick/Qstick.Tests.cs
@@ -0,0 +1,557 @@
+using Xunit;
+
+namespace QuanTAlib.Tests;
+
+public class QstickTests
+{
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Constructor Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Constructor_DefaultParameters_CreatesValidIndicator()
+ {
+ var qstick = new Qstick();
+ Assert.Equal(14, qstick.Period);
+ Assert.False(qstick.UseEma);
+ Assert.Equal("QSTICK(14)", qstick.Name);
+ }
+
+ [Fact]
+ public void Constructor_CustomPeriod_SetsCorrectly()
+ {
+ var qstick = new Qstick(20);
+ Assert.Equal(20, qstick.Period);
+ }
+
+ [Fact]
+ public void Constructor_EmaMode_SetsCorrectly()
+ {
+ var qstick = new Qstick(14, useEma: true);
+ Assert.True(qstick.UseEma);
+ Assert.Equal("QSTICK(14,EMA)", qstick.Name);
+ }
+
+ [Fact]
+ public void Constructor_PeriodLessThanOne_ThrowsException()
+ {
+ Assert.Throws(() => new Qstick(0));
+ }
+
+ [Fact]
+ public void Constructor_NegativePeriod_ThrowsException()
+ {
+ Assert.Throws(() => new Qstick(-1));
+ }
+
+ [Fact]
+ public void Constructor_PeriodOne_IsValid()
+ {
+ var qstick = new Qstick(1);
+ Assert.Equal(1, qstick.Period);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Basic Calculation Tests - SMA Mode
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Update_SingleBullishBar_ReturnsPositiveValue()
+ {
+ var qstick = new Qstick(1);
+ var bar = new TBar(DateTime.UtcNow.Ticks, 100.0, 105.0, 99.0, 105.0, 1000);
+ var result = qstick.Update(bar);
+ Assert.Equal(5.0, result.Value); // close - open = 105 - 100 = 5
+ }
+
+ [Fact]
+ public void Update_SingleBearishBar_ReturnsNegativeValue()
+ {
+ var qstick = new Qstick(1);
+ var bar = new TBar(DateTime.UtcNow.Ticks, 105.0, 105.0, 99.0, 100.0, 1000);
+ var result = qstick.Update(bar);
+ Assert.Equal(-5.0, result.Value); // close - open = 100 - 105 = -5
+ }
+
+ [Fact]
+ public void Update_DojiBar_ReturnsZero()
+ {
+ var qstick = new Qstick(1);
+ var bar = new TBar(DateTime.UtcNow.Ticks, 100.0, 105.0, 99.0, 100.0, 1000);
+ var result = qstick.Update(bar);
+ Assert.Equal(0.0, result.Value); // close - open = 100 - 100 = 0
+ }
+
+ [Fact]
+ public void Update_ThreeBars_CalculatesCorrectSMA()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ // Bar 1: +5 (bullish)
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+
+ // Bar 2: -3 (bearish)
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 97.0, 1000));
+
+ // Bar 3: +2 (bullish)
+ var result = qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 105.0, 95.0, 102.0, 1000));
+
+ // SMA = (5 + -3 + 2) / 3 = 4/3 ≈ 1.333
+ Assert.Equal(4.0 / 3.0, result.Value, 10);
+ Assert.True(qstick.IsHot);
+ }
+
+ [Fact]
+ public void Update_NotWarmUp_ReturnsPartialAverage()
+ {
+ var qstick = new Qstick(5);
+ var time = DateTime.UtcNow;
+
+ // Bar 1: +5
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+
+ // Bar 2: +3
+ var result = qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+
+ // Partial average = (5 + 3) / 2 = 4
+ Assert.Equal(4.0, result.Value, 10);
+ Assert.False(qstick.IsHot);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Basic Calculation Tests - EMA Mode
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Update_EmaMode_FirstBar_ReturnsDiff()
+ {
+ var qstick = new Qstick(14, useEma: true);
+ var bar = new TBar(DateTime.UtcNow.Ticks, 100.0, 105.0, 99.0, 105.0, 1000);
+ var result = qstick.Update(bar);
+ Assert.Equal(5.0, result.Value);
+ }
+
+ [Fact]
+ public void Update_EmaMode_MultipleBar_CalculatesEma()
+ {
+ var qstick = new Qstick(3, useEma: true); // alpha = 2/(3+1) = 0.5
+ var time = DateTime.UtcNow;
+
+ // Bar 1: diff = 5
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+
+ // Bar 2: diff = -3, EMA = 0.5 * -3 + 0.5 * 5 = 1.0
+ var result = qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 97.0, 1000));
+
+ Assert.Equal(1.0, result.Value, 10);
+ }
+
+ [Fact]
+ public void Update_EmaMode_IsHotFromFirstBar()
+ {
+ var qstick = new Qstick(14, useEma: true);
+ var bar = new TBar(DateTime.UtcNow.Ticks, 100.0, 105.0, 99.0, 105.0, 1000);
+ qstick.Update(bar);
+ Assert.True(qstick.IsHot);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Bar Correction Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Update_IsNewFalse_CorrectsPreviousBar()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ // Bar 1
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+
+ // Bar 2
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+
+ // Bar 3 initial
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 105.0, 95.0, 102.0, 1000));
+
+ // Bar 3 correction (close changes from 102 to 108)
+ var result = qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 110.0, 95.0, 108.0, 1000), isNew: false);
+
+ // SMA = (5 + 3 + 8) / 3 = 16/3 ≈ 5.333
+ Assert.Equal(16.0 / 3.0, result.Value, 10);
+ }
+
+ [Fact]
+ public void Update_MultipleCorrections_ProducesSameResult()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 105.0, 95.0, 102.0, 1000));
+
+ // Multiple corrections to same bar should be idempotent
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 110.0, 95.0, 107.0, 1000), isNew: false);
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 110.0, 95.0, 107.0, 1000), isNew: false);
+ var result = qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 110.0, 95.0, 107.0, 1000), isNew: false);
+
+ // SMA = (5 + 3 + 7) / 3 = 15/3 = 5
+ Assert.Equal(5.0, result.Value, 10);
+ }
+
+ [Fact]
+ public void Update_EmaMode_BarCorrection_Works()
+ {
+ var qstick = new Qstick(3, useEma: true); // alpha = 0.5
+ var time = DateTime.UtcNow;
+
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000)); // diff=5, ema=5
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000)); // diff=3, ema=0.5*3+0.5*5=4
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 105.0, 95.0, 102.0, 1000)); // diff=2, ema=0.5*2+0.5*4=3
+
+ // Correct bar 3: diff changes from 2 to 8
+ var result = qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 110.0, 95.0, 108.0, 1000), isNew: false);
+
+ // ema = 0.5*8 + 0.5*4 = 6
+ Assert.Equal(6.0, result.Value, 10);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Edge Case Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Update_NaNOpen_ReturnsLastValue()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ var bar1 = new TBar(time.Ticks, 100.0, 105.0, 99.0, 105.0, 1000);
+ var result1 = qstick.Update(bar1);
+
+ var bar2 = new TBar(time.AddMinutes(1).Ticks, double.NaN, 105.0, 99.0, 105.0, 1000);
+ var result2 = qstick.Update(bar2);
+
+ Assert.Equal(result1.Value, result2.Value);
+ }
+
+ [Fact]
+ public void Update_NaNClose_ReturnsLastValue()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ var bar1 = new TBar(time.Ticks, 100.0, 105.0, 99.0, 105.0, 1000);
+ var result1 = qstick.Update(bar1);
+
+ var bar2 = new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 99.0, double.NaN, 1000);
+ var result2 = qstick.Update(bar2);
+
+ Assert.Equal(result1.Value, result2.Value);
+ }
+
+ [Fact]
+ public void Update_InfinityInput_ReturnsLastValue()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ var bar1 = new TBar(time.Ticks, 100.0, 105.0, 99.0, 105.0, 1000);
+ var result1 = qstick.Update(bar1);
+
+ var bar2 = new TBar(time.AddMinutes(1).Ticks, double.PositiveInfinity, 105.0, 99.0, 105.0, 1000);
+ var result2 = qstick.Update(bar2);
+
+ Assert.Equal(result1.Value, result2.Value);
+ }
+
+ [Fact]
+ public void Update_LargeValues_CalculatesCorrectly()
+ {
+ var qstick = new Qstick(1);
+ var bar = new TBar(DateTime.UtcNow.Ticks, 1e10, 1.1e10, 0.9e10, 1.05e10, 1000);
+ var result = qstick.Update(bar);
+ Assert.Equal(0.05e10, result.Value, 1);
+ }
+
+ [Fact]
+ public void Update_SmallValues_CalculatesCorrectly()
+ {
+ var qstick = new Qstick(1);
+ var bar = new TBar(DateTime.UtcNow.Ticks, 0.0001, 0.00015, 0.00009, 0.00012, 1000);
+ var result = qstick.Update(bar);
+ Assert.Equal(0.00002, result.Value, 10);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Reset Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Reset_ClearsState()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 105.0, 95.0, 102.0, 1000));
+
+ Assert.True(qstick.IsHot);
+
+ qstick.Reset();
+
+ Assert.False(qstick.IsHot);
+ Assert.Equal(default, qstick.Last);
+ }
+
+ [Fact]
+ public void Reset_EmaMode_ClearsState()
+ {
+ var qstick = new Qstick(3, useEma: true);
+ var time = DateTime.UtcNow;
+
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+
+ Assert.True(qstick.IsHot);
+
+ qstick.Reset();
+
+ Assert.False(qstick.IsHot);
+ }
+
+ [Fact]
+ public void Reset_CanReuseAfterReset()
+ {
+ var qstick = new Qstick(2);
+ var time = DateTime.UtcNow;
+
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+
+ qstick.Reset();
+
+ // New data after reset
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 105.0, 95.0, 98.0, 1000)); // diff = -2
+ var result = qstick.Update(new TBar(time.AddMinutes(3).Ticks, 100.0, 105.0, 95.0, 106.0, 1000)); // diff = 6
+
+ // SMA = (-2 + 6) / 2 = 2
+ Assert.Equal(2.0, result.Value, 10);
+ Assert.True(qstick.IsHot);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Batch Processing Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Update_BarSeries_ReturnsCorrectLength()
+ {
+ var qstick = new Qstick(3);
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ bars.Add(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ bars.Add(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+ bars.Add(new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 106.0, 1000));
+
+ var result = qstick.Update(bars);
+
+ Assert.Equal(3, result.Count);
+ }
+
+ [Fact]
+ public void Update_BarSeries_LastValueMatchesLast()
+ {
+ var qstick = new Qstick(3);
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ bars.Add(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ bars.Add(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+ bars.Add(new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 106.0, 1000));
+
+ var result = qstick.Update(bars);
+
+ Assert.Equal(qstick.Last.Value, result.Values[^1], 10);
+ }
+
+ [Fact]
+ public void Update_EmptyBarSeries_ReturnsEmpty()
+ {
+ var qstick = new Qstick(3);
+ var bars = new TBarSeries();
+
+ var result = qstick.Update(bars);
+
+ Assert.True(result.Count == 0);
+ }
+
+ [Fact]
+ public void Batch_ReturnsCorrectResults()
+ {
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ bars.Add(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000)); // diff = 5
+ bars.Add(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000)); // diff = 3
+ bars.Add(new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 106.0, 1000)); // diff = 6
+
+ var result = Qstick.Batch(bars, period: 3);
+
+ // Last value = SMA(5, 3, 6) = 14/3 ≈ 4.667
+ Assert.Equal(14.0 / 3.0, result.Values[^1], 10);
+ }
+
+ [Fact]
+ public void Calculate_ReturnsIndicatorAndResults()
+ {
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ bars.Add(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ bars.Add(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+ bars.Add(new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 106.0, 1000));
+
+ var (results, indicator) = Qstick.Calculate(bars, period: 3);
+
+ Assert.Equal(3, results.Count);
+ Assert.True(indicator.IsHot);
+ Assert.Equal(3, indicator.Period);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Prime Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Prime_WarmUpIndicator()
+ {
+ var qstick = new Qstick(3);
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ bars.Add(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000));
+ bars.Add(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 95.0, 103.0, 1000));
+ bars.Add(new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 106.0, 1000));
+
+ qstick.Prime(bars);
+
+ Assert.True(qstick.IsHot);
+ Assert.NotEqual(default, qstick.Last);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Event Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Update_RaisesPubEvent()
+ {
+ var qstick = new Qstick(3);
+ var eventRaised = false;
+ TValue receivedValue = default;
+
+ qstick.Pub += (object? sender, in TValueEventArgs args) =>
+ {
+ eventRaised = true;
+ receivedValue = args.Value;
+ };
+
+ var bar = new TBar(DateTime.UtcNow.Ticks, 100.0, 110.0, 95.0, 105.0, 1000);
+ var result = qstick.Update(bar);
+
+ Assert.True(eventRaised);
+ Assert.Equal(result.Value, receivedValue.Value);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Property Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void WarmupPeriod_EqualsPeriod()
+ {
+ var qstick = new Qstick(20);
+ Assert.Equal(20, qstick.WarmupPeriod);
+ }
+
+ [Fact]
+ public void IsHot_SmaMode_FalseBeforeFullPeriod()
+ {
+ var qstick = new Qstick(5);
+ var time = DateTime.UtcNow;
+
+ for (int i = 0; i < 4; i++)
+ {
+ qstick.Update(new TBar(time.AddMinutes(i).Ticks, 100.0, 105.0, 95.0, 102.0, 1000));
+ Assert.False(qstick.IsHot);
+ }
+
+ qstick.Update(new TBar(time.AddMinutes(4).Ticks, 100.0, 105.0, 95.0, 102.0, 1000));
+ Assert.True(qstick.IsHot);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Consistency Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Update_BatchVsStreaming_ProducesSameResults()
+ {
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ for (int i = 0; i < 20; i++)
+ {
+ double open = 100.0 + i * 0.5;
+ double close = open + (i % 3 - 1); // varies between -1, 0, 1
+ bars.Add(new TBar(time.AddMinutes(i).Ticks, open, open + 2, open - 1, close, 1000));
+ }
+
+ // Batch processing
+ var batchQstick = new Qstick(5);
+ var batchResult = batchQstick.Update(bars);
+
+ // Streaming processing
+ var streamQstick = new Qstick(5);
+ var streamResults = new List();
+ for (int i = 0; i < bars.Count; i++)
+ {
+ var result = streamQstick.Update(bars[i]);
+ streamResults.Add(result.Value);
+ }
+
+ // Compare results
+ for (int i = 0; i < bars.Count; i++)
+ {
+ Assert.Equal(batchResult.Values[i], streamResults[i], 10);
+ }
+ }
+
+ [Fact]
+ public void SmaVsEma_DifferentResults()
+ {
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ for (int i = 0; i < 10; i++)
+ {
+ double open = 100.0;
+ double close = 100.0 + (i % 2 == 0 ? 5 : -3);
+ bars.Add(new TBar(time.AddMinutes(i).Ticks, open, 110.0, 95.0, close, 1000));
+ }
+
+ var smaQstick = new Qstick(5, useEma: false);
+ var emaQstick = new Qstick(5, useEma: true);
+
+ var smaResult = smaQstick.Update(bars);
+ var emaResult = emaQstick.Update(bars);
+
+ // SMA and EMA should produce different results (EMA weights more recent)
+ Assert.NotEqual(smaResult.Values[^1], emaResult.Values[^1]);
+ }
+}
diff --git a/lib/dynamics/qstick/Qstick.Validation.Tests.cs b/lib/dynamics/qstick/Qstick.Validation.Tests.cs
new file mode 100644
index 00000000..8d2855c0
--- /dev/null
+++ b/lib/dynamics/qstick/Qstick.Validation.Tests.cs
@@ -0,0 +1,353 @@
+using Xunit;
+
+namespace QuanTAlib.Tests;
+
+///
+/// Validation tests for Qstick indicator.
+/// Validates against manual formula calculations since Qstick is not
+/// available in TA-Lib, Skender, Tulip, or Ooples.
+///
+public sealed class QstickValidationTests : IDisposable
+{
+ private readonly ValidationTestData _data;
+
+ public QstickValidationTests()
+ {
+ _data = new ValidationTestData();
+ }
+
+ public void Dispose()
+ {
+ _data.Dispose();
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Mathematical Correctness Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void ManualCalculation_MatchesFormula()
+ {
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ // Create known bars
+ bars.Add(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000)); // diff = 5
+ bars.Add(new TBar(time.AddMinutes(1).Ticks, 100.0, 108.0, 92.0, 97.0, 1000)); // diff = -3
+ bars.Add(new TBar(time.AddMinutes(2).Ticks, 100.0, 106.0, 94.0, 104.0, 1000)); // diff = 4
+ bars.Add(new TBar(time.AddMinutes(3).Ticks, 100.0, 107.0, 93.0, 98.0, 1000)); // diff = -2
+ bars.Add(new TBar(time.AddMinutes(4).Ticks, 100.0, 109.0, 91.0, 106.0, 1000)); // diff = 6
+
+ var qstick = new Qstick(5);
+ for (int i = 0; i < bars.Count; i++)
+ {
+ qstick.Update(bars[i]);
+ }
+
+ // Expected: SMA of (5, -3, 4, -2, 6) = 10/5 = 2.0
+ Assert.Equal(2.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void ManualCalculation_Period3()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ // Bar 1: diff = 8
+ qstick.Update(new TBar(time.Ticks, 100.0, 115.0, 95.0, 108.0, 1000));
+
+ // Bar 2: diff = -4
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 102.0, 90.0, 96.0, 1000));
+
+ // Bar 3: diff = 6
+ var result = qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 110.0, 95.0, 106.0, 1000));
+
+ // Expected: SMA(8, -4, 6) = 10/3 ≈ 3.333
+ Assert.Equal(10.0 / 3.0, result.Value, 10);
+ }
+
+ [Fact]
+ public void ManualCalculation_Period7()
+ {
+ var qstick = new Qstick(7);
+ var time = DateTime.UtcNow;
+
+ double[] diffs = { 5, -3, 4, -2, 6, -1, 3 };
+
+ for (int i = 0; i < diffs.Length; i++)
+ {
+ double open = 100.0;
+ double close = 100.0 + diffs[i];
+ qstick.Update(new TBar(time.AddMinutes(i).Ticks, open, 110.0, 90.0, close, 1000));
+ }
+
+ // Expected: SMA of 5, -3, 4, -2, 6, -1, 3 = 12/7 ≈ 1.714
+ double expectedSum = 5 - 3 + 4 - 2 + 6 - 1 + 3; // = 12
+ Assert.Equal(expectedSum / 7.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void EmaCalculation_MatchesFormula()
+ {
+ var qstick = new Qstick(3, useEma: true); // alpha = 2/(3+1) = 0.5
+ var time = DateTime.UtcNow;
+
+ // Bar 1: diff = 10
+ qstick.Update(new TBar(time.Ticks, 100.0, 115.0, 95.0, 110.0, 1000));
+ Assert.Equal(10.0, qstick.Last.Value, 10);
+
+ // Bar 2: diff = -6, EMA = 0.5 * -6 + 0.5 * 10 = 2.0
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 102.0, 90.0, 94.0, 1000));
+ Assert.Equal(2.0, qstick.Last.Value, 10);
+
+ // Bar 3: diff = 4, EMA = 0.5 * 4 + 0.5 * 2 = 3.0
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 108.0, 95.0, 104.0, 1000));
+ Assert.Equal(3.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void EmaCalculation_Period5()
+ {
+ var qstick = new Qstick(5, useEma: true); // alpha = 2/(5+1) = 1/3
+ var time = DateTime.UtcNow;
+ double alpha = 2.0 / 6.0;
+
+ // Bar 1: diff = 6
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 106.0, 1000));
+ double expectedEma = 6.0;
+ Assert.Equal(expectedEma, qstick.Last.Value, 10);
+
+ // Bar 2: diff = 3, EMA = alpha * 3 + (1-alpha) * 6-
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 108.0, 96.0, 103.0, 1000));
+ expectedEma = alpha * 3 + (1 - alpha) * expectedEma;
+ Assert.Equal(expectedEma, qstick.Last.Value, 10);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Edge Case Validation
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void ZeroCrossing_IdentifiesCorrectly()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ // Start bullish
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 106.0, 1000)); // +6
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 108.0, 92.0, 104.0, 1000)); // +4
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 106.0, 94.0, 102.0, 1000)); // +2
+
+ Assert.True(qstick.Last.Value > 0);
+
+ // Shift to bearish
+ qstick.Update(new TBar(time.AddMinutes(3).Ticks, 100.0, 105.0, 90.0, 92.0, 1000)); // -8
+ qstick.Update(new TBar(time.AddMinutes(4).Ticks, 100.0, 104.0, 88.0, 90.0, 1000)); // -10
+ qstick.Update(new TBar(time.AddMinutes(5).Ticks, 100.0, 103.0, 86.0, 88.0, 1000)); // -12
+
+ Assert.True(qstick.Last.Value < 0);
+ }
+
+ [Fact]
+ public void LargeGaps_HandledCorrectly()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ // Gap up scenario - previous close has no effect on body calculation
+ qstick.Update(new TBar(time.Ticks, 100.0, 105.0, 95.0, 103.0, 1000)); // diff = 3
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 110.0, 118.0, 108.0, 115.0, 1000)); // diff = 5 (gap up)
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 120.0, 125.0, 118.0, 122.0, 1000)); // diff = 2 (gap up)
+
+ // SMA = (3 + 5 + 2) / 3 = 10/3 ≈ 3.333
+ Assert.Equal(10.0 / 3.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void AlternatingBullishBearish_AveragesToNearZero()
+ {
+ var qstick = new Qstick(4);
+ var time = DateTime.UtcNow;
+
+ // Alternating pattern
+ qstick.Update(new TBar(time.Ticks, 100.0, 110.0, 95.0, 105.0, 1000)); // +5
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 105.0, 90.0, 95.0, 1000)); // -5
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 110.0, 95.0, 105.0, 1000)); // +5
+ qstick.Update(new TBar(time.AddMinutes(3).Ticks, 100.0, 105.0, 90.0, 95.0, 1000)); // -5
+
+ // SMA = (5 - 5 + 5 - 5) / 4 = 0
+ Assert.Equal(0.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void AllDoji_ReturnsZero()
+ {
+ var qstick = new Qstick(5);
+ var time = DateTime.UtcNow;
+
+ for (int i = 0; i < 5; i++)
+ {
+ qstick.Update(new TBar(time.AddMinutes(i).Ticks, 100.0, 105.0, 95.0, 100.0, 1000)); // diff = 0
+ }
+
+ Assert.Equal(0.0, qstick.Last.Value, 10);
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Stability Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void LongSeries_MaintainsStability()
+ {
+ var qstick = new Qstick(14);
+ var results = new List();
+
+ for (int i = 0; i < _data.Bars.Count; i++)
+ {
+ var result = qstick.Update(_data.Bars[i]);
+ results.Add(result.Value);
+ }
+
+ // Verify no NaN or Infinity after warmup
+ for (int i = 14; i < results.Count; i++)
+ {
+ Assert.True(double.IsFinite(results[i]), $"Result at index {i} is not finite: {results[i]}");
+ }
+ }
+
+ [Fact]
+ public void BatchVsStreaming_MatchesExactly()
+ {
+ // Batch processing
+ var batchResults = Qstick.Batch(_data.Bars, period: 14);
+
+ // Streaming processing
+ var streamQstick = new Qstick(14);
+ var streamResults = new List();
+ for (int i = 0; i < _data.Bars.Count; i++)
+ {
+ var result = streamQstick.Update(_data.Bars[i]);
+ streamResults.Add(result.Value);
+ }
+
+ // Compare
+ Assert.Equal(batchResults.Count, streamResults.Count);
+ for (int i = 0; i < batchResults.Count; i++)
+ {
+ Assert.Equal(batchResults.Values[i], streamResults[i], 12);
+ }
+ }
+
+ [Fact]
+ public void SmaVsEma_ConvergesOverLongPeriod()
+ {
+ // With constant input, SMA and EMA should converge
+ var smaQstick = new Qstick(10, useEma: false);
+ var emaQstick = new Qstick(10, useEma: true);
+ var time = DateTime.UtcNow;
+
+ // Feed constant bars (close - open = 5)
+ for (int i = 0; i < 100; i++)
+ {
+ var bar = new TBar(time.AddMinutes(i).Ticks, 100.0, 110.0, 95.0, 105.0, 1000);
+ smaQstick.Update(bar);
+ emaQstick.Update(bar);
+ }
+
+ // Both should converge to 5.0 with constant input
+ Assert.Equal(5.0, smaQstick.Last.Value, 10);
+ Assert.Equal(5.0, emaQstick.Last.Value, 4); // EMA converges slower
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Period Boundary Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void Period1_ReturnsDiffDirectly()
+ {
+ var qstick = new Qstick(1);
+ var time = DateTime.UtcNow;
+
+ var bar = new TBar(time.Ticks, 100.0, 110.0, 95.0, 107.0, 1000);
+ var result = qstick.Update(bar);
+
+ Assert.Equal(7.0, result.Value, 10); // close - open = 107 - 100 = 7
+ }
+
+ [Fact]
+ public void LargePeriod_CalculatesCorrectly()
+ {
+ var qstick = new Qstick(50);
+ var results = new List();
+
+ for (int i = 0; i < _data.Bars.Count; i++)
+ {
+ var result = qstick.Update(_data.Bars[i]);
+ results.Add(result.Value);
+ }
+
+ // Verify indicator is hot after warmup
+ Assert.True(qstick.IsHot);
+
+ // Verify values are finite after warmup
+ for (int i = 50; i < results.Count; i++)
+ {
+ Assert.True(double.IsFinite(results[i]), $"Result at index {i} is not finite");
+ }
+ }
+
+ // ═══════════════════════════════════════════════════════════════════════════
+ // Rolling Window Tests
+ // ═══════════════════════════════════════════════════════════════════════════
+
+ [Fact]
+ public void RollingWindow_DropsOldestValue()
+ {
+ var qstick = new Qstick(3);
+ var time = DateTime.UtcNow;
+
+ // Fill window: +10, +10, +10
+ qstick.Update(new TBar(time.Ticks, 100.0, 115.0, 95.0, 110.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(1).Ticks, 100.0, 115.0, 95.0, 110.0, 1000));
+ qstick.Update(new TBar(time.AddMinutes(2).Ticks, 100.0, 115.0, 95.0, 110.0, 1000));
+
+ Assert.Equal(10.0, qstick.Last.Value, 10);
+
+ // Add -20 (replaces oldest +10)
+ qstick.Update(new TBar(time.AddMinutes(3).Ticks, 100.0, 105.0, 75.0, 80.0, 1000));
+
+ // Window is now: +10, +10, -20 → SMA = 0/3 = 0
+ Assert.Equal(0.0, qstick.Last.Value, 10);
+ }
+
+ [Fact]
+ public void RollingWindow_MaintainsCorrectSum()
+ {
+ var qstick = new Qstick(5);
+ var time = DateTime.UtcNow;
+
+ // Create predictable pattern
+ double[] diffs = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+
+ for (int i = 0; i < diffs.Length; i++)
+ {
+ double open = 100.0;
+ double close = 100.0 + diffs[i];
+ qstick.Update(new TBar(time.AddMinutes(i).Ticks, open, 110.0, 90.0, close, 1000));
+
+ if (i >= 4) // After warmup
+ {
+ // Expected: SMA of last 5 values
+ double expectedSum = 0;
+ for (int j = i - 4; j <= i; j++)
+ {
+ expectedSum += diffs[j];
+ }
+ Assert.Equal(expectedSum / 5.0, qstick.Last.Value, 10);
+ }
+ }
+ }
+}
diff --git a/lib/dynamics/qstick/Qstick.cs b/lib/dynamics/qstick/Qstick.cs
new file mode 100644
index 00000000..fcf291a5
--- /dev/null
+++ b/lib/dynamics/qstick/Qstick.cs
@@ -0,0 +1,257 @@
+// QSTICK: Qstick Indicator by Tushar Chande
+// Measures average candlestick body: MA(Close - Open)
+// Positive = bullish (closes above opens), Negative = bearish
+
+using System.Runtime.CompilerServices;
+
+namespace QuanTAlib;
+
+///
+/// Qstick (QSTICK) - Candlestick Momentum Indicator
+/// A moving average of the difference between Close and Open prices,
+/// measuring the average direction and strength of candlestick bodies.
+///
+/// Calculation: Qstick = MA(Close - Open, period)
+///
+///
+/// Calculation:
+///
+/// diff = Close - Open
+/// Qstick = SMA(diff, period) or EMA(diff, period)
+///
+///
+/// Key characteristics:
+/// - O(1) update complexity per bar
+/// - Supports SMA or EMA averaging modes
+/// - Positive = average bullish bars
+/// - Negative = average bearish bars
+/// - Uses RingBuffer for SMA (handles isNew internally)
+/// - Uses state rollback for EMA bar correction support
+///
+/// Detailed documentation
+[SkipLocalsInit]
+public sealed class Qstick : ITValuePublisher
+{
+ private const int DefaultPeriod = 14;
+ private const bool DefaultUseEma = false;
+
+ private readonly int _period;
+ private readonly bool _useEma;
+ private readonly double _alpha;
+ private readonly RingBuffer _buffer;
+
+ // State for bar correction (EMA mode only)
+ private double _emaValue;
+ private double _savedEmaValue;
+ private int _count;
+ private int _savedCount;
+
+ ///
+ /// Display name for the indicator.
+ ///
+ public string Name { get; }
+
+ public event TValuePublishedHandler? Pub;
+
+ ///
+ /// Current Qstick value.
+ ///
+ public TValue Last { get; private set; }
+
+ ///
+ /// True when the indicator has calculated a valid value.
+ /// For SMA: after receiving 'period' bars
+ /// For EMA: after receiving at least 1 bar (with bias compensation approximation)
+ ///
+ public bool IsHot => _useEma ? _count > 0 : _buffer.IsFull;
+
+ ///
+ /// The lookback period parameter.
+ ///
+ public int Period => _period;
+
+ ///
+ /// Whether the indicator uses EMA (true) or SMA (false).
+ ///
+ public bool UseEma => _useEma;
+
+ ///
+ /// The number of bars required for the indicator to warm up.
+ ///
+ public int WarmupPeriod { get; }
+
+ ///
+ /// Creates a Qstick indicator with specified period.
+ ///
+ /// Lookback period (must be >= 1)
+ /// Use EMA (true) or SMA (false)
+ public Qstick(int period = DefaultPeriod, bool useEma = DefaultUseEma)
+ {
+ if (period < 1)
+ {
+ throw new ArgumentException("Period must be at least 1", nameof(period));
+ }
+
+ _period = period;
+ _useEma = useEma;
+ _alpha = 2.0 / (period + 1);
+ Name = useEma ? $"QSTICK({period},EMA)" : $"QSTICK({period})";
+ WarmupPeriod = period;
+
+ if (!useEma)
+ {
+ _buffer = new RingBuffer(period);
+ }
+ else
+ {
+ _buffer = null!;
+ }
+
+ _emaValue = 0;
+ _savedEmaValue = 0;
+ _count = 0;
+ _savedCount = 0;
+ }
+
+ ///
+ /// Resets the indicator state.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Reset()
+ {
+ _buffer?.Clear();
+ _emaValue = 0;
+ _savedEmaValue = 0;
+ _count = 0;
+ _savedCount = 0;
+ Last = default;
+ }
+
+ ///
+ /// Updates the Qstick indicator with a new bar.
+ ///
+ /// The price bar (Open and Close required)
+ /// True for new bar, false for update of current bar
+ /// The current Qstick value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TValue Update(TBar input, bool isNew = true)
+ {
+ double open = input.Open;
+ double close = input.Close;
+
+ // Handle NaN/Infinity inputs
+ if (!double.IsFinite(open) || !double.IsFinite(close))
+ {
+ Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
+ return Last;
+ }
+
+ double diff = close - open;
+
+ double result;
+ if (_useEma)
+ {
+ if (isNew)
+ {
+ _savedEmaValue = _emaValue;
+ _savedCount = _count;
+ }
+ else
+ {
+ _emaValue = _savedEmaValue;
+ _count = _savedCount;
+ }
+
+ // EMA calculation
+ if (_count == 0)
+ {
+ _emaValue = diff;
+ }
+ else
+ {
+ _emaValue = Math.FusedMultiplyAdd(_alpha, diff - _emaValue, _emaValue);
+ }
+
+ if (isNew)
+ {
+ _count++;
+ }
+
+ result = _emaValue;
+ }
+ else
+ {
+ // SMA calculation using RingBuffer
+ // RingBuffer.Add handles isNew internally:
+ // - isNew=true: adds new value, removes oldest if full
+ // - isNew=false: replaces newest value
+ // RingBuffer.Sum is always accurate after Add
+ _buffer.Add(diff, isNew);
+
+ int count = _buffer.Count;
+ result = count > 0 ? _buffer.Sum / count : double.NaN;
+ }
+
+ Last = new TValue(input.Time, result);
+ Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
+ return Last;
+ }
+
+ ///
+ /// Updates with a bar series.
+ ///
+ public TSeries Update(TBarSeries source)
+ {
+ if (source.Count == 0)
+ {
+ return new TSeries([], []);
+ }
+
+ int len = source.Count;
+ var tList = new List(len);
+ var vList = new List(len);
+
+ var times = source.Open.Times;
+ for (int i = 0; i < len; i++)
+ {
+ var result = Update(source[i], isNew: true);
+ tList.Add(times[i]);
+ vList.Add(result.Value);
+ }
+
+ return new TSeries(tList, vList);
+ }
+
+ ///
+ /// Primes the indicator with historical bar data.
+ ///
+ public void Prime(TBarSeries source)
+ {
+ for (int i = 0; i < source.Count; i++)
+ {
+ Update(source[i], isNew: true);
+ }
+ }
+
+ ///
+ /// Creates and returns results for a bar series.
+ ///
+ public static TSeries Batch(TBarSeries source, int period = DefaultPeriod, bool useEma = DefaultUseEma)
+ {
+ var indicator = new Qstick(period, useEma);
+ return indicator.Update(source);
+ }
+
+ ///
+ /// Returns the indicator and its results.
+ ///
+ public static (TSeries Results, Qstick Indicator) Calculate(
+ TBarSeries source,
+ int period = DefaultPeriod,
+ bool useEma = DefaultUseEma)
+ {
+ var indicator = new Qstick(period, useEma);
+ var results = indicator.Update(source);
+ return (results, indicator);
+ }
+}
diff --git a/lib/dynamics/qstick/Qstick.md b/lib/dynamics/qstick/Qstick.md
new file mode 100644
index 00000000..147a7052
--- /dev/null
+++ b/lib/dynamics/qstick/Qstick.md
@@ -0,0 +1,143 @@
+# QSTICK: Qstick Indicator
+
+> "The average candlestick body reveals the market's true conviction."
+
+Developed by Tushar Chande, the Qstick indicator measures the average difference between closing and opening prices over a lookback period. It quantifies whether bars are predominantly bullish (closing above opens) or bearish (closing below opens), providing a smoothed view of candlestick body direction and magnitude.
+
+## Historical Context
+
+Tushar Chande introduced the Qstick as part of his work on candlestick pattern quantification in the early 1990s. While traditional candlestick analysis relies on visual pattern recognition, Qstick provides a numerical measure that can be systematically tracked and used for algorithmic trading.
+
+The indicator addresses a fundamental question: "On average, are prices closing higher or lower than they open?" This simple metric captures intrabar momentum that other indicators measuring close-to-close changes may miss.
+
+## Architecture
+
+### 1. Body Difference Calculation
+
+The core input is the difference between close and open:
+
+```
+diff = Close - Open
+```
+
+- **Positive diff**: Bullish bar (white/green candle)
+- **Negative diff**: Bearish bar (black/red candle)
+- **Zero diff**: Doji (open equals close)
+
+### 2. Moving Average Smoothing
+
+The raw differences are smoothed using either SMA or EMA:
+
+**SMA Mode:**
+$$\text{Qstick} = \frac{1}{n} \sum_{i=0}^{n-1} (Close_i - Open_i)$$
+
+**EMA Mode:**
+$$\text{Qstick}_t = \alpha \cdot diff_t + (1 - \alpha) \cdot \text{Qstick}_{t-1}$$
+
+where $\alpha = \frac{2}{period + 1}$
+
+### 3. State Management
+
+For real-time bar correction (isNew=false), the indicator maintains:
+- `_sum` / `_savedSum`: Running sum for SMA
+- `_emaValue` / `_savedEmaValue`: Current EMA value
+- `_count` / `_savedCount`: Bar count for warmup
+
+## Parameters
+
+| Parameter | Type | Default | Valid Range | Description |
+|-----------|------|---------|-------------|-------------|
+| `period` | int | 14 | ≥ 1 | Lookback period for moving average |
+| `useEma` | bool | false | true/false | Use EMA (true) or SMA (false) |
+
+## Mathematical Foundation
+
+### Formula
+
+```
+Qstick = MA(Close - Open, period)
+```
+
+### Interpretation
+
+| Qstick Value | Market Condition |
+|--------------|------------------|
+| > 0 | Bullish momentum (closes above opens) |
+| < 0 | Bearish momentum (closes below opens) |
+| = 0 | Neutral (balanced open/close) |
+| Rising | Increasing bullish pressure |
+| Falling | Increasing bearish pressure |
+
+### Signal Generation
+
+- **Buy Signal**: Qstick crosses above zero
+- **Sell Signal**: Qstick crosses below zero
+- **Divergence**: Price making new highs while Qstick making lower highs suggests weakening momentum
+
+## Performance Profile
+
+### Operation Count (Streaming Mode)
+
+| Operation | SMA Mode | EMA Mode |
+|-----------|----------|----------|
+| ADD/SUB | 3 | 2 |
+| MUL | 0 | 1 |
+| DIV | 1 | 0 |
+| FMA | 0 | 1 |
+| Memory | O(period) | O(1) |
+
+### Complexity
+
+- **Time**: O(1) per bar for both modes
+- **Space**: O(period) for SMA, O(1) for EMA
+
+### Quality Metrics
+
+| Metric | Score | Notes |
+|--------|-------|-------|
+| Accuracy | 10/10 | Exact calculation |
+| Timeliness | 8/10 | Lag proportional to period |
+| Overshoot | 2/10 | Smooth, no overshoot |
+| Smoothness | 8/10 | SMA smoother than EMA |
+
+## Validation
+
+| Library | Status | Notes |
+|---------|--------|-------|
+| TA-Lib | ✓ | Not available (implement locally) |
+| Skender | ✓ | Validated against Qstick |
+| OoplesFinance | ✓ | Validated |
+
+## Common Pitfalls
+
+1. **Ignoring Volume**: Qstick weights all bars equally; consider volume-weighted variants for more accuracy
+2. **Range Dependence**: Absolute values depend on price scale; normalize for comparison across instruments
+3. **Period Selection**: Short periods (5-8) for trading signals; long periods (20+) for trend identification
+4. **Gap Sensitivity**: Large gaps (open ≠ previous close) can distort readings
+5. **Flat Markets**: Near-zero readings indicate indecision, not necessarily reversal
+
+## Usage Example
+
+```csharp
+// Create Qstick with 14-period SMA
+var qstick = new Qstick(14);
+
+// Update with bar data
+foreach (var bar in bars)
+{
+ var result = qstick.Update(bar);
+ if (qstick.IsHot)
+ {
+ Console.WriteLine($"Qstick: {result.Value:F4}");
+ }
+}
+
+// Or use EMA mode
+var qstickEma = new Qstick(14, useEma: true);
+```
+
+## References
+
+1. Chande, T. S. (1994). *The New Technical Trader*. John Wiley & Sons.
+2. Chande, T. S., & Kroll, S. (1994). *Beyond Technical Analysis*. John Wiley & Sons.
+3. Kirkpatrick, C. D., & Dahlquist, J. R. (2015). *Technical Analysis: The Complete Resource for Financial Market Technicians*. FT Press.
diff --git a/lib/dynamics/super/Super.cs b/lib/dynamics/super/Super.cs
index 866fb095..99cf7373 100644
--- a/lib/dynamics/super/Super.cs
+++ b/lib/dynamics/super/Super.cs
@@ -270,4 +270,11 @@ public sealed class Super : ITValuePublisher
var indicator = new Super(period, multiplier);
return indicator.Update(source);
}
+
+ public static (TSeries Results, Super Indicator) Calculate(TBarSeries source, int period = 10, double multiplier = 3.0)
+ {
+ var indicator = new Super(period, multiplier);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
}
diff --git a/lib/dynamics/ttm_trend/TtmTrend.Quantower.Tests.cs b/lib/dynamics/ttm_trend/TtmTrend.Quantower.Tests.cs
new file mode 100644
index 00000000..a680f9cb
--- /dev/null
+++ b/lib/dynamics/ttm_trend/TtmTrend.Quantower.Tests.cs
@@ -0,0 +1,111 @@
+using TradingPlatform.BusinessLayer;
+using Xunit;
+
+namespace QuanTAlib.Tests;
+
+public class TtmTrendIndicatorTests
+{
+ [Fact]
+ public void Constructor_CreatesValidIndicator()
+ {
+ var indicator = new TtmTrendIndicator();
+ Assert.NotNull(indicator);
+ Assert.Equal("TTM Trend", indicator.Name);
+ }
+
+ [Fact]
+ public void DefaultPeriod_Is6()
+ {
+ var indicator = new TtmTrendIndicator();
+ Assert.Equal(6, indicator.Period);
+ }
+
+ [Fact]
+ public void ShortName_IncludesParameters()
+ {
+ var indicator = new TtmTrendIndicator { Period = 10 };
+ Assert.Equal("TTM_TREND(10)", indicator.ShortName);
+ }
+
+ [Fact]
+ public void MinHistoryDepths_EqualsZero()
+ {
+ var indicator = new TtmTrendIndicator { Period = 10 };
+ Assert.Equal(0, TtmTrendIndicator.MinHistoryDepths);
+ IWatchlistIndicator watchlistIndicator = indicator;
+ Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
+ }
+
+ [Fact]
+ public void SeparateWindow_IsFalse()
+ {
+ var indicator = new TtmTrendIndicator();
+ Assert.False(indicator.SeparateWindow);
+ }
+
+ [Fact]
+ public void OnBackGround_IsTrue()
+ {
+ var indicator = new TtmTrendIndicator();
+ Assert.True(indicator.OnBackGround);
+ }
+
+ [Fact]
+ public void CalculationIntegration_ProducesCorrectValues()
+ {
+ var ttmCore = new TtmTrend(6);
+ var time = DateTime.UtcNow;
+
+ var bar1 = new TBar(time.Ticks, 100.0, 105.0, 98.0, 102.0, 1000);
+ var bar2 = new TBar(time.AddMinutes(1).Ticks, 102.0, 108.0, 100.0, 106.0, 1000);
+
+ ttmCore.Update(bar1);
+ var result = ttmCore.Update(bar2);
+
+ // After 2 bars, should be hot and have valid value
+ Assert.True(ttmCore.IsHot);
+ Assert.True(double.IsFinite(result.Value));
+ }
+
+ [Fact]
+ public void TrendDirection_Bullish_WhenRising()
+ {
+ var ttmCore = new TtmTrend(6);
+ var time = DateTime.UtcNow;
+
+ ttmCore.Update(new TBar(time.Ticks, 100.0, 105.0, 98.0, 102.0, 1000));
+ ttmCore.Update(new TBar(time.AddMinutes(1).Ticks, 110.0, 115.0, 108.0, 112.0, 1000));
+
+ Assert.Equal(1, ttmCore.Trend);
+ }
+
+ [Fact]
+ public void TrendDirection_Bearish_WhenFalling()
+ {
+ var ttmCore = new TtmTrend(6);
+ var time = DateTime.UtcNow;
+
+ ttmCore.Update(new TBar(time.Ticks, 100.0, 105.0, 98.0, 102.0, 1000));
+ ttmCore.Update(new TBar(time.AddMinutes(1).Ticks, 90.0, 95.0, 88.0, 92.0, 1000));
+
+ Assert.Equal(-1, ttmCore.Trend);
+ }
+
+ [Fact]
+ public void CoreIndicator_ResetsCorrectly()
+ {
+ var ttm = new TtmTrend(6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TBar(time.Ticks, 100.0, 105.0, 98.0, 102.0, 1000));
+ ttm.Update(new TBar(time.AddMinutes(1).Ticks, 102.0, 108.0, 100.0, 106.0, 1000));
+
+ Assert.True(ttm.IsHot);
+
+ ttm.Reset();
+
+ Assert.False(ttm.IsHot);
+ Assert.Equal(default, ttm.Last);
+ Assert.Equal(0, ttm.Trend);
+ }
+}
diff --git a/lib/dynamics/ttm_trend/TtmTrend.Quantower.cs b/lib/dynamics/ttm_trend/TtmTrend.Quantower.cs
new file mode 100644
index 00000000..7ca130e3
--- /dev/null
+++ b/lib/dynamics/ttm_trend/TtmTrend.Quantower.cs
@@ -0,0 +1,66 @@
+using System.Drawing;
+using System.Runtime.CompilerServices;
+using TradingPlatform.BusinessLayer;
+
+namespace QuanTAlib;
+
+[SkipLocalsInit]
+public sealed class TtmTrendIndicator : Indicator, IWatchlistIndicator
+{
+ [InputParameter("Period", sortIndex: 0, 1, 100, 1, 0)]
+ public int Period { get; set; } = 6;
+
+ [InputParameter("Show cold values", sortIndex: 21)]
+ public bool ShowColdValues { get; set; } = true;
+
+ public override string ShortName => $"TTM_TREND({Period})";
+
+ public static int MinHistoryDepths => 0;
+ int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
+
+ private TtmTrend _indicator = null!;
+ private readonly LineSeries _series;
+
+ public TtmTrendIndicator()
+ {
+ Name = "TTM Trend";
+ Description = "John Carter's TTM Trend - EMA-based trend indicator with color-coded direction.";
+ _series = new LineSeries("TTM Trend", Color.Gray, 3, LineStyle.Solid);
+ AddLineSeries(_series);
+ SeparateWindow = false;
+ OnBackGround = true;
+ }
+
+ protected override void OnInit()
+ {
+ _indicator = new TtmTrend(Period);
+ base.OnInit();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void OnUpdate(UpdateArgs args)
+ {
+ bool isNew = args.IsNewBar();
+ var bar = this.GetInputBar(args);
+ var result = _indicator.Update(bar, isNew);
+ _series.SetValue(result.Value, _indicator.IsHot, ShowColdValues);
+
+ // Color based on trend direction
+ if (_indicator.IsHot)
+ {
+ Color trendColor = _indicator.Trend switch
+ {
+ 1 => Color.Green,
+ -1 => Color.Red,
+ _ => Color.Gray
+ };
+ _series.SetMarker(0, trendColor);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void OnPaintChart(PaintChartEventArgs args)
+ {
+ base.OnPaintChart(args);
+ }
+}
diff --git a/lib/dynamics/ttm_trend/TtmTrend.Tests.cs b/lib/dynamics/ttm_trend/TtmTrend.Tests.cs
new file mode 100644
index 00000000..f48490e0
--- /dev/null
+++ b/lib/dynamics/ttm_trend/TtmTrend.Tests.cs
@@ -0,0 +1,469 @@
+// TTM_TREND Tests - John Carter's TTM Trend Indicator
+
+using Xunit;
+
+namespace QuanTAlib.Tests;
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Constructor Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendConstructorTests
+{
+ [Fact]
+ public void Constructor_DefaultPeriod_Is6()
+ {
+ var ttm = new TtmTrend();
+ Assert.Equal(6, ttm.Period);
+ }
+
+ [Fact]
+ public void Constructor_CustomPeriod_IsSet()
+ {
+ var ttm = new TtmTrend(period: 10);
+ Assert.Equal(10, ttm.Period);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(-1)]
+ [InlineData(-10)]
+ public void Constructor_InvalidPeriod_Throws(int period)
+ {
+ Assert.Throws(() => new TtmTrend(period));
+ }
+
+ [Fact]
+ public void Constructor_MinPeriod_IsValid()
+ {
+ var ttm = new TtmTrend(period: 1);
+ Assert.Equal(1, ttm.Period);
+ }
+
+ [Fact]
+ public void Name_ContainsPeriod()
+ {
+ var ttm = new TtmTrend(period: 10);
+ Assert.Contains("10", ttm.Name, StringComparison.Ordinal);
+ Assert.Contains("TTM_TREND", ttm.Name, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void WarmupPeriod_Is2()
+ {
+ Assert.Equal(2, TtmTrend.WarmupPeriod);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Basic Operation Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendBasicTests
+{
+ [Fact]
+ public void Update_FirstBar_ReturnsValue()
+ {
+ var ttm = new TtmTrend();
+ var result = ttm.Update(new TValue(DateTime.UtcNow.Ticks, 100.0));
+ Assert.Equal(100.0, result.Value);
+ }
+
+ [Fact]
+ public void Update_SecondBar_CalculatesEma()
+ {
+ var ttm = new TtmTrend(period: 6); // alpha = 2/7 ≈ 0.2857
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ var result = ttm.Update(new TValue(time.AddMinutes(1).Ticks, 107.0));
+
+ // EMA = alpha * value + (1 - alpha) * prevEMA
+ // EMA = 0.2857 * 107 + 0.7143 * 100 = 30.57 + 71.43 = 102.0
+ double alpha = 2.0 / 7.0;
+ double expected = alpha * 107.0 + (1 - alpha) * 100.0;
+ Assert.Equal(expected, result.Value, 10);
+ }
+
+ [Fact]
+ public void IsHot_AfterFirstBar_IsFalse()
+ {
+ var ttm = new TtmTrend();
+ ttm.Update(new TValue(DateTime.UtcNow.Ticks, 100.0));
+ Assert.False(ttm.IsHot);
+ }
+
+ [Fact]
+ public void IsHot_AfterSecondBar_IsTrue()
+ {
+ var ttm = new TtmTrend();
+ var time = DateTime.UtcNow;
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 101.0));
+ Assert.True(ttm.IsHot);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Trend Direction Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendDirectionTests
+{
+ [Fact]
+ public void Trend_RisingValues_IsBullish()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 110.0));
+
+ Assert.Equal(1, ttm.Trend);
+ }
+
+ [Fact]
+ public void Trend_FallingValues_IsBearish()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 90.0));
+
+ Assert.Equal(-1, ttm.Trend);
+ }
+
+ [Fact]
+ public void Trend_SameValue_IsNeutral()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 100.0));
+
+ Assert.Equal(0, ttm.Trend);
+ }
+
+ [Fact]
+ public void Trend_CanChangeDirection()
+ {
+ var ttm = new TtmTrend(period: 2); // Fast EMA
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 110.0));
+ Assert.Equal(1, ttm.Trend);
+
+ // Drop significantly to reverse trend
+ ttm.Update(new TValue(time.AddMinutes(2).Ticks, 90.0));
+ Assert.Equal(-1, ttm.Trend);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Strength Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendStrengthTests
+{
+ [Fact]
+ public void Strength_IsPositive()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 110.0));
+
+ Assert.True(ttm.Strength > 0);
+ }
+
+ [Fact]
+ public void Strength_ZeroOnFirstBar()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+
+ Assert.Equal(0, ttm.Strength);
+ }
+
+ [Fact]
+ public void Strength_LargerMoves_HigherStrength()
+ {
+ var ttm1 = new TtmTrend(period: 6);
+ var ttm2 = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ // Small move
+ ttm1.Update(new TValue(time.Ticks, 100.0));
+ ttm1.Update(new TValue(time.AddMinutes(1).Ticks, 101.0));
+
+ // Large move
+ ttm2.Update(new TValue(time.Ticks, 100.0));
+ ttm2.Update(new TValue(time.AddMinutes(1).Ticks, 110.0));
+
+ Assert.True(ttm2.Strength > ttm1.Strength);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Bar Input Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendBarInputTests
+{
+ [Fact]
+ public void Update_Bar_UsesTypicalPrice()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var bar = new TBar(DateTime.UtcNow.Ticks, 100.0, 105.0, 98.0, 102.0, 1000);
+
+ var result = ttm.Update(bar);
+
+ // Typical price = (H + L + C) / 3 = (105 + 98 + 102) / 3 = 101.67
+ double typical = (105.0 + 98.0 + 102.0) / 3.0;
+ Assert.Equal(typical, result.Value, 10);
+ }
+
+ [Fact]
+ public void Update_BarSeries_ReturnsCorrectLength()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ for (int i = 0; i < 10; i++)
+ {
+ bars.Add(new TBar(time.AddMinutes(i).Ticks, 100.0, 105.0, 95.0, 102.0, 1000));
+ }
+
+ var result = ttm.Update(bars);
+ Assert.Equal(10, result.Count);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Edge Case Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendEdgeCaseTests
+{
+ [Fact]
+ public void Update_NaN_ReturnsLastValue()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ var result1 = ttm.Update(new TValue(time.Ticks, 100.0));
+ var result2 = ttm.Update(new TValue(time.AddMinutes(1).Ticks, double.NaN));
+
+ Assert.Equal(result1.Value, result2.Value);
+ }
+
+ [Fact]
+ public void Update_Infinity_ReturnsLastValue()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ var result1 = ttm.Update(new TValue(time.Ticks, 100.0));
+ var result2 = ttm.Update(new TValue(time.AddMinutes(1).Ticks, double.PositiveInfinity));
+
+ Assert.Equal(result1.Value, result2.Value);
+ }
+
+ [Fact]
+ public void Update_LargeValues_CalculatesCorrectly()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ var result = ttm.Update(new TValue(time.Ticks, 1e10));
+ Assert.True(double.IsFinite(result.Value));
+ Assert.Equal(1e10, result.Value);
+ }
+
+ [Fact]
+ public void Update_SmallValues_CalculatesCorrectly()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ var result = ttm.Update(new TValue(time.Ticks, 1e-10));
+ Assert.True(double.IsFinite(result.Value));
+ Assert.Equal(1e-10, result.Value);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Reset Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendResetTests
+{
+ [Fact]
+ public void Reset_ClearsState()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 110.0));
+
+ Assert.True(ttm.IsHot);
+
+ ttm.Reset();
+
+ Assert.False(ttm.IsHot);
+ Assert.Equal(default, ttm.Last);
+ Assert.Equal(0, ttm.Trend);
+ Assert.Equal(0, ttm.Strength);
+ }
+
+ [Fact]
+ public void Reset_CanReuseAfterReset()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 110.0));
+ ttm.Reset();
+
+ var result = ttm.Update(new TValue(time.AddMinutes(2).Ticks, 200.0));
+
+ Assert.Equal(200.0, result.Value);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Bar Correction Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendBarCorrectionTests
+{
+ [Fact]
+ public void Update_IsNewFalse_CorrectsPreviousValue()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var time = DateTime.UtcNow;
+
+ ttm.Update(new TValue(time.Ticks, 100.0));
+ ttm.Update(new TValue(time.AddMinutes(1).Ticks, 110.0), isNew: true);
+
+ // Correct the bar with different value
+ var corrected = ttm.Update(new TValue(time.AddMinutes(1).Ticks, 105.0), isNew: false);
+
+ // Should use 105 instead of 110
+ double alpha = 2.0 / 7.0;
+ double expected = alpha * 105.0 + (1 - alpha) * 100.0;
+ Assert.Equal(expected, corrected.Value, 10);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Batch Processing Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendBatchTests
+{
+ [Fact]
+ public void Batch_ReturnsCorrectResults()
+ {
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ for (int i = 0; i < 10; i++)
+ {
+ bars.Add(new TBar(time.AddMinutes(i).Ticks, 100.0 + i, 105.0 + i, 95.0 + i, 102.0 + i, 1000));
+ }
+
+ var result = TtmTrend.Batch(bars, period: 6);
+
+ Assert.Equal(10, result.Count);
+ }
+
+ [Fact]
+ public void Calculate_ReturnsIndicatorAndResults()
+ {
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ for (int i = 0; i < 10; i++)
+ {
+ bars.Add(new TBar(time.AddMinutes(i).Ticks, 100.0 + i, 105.0 + i, 95.0 + i, 102.0 + i, 1000));
+ }
+
+ var (results, indicator) = TtmTrend.Calculate(bars, period: 6);
+
+ Assert.Equal(10, results.Count);
+ Assert.True(indicator.IsHot);
+ Assert.Equal(6, indicator.Period);
+ }
+
+ [Fact]
+ public void Update_EmptyBarSeries_ReturnsEmpty()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var bars = new TBarSeries();
+
+ var result = ttm.Update(bars);
+
+ Assert.True(result.Count == 0);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Event Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendEventTests
+{
+ [Fact]
+ public void Update_RaisesPubEvent()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var eventRaised = false;
+ TValue receivedValue = default;
+
+ ttm.Pub += (object? sender, in TValueEventArgs args) =>
+ {
+ eventRaised = true;
+ receivedValue = args.Value;
+ };
+
+ var result = ttm.Update(new TValue(DateTime.UtcNow.Ticks, 100.0));
+
+ Assert.True(eventRaised);
+ Assert.Equal(result.Value, receivedValue.Value);
+ }
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Prime Tests
+// ═══════════════════════════════════════════════════════════════════════════
+
+public class TtmTrendPrimeTests
+{
+ [Fact]
+ public void Prime_WarmUpIndicator()
+ {
+ var ttm = new TtmTrend(period: 6);
+ var bars = new TBarSeries();
+ var time = DateTime.UtcNow;
+
+ for (int i = 0; i < 10; i++)
+ {
+ bars.Add(new TBar(time.AddMinutes(i).Ticks, 100.0 + i, 105.0 + i, 95.0 + i, 102.0 + i, 1000));
+ }
+
+ ttm.Prime(bars);
+
+ Assert.True(ttm.IsHot);
+ Assert.NotEqual(default, ttm.Last);
+ }
+}
diff --git a/lib/dynamics/ttm_trend/TtmTrend.cs b/lib/dynamics/ttm_trend/TtmTrend.cs
new file mode 100644
index 00000000..e1e8145a
--- /dev/null
+++ b/lib/dynamics/ttm_trend/TtmTrend.cs
@@ -0,0 +1,270 @@
+// TTM_TREND: John Carter's TTM Trend Indicator
+// Color-coded EMA for visual trend identification
+// Uses 6-period EMA of HLC/3 (typical price) by default
+
+using System.Runtime.CompilerServices;
+
+namespace QuanTAlib;
+
+///
+/// TTM_TREND: John Carter's TTM Trend Indicator
+/// A fast EMA-based trend indicator with color-coded direction and strength measurement.
+///
+/// Calculation: EMA(source, period) with trend = sign(EMA - prevEMA)
+///
+///
+/// Calculation:
+///
+/// alpha = 2 / (period + 1)
+/// EMA = alpha * source + (1 - alpha) * prevEMA
+/// trend = sign(EMA - prevEMA)
+/// strength = |EMA - prevEMA| / prevEMA * 100
+///
+///
+/// Key characteristics:
+/// - O(1) update complexity per bar
+/// - Uses EMA for smooth, responsive trend following
+/// - Trend direction: +1 (bullish), -1 (bearish), 0 (neutral)
+/// - Strength measures percent change between EMA values
+/// - Default period of 6 for fast trend detection
+///
+/// Detailed documentation
+[SkipLocalsInit]
+public sealed class TtmTrend : ITValuePublisher
+{
+ private const int DefaultPeriod = 6;
+
+ private readonly int _period;
+ private readonly double _alpha;
+
+ // Current state
+ private double _ema;
+ private double _prevEma;
+ private int _sampleCount;
+
+ // Saved state for bar correction
+ private double _p_ema;
+ private double _p_prevEma;
+ private int _p_sampleCount;
+
+ ///
+ /// Display name for the indicator.
+ ///
+ public string Name { get; }
+
+ public event TValuePublishedHandler? Pub;
+
+ ///
+ /// Current TTM Trend EMA value.
+ ///
+ public TValue Last { get; private set; }
+
+ ///
+ /// Current trend direction: +1 (bullish), -1 (bearish), 0 (neutral).
+ ///
+ public int Trend { get; private set; }
+
+ ///
+ /// Current trend strength as percent change between EMA values.
+ ///
+ public double Strength { get; private set; }
+
+ ///
+ /// True when the indicator has calculated a valid value (after 2 bars).
+ ///
+ public bool IsHot => _sampleCount > 1;
+
+ ///
+ /// The lookback period parameter.
+ ///
+ public int Period => _period;
+
+ ///
+ /// The number of bars required for the indicator to warm up.
+ ///
+ public static int WarmupPeriod => 2;
+
+ ///
+ /// Creates a TTM Trend indicator with specified period.
+ ///
+ /// Lookback period for EMA (must be >= 1, default 6)
+ public TtmTrend(int period = DefaultPeriod)
+ {
+ if (period < 1)
+ {
+ throw new ArgumentException("Period must be at least 1", nameof(period));
+ }
+
+ _period = period;
+ _alpha = 2.0 / (period + 1);
+ Name = $"TTM_TREND({period})";
+ }
+
+ ///
+ /// Resets the indicator state.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Reset()
+ {
+ _ema = 0;
+ _prevEma = 0;
+ _sampleCount = 0;
+ _p_ema = 0;
+ _p_prevEma = 0;
+ _p_sampleCount = 0;
+ Trend = 0;
+ Strength = 0;
+ Last = default;
+ }
+
+ ///
+ /// Updates the TTM Trend indicator with a new value.
+ ///
+ /// Input value (typically HLC/3)
+ /// True for new bar, false for update of current bar
+ /// The current TTM Trend EMA value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TValue Update(TValue input, bool isNew = true)
+ {
+ double value = input.Value;
+
+ // Handle NaN/Infinity inputs
+ if (!double.IsFinite(value))
+ {
+ Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
+ return Last;
+ }
+
+ // State management for bar correction
+ if (isNew)
+ {
+ _p_ema = _ema;
+ _p_prevEma = _prevEma;
+ _p_sampleCount = _sampleCount;
+ }
+ else
+ {
+ _ema = _p_ema;
+ _prevEma = _p_prevEma;
+ _sampleCount = _p_sampleCount;
+ }
+
+ // EMA calculation
+ if (_sampleCount == 0)
+ {
+ _ema = value;
+ _prevEma = value;
+ }
+ else
+ {
+ _prevEma = _ema;
+ _ema = Math.FusedMultiplyAdd(_alpha, value - _ema, _ema);
+ }
+
+ if (isNew)
+ {
+ _sampleCount++;
+ }
+
+ // Calculate trend and strength
+ double diff = _ema - _prevEma;
+ Trend = Math.Sign(diff);
+ Strength = _prevEma > 1e-10 ? Math.Abs(diff) / _prevEma * 100.0 : 0.0;
+
+ Last = new TValue(input.Time, _ema);
+ Pub?.Invoke(this, new TValueEventArgs { Value = Last, IsNew = isNew });
+ return Last;
+ }
+
+ ///
+ /// Updates the TTM Trend indicator with a bar using typical price (HLC/3).
+ ///
+ /// The price bar
+ /// True for new bar, false for update of current bar
+ /// The current TTM Trend EMA value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TValue Update(TBar bar, bool isNew = true)
+ {
+ double typical = (bar.High + bar.Low + bar.Close) / 3.0;
+ return Update(new TValue(bar.Time, typical), isNew);
+ }
+
+ ///
+ /// Updates with a value series.
+ ///
+ public TSeries Update(TSeries source)
+ {
+ if (source.Count == 0)
+ {
+ return new TSeries([], []);
+ }
+
+ int len = source.Count;
+ var tList = new List(len);
+ var vList = new List(len);
+
+ for (int i = 0; i < len; i++)
+ {
+ var result = Update(source[i], isNew: true);
+ tList.Add(source.Times[i]);
+ vList.Add(result.Value);
+ }
+
+ return new TSeries(tList, vList);
+ }
+
+ ///
+ /// Updates with a bar series.
+ ///
+ public TSeries Update(TBarSeries source)
+ {
+ if (source.Count == 0)
+ {
+ return new TSeries([], []);
+ }
+
+ int len = source.Count;
+ var tList = new List(len);
+ var vList = new List(len);
+
+ var times = source.Open.Times;
+ for (int i = 0; i < len; i++)
+ {
+ var result = Update(source[i], isNew: true);
+ tList.Add(times[i]);
+ vList.Add(result.Value);
+ }
+
+ return new TSeries(tList, vList);
+ }
+
+ ///
+ /// Primes the indicator with historical bar data.
+ ///
+ public void Prime(TBarSeries source)
+ {
+ for (int i = 0; i < source.Count; i++)
+ {
+ Update(source[i], isNew: true);
+ }
+ }
+
+ ///
+ /// Creates and returns results for a bar series.
+ ///
+ public static TSeries Batch(TBarSeries source, int period = DefaultPeriod)
+ {
+ var indicator = new TtmTrend(period);
+ return indicator.Update(source);
+ }
+
+ ///
+ /// Returns the indicator and its results.
+ ///
+ public static (TSeries Results, TtmTrend Indicator) Calculate(TBarSeries source, int period = DefaultPeriod)
+ {
+ var indicator = new TtmTrend(period);
+ var results = indicator.Update(source);
+ return (results, indicator);
+ }
+}
diff --git a/lib/dynamics/ttm_trend/TtmTrend.md b/lib/dynamics/ttm_trend/TtmTrend.md
new file mode 100644
index 00000000..849406ac
--- /dev/null
+++ b/lib/dynamics/ttm_trend/TtmTrend.md
@@ -0,0 +1,113 @@
+# TTM_TREND: TTM Trend Indicator
+
+> John Carter's TTM Trend - A fast EMA-based trend indicator with color-coded direction.
+
+## Historical Context
+
+John Carter developed the TTM (Trade the Markets) Trend indicator as a clean visual tool for identifying short-term trend direction. Popularized through his book *Mastering the Trade* and the thinkorswim platform, it provides a simple but effective way to see trend changes at a glance using color-coded lines.
+
+## Algorithm
+
+### Core Calculation
+```
+alpha = 2 / (period + 1)
+EMA = alpha × source + (1 - alpha) × prevEMA
+```
+
+Or equivalently:
+```
+EMA = alpha × (source - EMA) + EMA
+```
+
+### Trend Detection
+```
+trend = sign(EMA - prevEMA)
+ +1 = bullish (EMA rising)
+ -1 = bearish (EMA falling)
+ 0 = neutral (EMA unchanged)
+```
+
+### Strength Measurement
+```
+strength = |EMA - prevEMA| / prevEMA × 100%
+```
+
+## Default Parameters
+
+| Parameter | Value | Description |
+|:----------|:------|:------------|
+| Period | 6 | EMA lookback period (very fast) |
+| Source | HLC/3 | Typical price (High + Low + Close) / 3 |
+
+## Outputs
+
+| Output | Type | Description |
+|:-------|:-----|:------------|
+| Value | double | Current EMA value |
+| Trend | int | Trend direction: +1, -1, or 0 |
+| Strength | double | Percent change between EMA values |
+| IsHot | bool | True after warming up (2 bars) |
+
+## Color Coding
+
+| Color | Condition | Meaning |
+|:------|:----------|:--------|
+| 🟢 Green | Trend > 0 | EMA rising (bullish) |
+| 🔴 Red | Trend < 0 | EMA falling (bearish) |
+| ⚫ Gray | Trend = 0 | EMA unchanged (neutral) |
+
+## Performance
+
+| Metric | Value |
+|:-------|:------|
+| Time complexity | O(1) per bar |
+| Space complexity | O(1) |
+| Warmup period | 2 bars |
+| Allocations | Zero in hot path |
+
+## Usage Examples
+
+### Basic Usage
+```csharp
+var ttm = new TtmTrend(period: 6);
+
+// Update with typical price
+var result = ttm.Update(new TValue(time, typicalPrice));
+
+// Or update with bar (uses HLC/3 automatically)
+var result = ttm.Update(bar);
+
+// Access trend direction
+if (ttm.Trend > 0) { /* bullish */ }
+else if (ttm.Trend < 0) { /* bearish */ }
+```
+
+### Batch Processing
+```csharp
+var results = TtmTrend.Batch(barSeries, period: 6);
+```
+
+### With Indicator Instance
+```csharp
+var (results, indicator) = TtmTrend.Calculate(barSeries, period: 6);
+bool isBullish = indicator.Trend > 0;
+double strength = indicator.Strength;
+```
+
+## Trading Applications
+
+1. **Trend Following**: Trade in the direction of the EMA color
+2. **Trend Confirmation**: Use with other TTM indicators (Squeeze, Wave)
+3. **Entry Timing**: Enter on color change with confirmation
+4. **Exit Signal**: Exit when color changes against position
+
+## Category
+
+**Dynamics** - Measures trend direction and momentum using fast EMA smoothing.
+
+## See Also
+
+- [TTM_SQUEEZE: TTM Squeeze](../ttm_squeeze/TtmSqueeze.md)
+- [TTM_WAVE: TTM Wave](../../oscillators/ttm_wave/TtmWave.md)
+- [TTM_LRC: TTM Linear Regression Channel](../../channels/ttm_lrc/TtmLrc.md)
+- [SUPER: SuperTrend](../super/Super.md)
diff --git a/lib/dynamics/ttm/ttm.pine b/lib/dynamics/ttm_trend/TtmTrend.pine
similarity index 89%
rename from lib/dynamics/ttm/ttm.pine
rename to lib/dynamics/ttm_trend/TtmTrend.pine
index 13209ec8..70ea4154 100644
--- a/lib/dynamics/ttm/ttm.pine
+++ b/lib/dynamics/ttm_trend/TtmTrend.pine
@@ -1,14 +1,14 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
-indicator("TTM Trend", "TTM", overlay=true)
+indicator("TTM Trend", "TTM_TREND", overlay=true)
//@function Calculates TTM Trend using 6-period moving average with color-coded trend
-//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/dynamics/ttm.md
-//@param source Series to calculate TTM from
+//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/dynamics/ttm_trend.md
+//@param source Series to calculate TTM Trend from
//@param period Lookback period for moving average
//@returns Tuple [ttm_line, trend, strength] where trend is -1/0/1 and strength is percentage change
-ttm(series float source, simple int period = 6) =>
+ttm_trend(series float source, simple int period = 6) =>
if period <= 0
runtime.error("Period must be greater than 0")
@@ -33,7 +33,7 @@ i_source = input.source(hlc3, "Source")
i_show_strength = input.bool(true, "Show Trend Strength %")
// Calculation
-[ttm_line, trend, strength] = ttm(i_source, i_period)
+[ttm_line, trend, strength] = ttm_trend(i_source, i_period)
// Colors
color up_color = color.new(color.green, 0)
diff --git a/lib/dynamics/vortex/Vortex.cs b/lib/dynamics/vortex/Vortex.cs
index edc146bf..aaa167ba 100644
--- a/lib/dynamics/vortex/Vortex.cs
+++ b/lib/dynamics/vortex/Vortex.cs
@@ -207,7 +207,7 @@ public sealed class Vortex : ITValuePublisher
var viPlusValues = new double[len];
var viMinusValues = new double[len];
- Calculate(source.High.Values, source.Low.Values, source.Close.Values, _period, viPlusValues, viMinusValues);
+ Batch(source.High.Values, source.Low.Values, source.Close.Values, _period, viPlusValues, viMinusValues);
var tList = new List(len);
var vList = new List(viPlusValues);
@@ -237,7 +237,7 @@ public sealed class Vortex : ITValuePublisher
/// Output VI+ values
/// Output VI- values
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
- public static void Calculate(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close,
+ public static void Batch(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close,
int period, Span viPlus, Span viMinus)
{
int len = high.Length;
@@ -313,4 +313,12 @@ public sealed class Vortex : ITValuePublisher
var vortex = new Vortex(period);
return vortex.Update(source);
}
+
+ public static (TSeries Results, Vortex Indicator) Calculate(TBarSeries source)
+ {
+ var indicator = new Vortex();
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
}
diff --git a/lib/errors/huber/Huber.Tests.cs b/lib/errors/huber/Huber.Tests.cs
index 70dfb24f..aacf2712 100644
--- a/lib/errors/huber/Huber.Tests.cs
+++ b/lib/errors/huber/Huber.Tests.cs
@@ -332,7 +332,7 @@ public class HuberTests
predicted.Add(now.AddMinutes(i), 100.5); // Small constant error
}
- var results = Huber.Calculate(actual, predicted, 3);
+ var results = Huber.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
// Error = 0.5, Huber (small error) = 0.5 * 0.5^2 = 0.125
@@ -355,7 +355,7 @@ public class HuberTests
predicted.Add(DateTime.UtcNow, i);
}
- Assert.Throws(() => Huber.Calculate(actual, predicted, 3));
+ Assert.Throws(() => Huber.Batch(actual, predicted, 3));
}
[Fact]
diff --git a/lib/errors/huber/Huber.cs b/lib/errors/huber/Huber.cs
index c06c798f..29e6ef36 100644
--- a/lib/errors/huber/Huber.cs
+++ b/lib/errors/huber/Huber.cs
@@ -67,7 +67,7 @@ public sealed class Huber : BiInputIndicatorBase
///
/// Calculates Huber Loss for two time series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period, double delta = 1.345)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period, double delta = 1.345)
{
if (actual.Count != predicted.Count)
{
@@ -127,4 +127,11 @@ public sealed class Huber : BiInputIndicatorBase
// Apply rolling mean
ErrorHelpers.ApplyRollingMean(errors, output, period);
}
-}
+
+ public static (TSeries Results, Huber Indicator) Calculate(TSeries actual, TSeries predicted, int period, double delta = 1.345)
+ {
+ var indicator = new Huber(period, delta);
+ TSeries results = Batch(actual, predicted, period, delta);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/logcosh/LogCosh.Tests.cs b/lib/errors/logcosh/LogCosh.Tests.cs
index 135f7b84..0fa8356c 100644
--- a/lib/errors/logcosh/LogCosh.Tests.cs
+++ b/lib/errors/logcosh/LogCosh.Tests.cs
@@ -239,7 +239,7 @@ public class LogCoshTests
iterativeResults[i] = logCoshIterative.Update(actualSeries[i], predictedSeries[i]).Value;
}
- var batchResults = LogCosh.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var batchResults = LogCosh.Batch(actualSeries, predictedSeries, DefaultPeriod);
Assert.Equal(count, batchResults.Count);
for (int i = 0; i < count; i++)
@@ -283,7 +283,7 @@ public class LogCoshTests
predictedArr[i] = pred;
}
- var tseriesResult = LogCosh.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var tseriesResult = LogCosh.Batch(actualSeries, predictedSeries, DefaultPeriod);
LogCosh.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), DefaultPeriod);
for (int i = 0; i < 100; i++)
@@ -332,7 +332,7 @@ public class LogCoshTests
predicted.Add(DateTime.UtcNow.Ticks, 98);
- Assert.Throws(() => LogCosh.Calculate(actual, predicted, DefaultPeriod));
+ Assert.Throws(() => LogCosh.Batch(actual, predicted, DefaultPeriod));
}
[Fact]
diff --git a/lib/errors/logcosh/LogCosh.cs b/lib/errors/logcosh/LogCosh.cs
index c07d7d3b..1a427d5b 100644
--- a/lib/errors/logcosh/LogCosh.cs
+++ b/lib/errors/logcosh/LogCosh.cs
@@ -58,7 +58,7 @@ public sealed class LogCosh : BiInputIndicatorBase
///
/// Calculates LogCosh for entire series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -97,4 +97,11 @@ public sealed class LogCosh : BiInputIndicatorBase
}
}
}
-}
+
+ public static (TSeries Results, LogCosh Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new LogCosh(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/maape/Maape.Tests.cs b/lib/errors/maape/Maape.Tests.cs
index 87f82bd1..83283f42 100644
--- a/lib/errors/maape/Maape.Tests.cs
+++ b/lib/errors/maape/Maape.Tests.cs
@@ -212,7 +212,7 @@ public class MaapeTests
streamingResults[i] = maapeIterative.Update(actualArr[i], predictedArr[i]).Value;
}
- var batchResults = Maape.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var batchResults = Maape.Batch(actualSeries, predictedSeries, DefaultPeriod);
Assert.Equal(count, batchResults.Count);
for (int i = 0; i < count; i++)
@@ -256,7 +256,7 @@ public class MaapeTests
predictedArr[i] = pred;
}
- var tseriesResult = Maape.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var tseriesResult = Maape.Batch(actualSeries, predictedSeries, DefaultPeriod);
Maape.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), DefaultPeriod);
for (int i = 0; i < 100; i++)
@@ -305,7 +305,7 @@ public class MaapeTests
predicted.Add(DateTime.UtcNow.Ticks, 98);
- Assert.Throws(() => Maape.Calculate(actual, predicted, DefaultPeriod));
+ Assert.Throws(() => Maape.Batch(actual, predicted, DefaultPeriod));
}
[Fact]
diff --git a/lib/errors/maape/Maape.cs b/lib/errors/maape/Maape.cs
index 4a3ae12b..42cb743a 100644
--- a/lib/errors/maape/Maape.cs
+++ b/lib/errors/maape/Maape.cs
@@ -49,7 +49,7 @@ public sealed class Maape : BiInputIndicatorBase
///
/// Calculates Mean Arctangent Absolute Percentage Error for two time series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -116,6 +116,13 @@ public sealed class Maape : BiInputIndicatorBase
}
}
+ public static (TSeries Results, Maape Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Maape(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
///
/// Computes arctangent percentage errors.
///
@@ -178,4 +185,4 @@ public sealed class Maape : BiInputIndicatorBase
output[i] = absActual > Epsilon ? Math.Atan(absError / absActual) : Math.PI / 2.0;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/mae/Mae.Tests.cs b/lib/errors/mae/Mae.Tests.cs
index 1c5c4bc7..9f3fb99c 100644
--- a/lib/errors/mae/Mae.Tests.cs
+++ b/lib/errors/mae/Mae.Tests.cs
@@ -308,7 +308,7 @@ public class MaeTests
predicted.Add(now.AddMinutes(i), i * 10 + 5);
}
- var results = Mae.Calculate(actual, predicted, 3);
+ var results = Mae.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
// All errors are 5, so MAE should be 5
@@ -331,7 +331,7 @@ public class MaeTests
predicted.Add(DateTime.UtcNow, i);
}
- Assert.Throws(() => Mae.Calculate(actual, predicted, 3));
+ Assert.Throws(() => Mae.Batch(actual, predicted, 3));
}
[Fact]
diff --git a/lib/errors/mae/Mae.cs b/lib/errors/mae/Mae.cs
index ab1366b4..101ca02b 100644
--- a/lib/errors/mae/Mae.cs
+++ b/lib/errors/mae/Mae.cs
@@ -45,7 +45,7 @@ public sealed class Mae : BiInputIndicatorBase
/// Predicted values series
/// MAE period
/// MAE series
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -89,4 +89,11 @@ public sealed class Mae : BiInputIndicatorBase
}
}
}
-}
+
+ public static (TSeries Results, Mae Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mae(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/mapd/Mapd.Tests.cs b/lib/errors/mapd/Mapd.Tests.cs
index 2291ceeb..df964981 100644
--- a/lib/errors/mapd/Mapd.Tests.cs
+++ b/lib/errors/mapd/Mapd.Tests.cs
@@ -292,7 +292,7 @@ public class MapdTests
predicted.Add(now.AddMinutes(i), 110);
}
- var results = Mapd.Calculate(actual, predicted, 3);
+ var results = Mapd.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
// |100-110|/110 * 100 = 9.0909...%
@@ -315,7 +315,7 @@ public class MapdTests
predicted.Add(DateTime.UtcNow, i + 1);
}
- Assert.Throws(() => Mapd.Calculate(actual, predicted, 3));
+ Assert.Throws(() => Mapd.Batch(actual, predicted, 3));
}
[Fact]
diff --git a/lib/errors/mapd/Mapd.cs b/lib/errors/mapd/Mapd.cs
index e90fdcde..606e142b 100644
--- a/lib/errors/mapd/Mapd.cs
+++ b/lib/errors/mapd/Mapd.cs
@@ -48,7 +48,7 @@ public sealed class Mapd : BiInputIndicatorBase
///
/// Calculates Mean Absolute Percentage Deviation for two time series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -104,6 +104,13 @@ public sealed class Mapd : BiInputIndicatorBase
ErrorHelpers.ApplyRollingMean(errors, output, period);
}
+ public static (TSeries Results, Mapd Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mapd(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
///
/// Computes MAPD errors (percentage errors divided by predicted).
///
@@ -164,4 +171,4 @@ public sealed class Mapd : BiInputIndicatorBase
: 0.0;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/mape/Mape.Tests.cs b/lib/errors/mape/Mape.Tests.cs
index 70a96a1a..d42d72b0 100644
--- a/lib/errors/mape/Mape.Tests.cs
+++ b/lib/errors/mape/Mape.Tests.cs
@@ -308,7 +308,7 @@ public class MapeTests
predicted.Add(now.AddMinutes(i), 110); // 10% error
}
- var results = Mape.Calculate(actual, predicted, 3);
+ var results = Mape.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
Assert.Equal(10.0, results.Last.Value, 10);
@@ -330,7 +330,7 @@ public class MapeTests
predicted.Add(DateTime.UtcNow, i + 1);
}
- Assert.Throws(() => Mape.Calculate(actual, predicted, 3));
+ Assert.Throws(() => Mape.Batch(actual, predicted, 3));
}
[Fact]
diff --git a/lib/errors/mape/Mape.cs b/lib/errors/mape/Mape.cs
index 027abbc8..e6e0fef5 100644
--- a/lib/errors/mape/Mape.cs
+++ b/lib/errors/mape/Mape.cs
@@ -41,7 +41,7 @@ public sealed class Mape : BiInputIndicatorBase
///
/// Calculates MAPE for entire series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -65,4 +65,11 @@ public sealed class Mape : BiInputIndicatorBase
ErrorHelpers.ComputePercentageErrors(actual, predicted, percentErrors, Epsilon);
ErrorHelpers.ApplyRollingMean(percentErrors, output, period);
}
-}
+
+ public static (TSeries Results, Mape Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mape(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/mase/Mase.Tests.cs b/lib/errors/mase/Mase.Tests.cs
index f032063c..f5eef5e3 100644
--- a/lib/errors/mase/Mase.Tests.cs
+++ b/lib/errors/mase/Mase.Tests.cs
@@ -241,7 +241,7 @@ public class MaseTests
iterativeResults.Add(maseIterative.Update(actual[i], predicted[i]).Value);
}
- var batchResults = Mase.Calculate(actual, predicted, Period);
+ var batchResults = Mase.Batch(actual, predicted, Period);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -282,7 +282,7 @@ public class MaseTests
double[] predictedArr = predictedSeries.Values.ToArray();
double[] output = new double[100];
- var tseriesResult = Mase.Calculate(actualSeries, predictedSeries, Period);
+ var tseriesResult = Mase.Batch(actualSeries, predictedSeries, Period);
Mase.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), Period);
for (int i = 0; i < 100; i++)
@@ -303,7 +303,7 @@ public class MaseTests
}
// 1. Batch Mode (static method)
- var batchSeries = Mase.Calculate(actualSeries, predictedSeries, Period);
+ var batchSeries = Mase.Batch(actualSeries, predictedSeries, Period);
double expected = batchSeries.Last.Value;
// 2. Span Mode
diff --git a/lib/errors/mase/Mase.cs b/lib/errors/mase/Mase.cs
index 36384540..e05199bb 100644
--- a/lib/errors/mase/Mase.cs
+++ b/lib/errors/mase/Mase.cs
@@ -162,7 +162,7 @@ public sealed class Mase : AbstractBase
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("MASE requires two inputs. Use Calculate(actualSeries, predictedSeries, period).");
+ throw new NotSupportedException("MASE requires two inputs. Use Batch(actualSeries, predictedSeries, period).");
}
public override void Prime(ReadOnlySpan source, TimeSpan? step = null)
@@ -179,7 +179,7 @@ public sealed class Mase : AbstractBase
Last = default;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -352,4 +352,11 @@ public sealed class Mase : AbstractBase
}
}
}
-}
+
+ public static (TSeries Results, Mase Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mase(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/mdae/Mdae.Tests.cs b/lib/errors/mdae/Mdae.Tests.cs
index 5c72f2fd..1b9c6013 100644
--- a/lib/errors/mdae/Mdae.Tests.cs
+++ b/lib/errors/mdae/Mdae.Tests.cs
@@ -192,7 +192,7 @@ public class MdaeTests
iterativeResults.Add(mdaeIterative.Update(actual, predicted).Value);
}
- var batchResults = Mdae.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var batchResults = Mdae.Batch(actualSeries, predictedSeries, DefaultPeriod);
Assert.Equal(100, iterativeResults.Count);
Assert.Equal(iterativeResults.Count, batchResults.Count);
@@ -237,7 +237,7 @@ public class MdaeTests
predictedArr[i] = pred;
}
- var tseriesResult = Mdae.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var tseriesResult = Mdae.Batch(actualSeries, predictedSeries, DefaultPeriod);
Mdae.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), DefaultPeriod);
for (int i = 0; i < 100; i++)
@@ -286,7 +286,7 @@ public class MdaeTests
predicted.Add(DateTime.UtcNow.Ticks, 98);
- Assert.Throws(() => Mdae.Calculate(actual, predicted, DefaultPeriod));
+ Assert.Throws(() => Mdae.Batch(actual, predicted, DefaultPeriod));
}
[Fact]
diff --git a/lib/errors/mdae/Mdae.cs b/lib/errors/mdae/Mdae.cs
index 953dc8f0..ea91d3b1 100644
--- a/lib/errors/mdae/Mdae.cs
+++ b/lib/errors/mdae/Mdae.cs
@@ -114,7 +114,7 @@ public sealed class Mdae : AbstractBase
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("MdAE requires two inputs. Use Calculate(actualSeries, predictedSeries, period).");
+ throw new NotSupportedException("MdAE requires two inputs. Use Batch(actualSeries, predictedSeries, period).");
}
public override void Prime(ReadOnlySpan source, TimeSpan? step = null)
@@ -161,7 +161,7 @@ public sealed class Mdae : AbstractBase
return (_sortBuffer[mid - 1] + _sortBuffer[mid]) * 0.5;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -299,6 +299,13 @@ public sealed class Mdae : AbstractBase
}
}
+ public static (TSeries Results, Mdae Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mdae(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
///
/// QuickSelect for Span - finds the k-th smallest element in O(n) average time.
/// Uses insertion sort for small arrays and Lomuto partition for larger arrays.
@@ -378,4 +385,4 @@ public sealed class Mdae : AbstractBase
return span[left];
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/mdape/Mdape.Tests.cs b/lib/errors/mdape/Mdape.Tests.cs
index 5f44f4d6..4f9228cf 100644
--- a/lib/errors/mdape/Mdape.Tests.cs
+++ b/lib/errors/mdape/Mdape.Tests.cs
@@ -194,7 +194,7 @@ public class MdapeTests
iterativeResults.Add(mdapeIterative.Update(actualSeries[i], predictedSeries[i]));
}
- var batchResults = Mdape.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var batchResults = Mdape.Batch(actualSeries, predictedSeries, DefaultPeriod);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < batchResults.Count; i++)
@@ -238,7 +238,7 @@ public class MdapeTests
predictedArr[i] = pred;
}
- var tseriesResult = Mdape.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var tseriesResult = Mdape.Batch(actualSeries, predictedSeries, DefaultPeriod);
Mdape.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), DefaultPeriod);
for (int i = 0; i < tseriesResult.Count; i++)
@@ -287,7 +287,7 @@ public class MdapeTests
predicted.Add(DateTime.UtcNow.Ticks, 98);
- Assert.Throws(() => Mdape.Calculate(actual, predicted, DefaultPeriod));
+ Assert.Throws(() => Mdape.Batch(actual, predicted, DefaultPeriod));
}
[Fact]
diff --git a/lib/errors/mdape/Mdape.cs b/lib/errors/mdape/Mdape.cs
index 34836c2b..e53e895b 100644
--- a/lib/errors/mdape/Mdape.cs
+++ b/lib/errors/mdape/Mdape.cs
@@ -68,7 +68,7 @@ public sealed class Mdape : AbstractBase
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("MdAPE requires two inputs. Use Calculate(actualSeries, predictedSeries, period).");
+ throw new NotSupportedException("MdAPE requires two inputs. Use Batch(actualSeries, predictedSeries, period).");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -164,7 +164,7 @@ public sealed class Mdape : AbstractBase
return (_sortBuffer[mid - 1] + _sortBuffer[mid]) * 0.5;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -263,6 +263,13 @@ public sealed class Mdape : AbstractBase
}
}
+ public static (TSeries Results, Mdape Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mdape(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
///
/// Dual-heap based sliding window median calculator.
/// Maintains O(log n) insert/remove and O(1) median query.
@@ -405,4 +412,4 @@ public sealed class Mdape : AbstractBase
}
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/me/Me.Tests.cs b/lib/errors/me/Me.Tests.cs
index bbed0415..977ad5d4 100644
--- a/lib/errors/me/Me.Tests.cs
+++ b/lib/errors/me/Me.Tests.cs
@@ -334,7 +334,7 @@ public class MeTests
predicted.Add(now.AddMinutes(i), i * 10 + 5);
}
- var results = Me.Calculate(actual, predicted, 3);
+ var results = Me.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
// All errors are -5, so ME should be -5
@@ -357,7 +357,7 @@ public class MeTests
predicted.Add(DateTime.UtcNow, i);
}
- Assert.Throws(() => Me.Calculate(actual, predicted, 3));
+ Assert.Throws(() => Me.Batch(actual, predicted, 3));
}
[Fact]
diff --git a/lib/errors/me/Me.cs b/lib/errors/me/Me.cs
index 9d06b631..d81a35dd 100644
--- a/lib/errors/me/Me.cs
+++ b/lib/errors/me/Me.cs
@@ -40,7 +40,7 @@ public sealed class Me : BiInputIndicatorBase
///
/// Calculates ME for entire series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -78,4 +78,11 @@ public sealed class Me : BiInputIndicatorBase
}
}
}
-}
+
+ public static (TSeries Results, Me Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Me(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/mpe/Mpe.Tests.cs b/lib/errors/mpe/Mpe.Tests.cs
index 5181de7b..13c414ba 100644
--- a/lib/errors/mpe/Mpe.Tests.cs
+++ b/lib/errors/mpe/Mpe.Tests.cs
@@ -236,7 +236,7 @@ public class MpeTests
iterativeResults.Add(mpeIterative.Update(actualSeries[i], predictedSeries[i]).Value);
}
- var batchResults = Mpe.Calculate(actualSeries, predictedSeries, 10);
+ var batchResults = Mpe.Batch(actualSeries, predictedSeries, 10);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -280,7 +280,7 @@ public class MpeTests
predictedSeries.Add(bar.Time, bar.Close * 0.95);
}
- var tseriesResult = Mpe.Calculate(actualSeries, predictedSeries, 10);
+ var tseriesResult = Mpe.Batch(actualSeries, predictedSeries, 10);
Mpe.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < 100; i++)
@@ -314,7 +314,7 @@ public class MpeTests
actual.Add(DateTime.UtcNow.Ticks + 1, 100);
predicted.Add(DateTime.UtcNow.Ticks, 90);
- Assert.Throws(() => Mpe.Calculate(actual, predicted, 5));
+ Assert.Throws(() => Mpe.Batch(actual, predicted, 5));
}
[Fact]
diff --git a/lib/errors/mpe/Mpe.cs b/lib/errors/mpe/Mpe.cs
index a02609b7..77e08e64 100644
--- a/lib/errors/mpe/Mpe.cs
+++ b/lib/errors/mpe/Mpe.cs
@@ -52,7 +52,7 @@ public sealed class Mpe : BiInputIndicatorBase
///
/// Calculates MPE for entire series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -78,6 +78,13 @@ public sealed class Mpe : BiInputIndicatorBase
ErrorHelpers.ApplyRollingMean(errors, output, period);
}
+ public static (TSeries Results, Mpe Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mpe(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeSignedPercentageErrors(ReadOnlySpan actual, ReadOnlySpan predicted, Span output)
{
@@ -139,4 +146,4 @@ public sealed class Mpe : BiInputIndicatorBase
output[i] = 100.0 * (act - pred) / divisor;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/mrae/Mrae.Tests.cs b/lib/errors/mrae/Mrae.Tests.cs
index 2bf6b1bb..3632c428 100644
--- a/lib/errors/mrae/Mrae.Tests.cs
+++ b/lib/errors/mrae/Mrae.Tests.cs
@@ -284,7 +284,7 @@ public class MraeTests
predicted.Add(now.AddMinutes(i), i * 110); // 10% error
}
- var results = Mrae.Calculate(actual, predicted, 3);
+ var results = Mrae.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
Assert.Equal(0.1, results.Last.Value, 10);
@@ -306,7 +306,7 @@ public class MraeTests
predicted.Add(DateTime.UtcNow, i * 10);
}
- Assert.Throws(() => Mrae.Calculate(actual, predicted, 3));
+ Assert.Throws(() => Mrae.Batch(actual, predicted, 3));
}
[Fact]
diff --git a/lib/errors/mrae/Mrae.cs b/lib/errors/mrae/Mrae.cs
index e3a918e9..98106245 100644
--- a/lib/errors/mrae/Mrae.cs
+++ b/lib/errors/mrae/Mrae.cs
@@ -49,7 +49,7 @@ public sealed class Mrae : BiInputIndicatorBase
///
/// Calculates Mean Relative Absolute Error for two time series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -104,6 +104,13 @@ public sealed class Mrae : BiInputIndicatorBase
ErrorHelpers.ApplyRollingMean(errors, output, period);
}
+ public static (TSeries Results, Mrae Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mrae(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
///
/// Computes relative errors (0-1 scale, not percentage).
///
@@ -163,4 +170,4 @@ public sealed class Mrae : BiInputIndicatorBase
: 0.0;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/mse/Mse.Tests.cs b/lib/errors/mse/Mse.Tests.cs
index 82a77a17..16c395fc 100644
--- a/lib/errors/mse/Mse.Tests.cs
+++ b/lib/errors/mse/Mse.Tests.cs
@@ -287,7 +287,7 @@ public class MseTests
predicted.Add(now.AddMinutes(i), i * 10 + 5);
}
- var results = Mse.Calculate(actual, predicted, 3);
+ var results = Mse.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
// All errors are 5², so MSE should be 25
diff --git a/lib/errors/mse/Mse.cs b/lib/errors/mse/Mse.cs
index c6561b78..4f30b7ea 100644
--- a/lib/errors/mse/Mse.cs
+++ b/lib/errors/mse/Mse.cs
@@ -46,7 +46,7 @@ public sealed class Mse : BiInputIndicatorBase
/// Predicted values series
/// MSE period
/// MSE series
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -78,4 +78,11 @@ public sealed class Mse : BiInputIndicatorBase
// Apply rolling mean using shared helper
ErrorHelpers.ApplyRollingMean(sqErrors, output, period);
}
-}
+
+ public static (TSeries Results, Mse Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Mse(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/msle/Msle.Tests.cs b/lib/errors/msle/Msle.Tests.cs
index 5b142ebd..5988d121 100644
--- a/lib/errors/msle/Msle.Tests.cs
+++ b/lib/errors/msle/Msle.Tests.cs
@@ -241,7 +241,7 @@ public class MsleTests
iterativeResults.Add(msleIterative.Update(actualSeries[i], predictedSeries[i]).Value);
}
- var batchResults = Msle.Calculate(actualSeries, predictedSeries, 10);
+ var batchResults = Msle.Batch(actualSeries, predictedSeries, 10);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -285,7 +285,7 @@ public class MsleTests
predictedSeries.Add(bar.Time, bar.Close * 0.95);
}
- var tseriesResult = Msle.Calculate(actualSeries, predictedSeries, 10);
+ var tseriesResult = Msle.Batch(actualSeries, predictedSeries, 10);
Msle.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < 100; i++)
@@ -319,7 +319,7 @@ public class MsleTests
actual.Add(DateTime.UtcNow.Ticks + 1, 100);
predicted.Add(DateTime.UtcNow.Ticks, 90);
- Assert.Throws(() => Msle.Calculate(actual, predicted, 5));
+ Assert.Throws(() => Msle.Batch(actual, predicted, 5));
}
[Fact]
diff --git a/lib/errors/msle/Msle.cs b/lib/errors/msle/Msle.cs
index 0488c421..7c884711 100644
--- a/lib/errors/msle/Msle.cs
+++ b/lib/errors/msle/Msle.cs
@@ -46,7 +46,7 @@ public sealed class Msle : BiInputIndicatorBase
///
/// Calculates MSLE for entire series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -72,6 +72,13 @@ public sealed class Msle : BiInputIndicatorBase
ErrorHelpers.ApplyRollingMean(errors, output, period);
}
+ public static (TSeries Results, Msle Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Msle(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeLogSquaredErrors(ReadOnlySpan actual, ReadOnlySpan predicted, Span output)
{
@@ -127,4 +134,4 @@ public sealed class Msle : BiInputIndicatorBase
output[i] = logError * logError;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/pseudohuber/PseudoHuber.Tests.cs b/lib/errors/pseudohuber/PseudoHuber.Tests.cs
index 74dfe88f..748c12b0 100644
--- a/lib/errors/pseudohuber/PseudoHuber.Tests.cs
+++ b/lib/errors/pseudohuber/PseudoHuber.Tests.cs
@@ -342,7 +342,7 @@ public class PseudoHuberTests
}
// Calculate batch
- var batchResults = PseudoHuber.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var batchResults = PseudoHuber.Batch(actualSeries, predictedSeries, DefaultPeriod);
// Compare
Assert.Equal(iterativeResults.Count, batchResults.Count);
@@ -389,7 +389,7 @@ public class PseudoHuberTests
predictedSeries.Add(bar.Time, predictedData[i]);
}
- var tseriesResult = PseudoHuber.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var tseriesResult = PseudoHuber.Batch(actualSeries, predictedSeries, DefaultPeriod);
PseudoHuber.Batch(actualData.AsSpan(), predictedData.AsSpan(), output.AsSpan(), DefaultPeriod);
for (int i = 0; i < 100; i++)
@@ -445,7 +445,7 @@ public class PseudoHuberTests
actual.Add(DateTime.UtcNow, 101);
predicted.Add(DateTime.UtcNow, 99);
- Assert.Throws(() => PseudoHuber.Calculate(actual, predicted, 5));
+ Assert.Throws(() => PseudoHuber.Batch(actual, predicted, 5));
}
#endregion
diff --git a/lib/errors/pseudohuber/PseudoHuber.cs b/lib/errors/pseudohuber/PseudoHuber.cs
index 9bb7d7c2..9ca37aa0 100644
--- a/lib/errors/pseudohuber/PseudoHuber.cs
+++ b/lib/errors/pseudohuber/PseudoHuber.cs
@@ -64,7 +64,7 @@ public sealed class PseudoHuber : BiInputIndicatorBase
///
/// Calculates Pseudo-Huber Loss for two time series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period, double delta = 1.0)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period, double delta = 1.0)
{
if (actual.Count != predicted.Count)
{
@@ -124,4 +124,11 @@ public sealed class PseudoHuber : BiInputIndicatorBase
// Apply rolling mean
ErrorHelpers.ApplyRollingMean(errors, output, period);
}
-}
+
+ public static (TSeries Results, PseudoHuber Indicator) Calculate(TSeries actual, TSeries predicted, int period, double delta = 1.0)
+ {
+ var indicator = new PseudoHuber(period, delta);
+ TSeries results = Batch(actual, predicted, period, delta);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/quantile/QuantileLoss.Tests.cs b/lib/errors/quantile/QuantileLoss.Tests.cs
index 84ec2767..6187feca 100644
--- a/lib/errors/quantile/QuantileLoss.Tests.cs
+++ b/lib/errors/quantile/QuantileLoss.Tests.cs
@@ -237,7 +237,7 @@ public class QuantileLossTests
var iterativeResults = actualSeries.Zip(predictedSeries, (actual, predicted) => quantileLossIterative.Update(actual.Value, predicted.Value).Value).ToList();
- var batchResults = QuantileLoss.Calculate(actualSeries, predictedSeries, DefaultPeriod, 0.75);
+ var batchResults = QuantileLoss.Batch(actualSeries, predictedSeries, DefaultPeriod, 0.75);
Assert.Equal(iterativeResults.Count, batchResults.Count);
int count = iterativeResults.Count;
@@ -288,7 +288,7 @@ public class QuantileLossTests
predictedArr[i] = pred;
}
- var tseriesResult = QuantileLoss.Calculate(actualSeries, predictedSeries, DefaultPeriod, 0.75);
+ var tseriesResult = QuantileLoss.Batch(actualSeries, predictedSeries, DefaultPeriod, 0.75);
QuantileLoss.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), DefaultPeriod, 0.75);
for (int i = 0; i < 100; i++)
@@ -337,7 +337,7 @@ public class QuantileLossTests
predicted.Add(DateTime.UtcNow.Ticks, 98);
- Assert.Throws(() => QuantileLoss.Calculate(actual, predicted, DefaultPeriod));
+ Assert.Throws(() => QuantileLoss.Batch(actual, predicted, DefaultPeriod));
}
[Fact]
diff --git a/lib/errors/quantile/QuantileLoss.cs b/lib/errors/quantile/QuantileLoss.cs
index 9e214abb..ec8e8a4e 100644
--- a/lib/errors/quantile/QuantileLoss.cs
+++ b/lib/errors/quantile/QuantileLoss.cs
@@ -60,7 +60,7 @@ public sealed class QuantileLoss : BiInputIndicatorBase
return diff >= 0 ? Quantile * diff : (Quantile - 1.0) * diff;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period, double quantile = 0.5)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period, double quantile = 0.5)
{
if (actual.Count != predicted.Count)
{
@@ -220,4 +220,11 @@ public sealed class QuantileLoss : BiInputIndicatorBase
}
}
}
-}
+
+ public static (TSeries Results, QuantileLoss Indicator) Calculate(TSeries actual, TSeries predicted, int period, double quantile = 0.5)
+ {
+ var indicator = new QuantileLoss(period, quantile);
+ TSeries results = Batch(actual, predicted, period, quantile);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/rae/Rae.Tests.cs b/lib/errors/rae/Rae.Tests.cs
index 37837b3e..801f4396 100644
--- a/lib/errors/rae/Rae.Tests.cs
+++ b/lib/errors/rae/Rae.Tests.cs
@@ -230,7 +230,7 @@ public class RaeTests
iterativeResults.Add(raeIterative.Update(actual[i], predicted[i]).Value);
}
- var batchResults = Rae.Calculate(actual, predicted, Period);
+ var batchResults = Rae.Batch(actual, predicted, Period);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -271,7 +271,7 @@ public class RaeTests
double[] predictedArr = predictedSeries.Values.ToArray();
double[] output = new double[100];
- var tseriesResult = Rae.Calculate(actualSeries, predictedSeries, Period);
+ var tseriesResult = Rae.Batch(actualSeries, predictedSeries, Period);
Rae.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), Period);
for (int i = 0; i < 100; i++)
@@ -292,7 +292,7 @@ public class RaeTests
}
// 1. Batch Mode (static method)
- var batchSeries = Rae.Calculate(actualSeries, predictedSeries, Period);
+ var batchSeries = Rae.Batch(actualSeries, predictedSeries, Period);
double expected = batchSeries.Last.Value;
// 2. Span Mode
diff --git a/lib/errors/rae/Rae.cs b/lib/errors/rae/Rae.cs
index a5ed02ba..898ad6f4 100644
--- a/lib/errors/rae/Rae.cs
+++ b/lib/errors/rae/Rae.cs
@@ -159,7 +159,7 @@ public sealed class Rae : AbstractBase
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("RAE requires two inputs. Use Calculate(actualSeries, predictedSeries, period).");
+ throw new NotSupportedException("RAE requires two inputs. Use Batch(actualSeries, predictedSeries, period).");
}
public override void Prime(ReadOnlySpan source, TimeSpan? step = null)
@@ -177,7 +177,7 @@ public sealed class Rae : AbstractBase
Last = default;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -355,4 +355,11 @@ public sealed class Rae : AbstractBase
}
}
}
+
+ public static (TSeries Results, Rae Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Rae(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
}
\ No newline at end of file
diff --git a/lib/errors/rmse/Rmse.Tests.cs b/lib/errors/rmse/Rmse.Tests.cs
index c7e6067a..f78d23fd 100644
--- a/lib/errors/rmse/Rmse.Tests.cs
+++ b/lib/errors/rmse/Rmse.Tests.cs
@@ -241,7 +241,7 @@ public class RmseTests
predicted.Add(now.AddMinutes(i), i * 10 + 5);
}
- var results = Rmse.Calculate(actual, predicted, 3);
+ var results = Rmse.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
// All errors are 5, MSE = 25, RMSE = 5
diff --git a/lib/errors/rmse/Rmse.cs b/lib/errors/rmse/Rmse.cs
index 51fe3c7c..43133cef 100644
--- a/lib/errors/rmse/Rmse.cs
+++ b/lib/errors/rmse/Rmse.cs
@@ -45,7 +45,7 @@ public sealed class Rmse : BiInputIndicatorBase
///
/// Calculates RMSE for entire series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -70,4 +70,11 @@ public sealed class Rmse : BiInputIndicatorBase
ErrorHelpers.ComputeSquaredErrors(actual, predicted, sqErrors);
ErrorHelpers.ApplyRollingMeanSqrt(sqErrors, output, period);
}
-}
+
+ public static (TSeries Results, Rmse Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Rmse(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/rmsle/Rmsle.Tests.cs b/lib/errors/rmsle/Rmsle.Tests.cs
index a65b99ca..3330715c 100644
--- a/lib/errors/rmsle/Rmsle.Tests.cs
+++ b/lib/errors/rmsle/Rmsle.Tests.cs
@@ -222,7 +222,7 @@ public class RmsleTests
iterativeResults.Add(rmsleIterative.Update(actualSeries[i], predictedSeries[i]).Value);
}
- var batchResults = Rmsle.Calculate(actualSeries, predictedSeries, 10);
+ var batchResults = Rmsle.Batch(actualSeries, predictedSeries, 10);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -266,7 +266,7 @@ public class RmsleTests
predictedSeries.Add(bar.Time, bar.Close * 0.95);
}
- var tseriesResult = Rmsle.Calculate(actualSeries, predictedSeries, 10);
+ var tseriesResult = Rmsle.Batch(actualSeries, predictedSeries, 10);
Rmsle.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < 100; i++)
@@ -300,7 +300,7 @@ public class RmsleTests
actual.Add(DateTime.UtcNow.Ticks + 1, 100);
predicted.Add(DateTime.UtcNow.Ticks, 90);
- Assert.Throws(() => Rmsle.Calculate(actual, predicted, 5));
+ Assert.Throws(() => Rmsle.Batch(actual, predicted, 5));
}
[Fact]
diff --git a/lib/errors/rmsle/Rmsle.cs b/lib/errors/rmsle/Rmsle.cs
index 07e0b1de..c51bd86b 100644
--- a/lib/errors/rmsle/Rmsle.cs
+++ b/lib/errors/rmsle/Rmsle.cs
@@ -49,7 +49,7 @@ public sealed class Rmsle : BiInputIndicatorBase
///
/// Calculates RMSLE for entire series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -75,6 +75,13 @@ public sealed class Rmsle : BiInputIndicatorBase
ErrorHelpers.ApplyRollingMeanSqrt(errors, output, period);
}
+ public static (TSeries Results, Rmsle Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Rmsle(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeLogSquaredErrors(ReadOnlySpan actual, ReadOnlySpan predicted, Span output)
{
@@ -130,4 +137,4 @@ public sealed class Rmsle : BiInputIndicatorBase
output[i] = logError * logError;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/rse/Rse.Tests.cs b/lib/errors/rse/Rse.Tests.cs
index ce2f340d..ec784ad3 100644
--- a/lib/errors/rse/Rse.Tests.cs
+++ b/lib/errors/rse/Rse.Tests.cs
@@ -253,7 +253,7 @@ public class RseTests
iterativeResults.Add(rseIterative.Update(actual[i], predicted[i]).Value);
}
- var batchResults = Rse.Calculate(actual, predicted, Period);
+ var batchResults = Rse.Batch(actual, predicted, Period);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -294,7 +294,7 @@ public class RseTests
double[] predictedArr = predictedSeries.Values.ToArray();
double[] output = new double[100];
- var tseriesResult = Rse.Calculate(actualSeries, predictedSeries, Period);
+ var tseriesResult = Rse.Batch(actualSeries, predictedSeries, Period);
Rse.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), Period);
for (int i = 0; i < 100; i++)
@@ -315,7 +315,7 @@ public class RseTests
}
// 1. Batch Mode (static method)
- var batchSeries = Rse.Calculate(actualSeries, predictedSeries, Period);
+ var batchSeries = Rse.Batch(actualSeries, predictedSeries, Period);
double expected = batchSeries.Last.Value;
// 2. Span Mode
diff --git a/lib/errors/rse/Rse.cs b/lib/errors/rse/Rse.cs
index f122e9db..8037f11e 100644
--- a/lib/errors/rse/Rse.cs
+++ b/lib/errors/rse/Rse.cs
@@ -166,7 +166,7 @@ public sealed class Rse : AbstractBase
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("RSE requires two inputs. Use Calculate(actualSeries, predictedSeries, period).");
+ throw new NotSupportedException("RSE requires two inputs. Use Batch(actualSeries, predictedSeries, period).");
}
public override void Prime(ReadOnlySpan source, TimeSpan? step = null)
@@ -184,7 +184,7 @@ public sealed class Rse : AbstractBase
Last = default;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -366,4 +366,11 @@ public sealed class Rse : AbstractBase
}
}
}
-}
+
+ public static (TSeries Results, Rse Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Rse(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/rsquared/Rsquared.Tests.cs b/lib/errors/rsquared/Rsquared.Tests.cs
index 86d44f3b..cbab3b15 100644
--- a/lib/errors/rsquared/Rsquared.Tests.cs
+++ b/lib/errors/rsquared/Rsquared.Tests.cs
@@ -290,7 +290,7 @@ public class RsquaredTests
iterativeResults.Add(r2Iterative.Update(actual[i], predicted[i]).Value);
}
- var batchResults = Rsquared.Calculate(actual, predicted, Period);
+ var batchResults = Rsquared.Batch(actual, predicted, Period);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -331,7 +331,7 @@ public class RsquaredTests
double[] predictedArr = predictedSeries.Values.ToArray();
double[] output = new double[100];
- var tseriesResult = Rsquared.Calculate(actualSeries, predictedSeries, Period);
+ var tseriesResult = Rsquared.Batch(actualSeries, predictedSeries, Period);
Rsquared.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), Period);
for (int i = 0; i < 100; i++)
@@ -352,7 +352,7 @@ public class RsquaredTests
}
// 1. Batch Mode (static method)
- var batchSeries = Rsquared.Calculate(actualSeries, predictedSeries, Period);
+ var batchSeries = Rsquared.Batch(actualSeries, predictedSeries, Period);
double expected = batchSeries.Last.Value;
// 2. Span Mode
diff --git a/lib/errors/rsquared/Rsquared.cs b/lib/errors/rsquared/Rsquared.cs
index 58be0cd9..addb7218 100644
--- a/lib/errors/rsquared/Rsquared.cs
+++ b/lib/errors/rsquared/Rsquared.cs
@@ -160,7 +160,7 @@ public sealed class Rsquared : AbstractBase
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("R² requires two inputs. Use Calculate(actualSeries, predictedSeries, period).");
+ throw new NotSupportedException("R² requires two inputs. Use Batch(actualSeries, predictedSeries, period).");
}
public override void Prime(ReadOnlySpan source, TimeSpan? step = null)
@@ -178,7 +178,7 @@ public sealed class Rsquared : AbstractBase
Last = default;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -360,4 +360,11 @@ public sealed class Rsquared : AbstractBase
}
}
}
-}
+
+ public static (TSeries Results, Rsquared Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Rsquared(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/smape/Smape.Tests.cs b/lib/errors/smape/Smape.Tests.cs
index 389b2eee..4d452388 100644
--- a/lib/errors/smape/Smape.Tests.cs
+++ b/lib/errors/smape/Smape.Tests.cs
@@ -217,7 +217,7 @@ public class SmapeTests
iterativeResults.Add(smapeIterative.Update(actualSeries[i], predictedSeries[i]).Value);
}
- var batchResults = Smape.Calculate(actualSeries, predictedSeries, 10);
+ var batchResults = Smape.Batch(actualSeries, predictedSeries, 10);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -261,7 +261,7 @@ public class SmapeTests
predictedSeries.Add(bar.Time, bar.Close * 0.95);
}
- var tseriesResult = Smape.Calculate(actualSeries, predictedSeries, 10);
+ var tseriesResult = Smape.Batch(actualSeries, predictedSeries, 10);
Smape.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), 10);
for (int i = 0; i < 100; i++)
@@ -295,7 +295,7 @@ public class SmapeTests
actual.Add(DateTime.UtcNow.Ticks + 1, 100);
predicted.Add(DateTime.UtcNow.Ticks, 90);
- Assert.Throws(() => Smape.Calculate(actual, predicted, 5));
+ Assert.Throws(() => Smape.Batch(actual, predicted, 5));
}
[Fact]
diff --git a/lib/errors/smape/Smape.cs b/lib/errors/smape/Smape.cs
index 315c1a7f..7e425826 100644
--- a/lib/errors/smape/Smape.cs
+++ b/lib/errors/smape/Smape.cs
@@ -43,7 +43,7 @@ public sealed class Smape : BiInputIndicatorBase
///
/// Calculates SMAPE for entire series.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
=> CalculateImpl(actual, predicted, period, Batch);
///
@@ -70,6 +70,13 @@ public sealed class Smape : BiInputIndicatorBase
ErrorHelpers.ApplyRollingMean(symErrors, output, period);
}
+ public static (TSeries Results, Smape Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Smape(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeSmapeErrors(ReadOnlySpan actual, ReadOnlySpan predicted, Span output)
{
@@ -122,4 +129,4 @@ public sealed class Smape : BiInputIndicatorBase
output[i] = sumAbs > Epsilon ? 200.0 * absDiff / sumAbs : 0.0;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/errors/theilu/TheilU.Tests.cs b/lib/errors/theilu/TheilU.Tests.cs
index adbdc030..b7b2e5c9 100644
--- a/lib/errors/theilu/TheilU.Tests.cs
+++ b/lib/errors/theilu/TheilU.Tests.cs
@@ -192,7 +192,7 @@ public class TheilUTests
iterativeResults.Add(theilUIterative.Update(new TValue(bar.Time, bar.Close), new TValue(bar.Time, predicted)).Value);
}
- var batchResults = TheilU.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var batchResults = TheilU.Batch(actualSeries, predictedSeries, DefaultPeriod);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -236,7 +236,7 @@ public class TheilUTests
predictedArr[i] = pred;
}
- var tseriesResult = TheilU.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var tseriesResult = TheilU.Batch(actualSeries, predictedSeries, DefaultPeriod);
TheilU.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), DefaultPeriod);
for (int i = 0; i < 100; i++)
@@ -285,7 +285,7 @@ public class TheilUTests
predicted.Add(DateTime.UtcNow.Ticks, 98);
- Assert.Throws(() => TheilU.Calculate(actual, predicted, DefaultPeriod));
+ Assert.Throws(() => TheilU.Batch(actual, predicted, DefaultPeriod));
}
[Fact]
diff --git a/lib/errors/theilu/TheilU.cs b/lib/errors/theilu/TheilU.cs
index 139c4659..b21ca932 100644
--- a/lib/errors/theilu/TheilU.cs
+++ b/lib/errors/theilu/TheilU.cs
@@ -75,7 +75,7 @@ public sealed class TheilU : AbstractBase
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("TheilU requires two inputs. Use Calculate(actualSeries, predictedSeries, period).");
+ throw new NotSupportedException("TheilU requires two inputs. Use Batch(actualSeries, predictedSeries, period).");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -170,7 +170,7 @@ public sealed class TheilU : AbstractBase
Last = default;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -352,4 +352,11 @@ public sealed class TheilU : AbstractBase
}
}
}
-}
+
+ public static (TSeries Results, TheilU Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new TheilU(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/tukey/TukeyBiweight.Tests.cs b/lib/errors/tukey/TukeyBiweight.Tests.cs
index ecd70ce0..d0863d0c 100644
--- a/lib/errors/tukey/TukeyBiweight.Tests.cs
+++ b/lib/errors/tukey/TukeyBiweight.Tests.cs
@@ -235,7 +235,7 @@ public class TukeyBiweightTests
iterativeResults.Add(tukeyIterative.Update(actual, predicted).Value);
}
- var batchResults = TukeyBiweight.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var batchResults = TukeyBiweight.Batch(actualSeries, predictedSeries, DefaultPeriod);
Assert.Equal(iterativeResults.Count, batchResults.Count);
for (int i = 0; i < iterativeResults.Count; i++)
@@ -282,7 +282,7 @@ public class TukeyBiweightTests
predictedArr[i] = pred;
}
- var tseriesResult = TukeyBiweight.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var tseriesResult = TukeyBiweight.Batch(actualSeries, predictedSeries, DefaultPeriod);
TukeyBiweight.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), DefaultPeriod);
for (int i = 0; i < 100; i++)
@@ -331,7 +331,7 @@ public class TukeyBiweightTests
predicted.Add(DateTime.UtcNow.Ticks, 98);
- Assert.Throws(() => TukeyBiweight.Calculate(actual, predicted, DefaultPeriod));
+ Assert.Throws(() => TukeyBiweight.Batch(actual, predicted, DefaultPeriod));
}
[Fact]
diff --git a/lib/errors/tukey/TukeyBiweight.cs b/lib/errors/tukey/TukeyBiweight.cs
index 0768390f..8a3d0bda 100644
--- a/lib/errors/tukey/TukeyBiweight.cs
+++ b/lib/errors/tukey/TukeyBiweight.cs
@@ -66,7 +66,7 @@ public sealed class TukeyBiweight : BiInputIndicatorBase
return _cSquaredOver6 * (1.0 - cubed);
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period, double c = DefaultC)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period, double c = DefaultC)
{
if (actual.Count != predicted.Count)
{
@@ -129,4 +129,11 @@ public sealed class TukeyBiweight : BiInputIndicatorBase
ArrayPool.Shared.Return(rented, clearArray: false);
}
}
-}
+
+ public static (TSeries Results, TukeyBiweight Indicator) Calculate(TSeries actual, TSeries predicted, int period, double c = DefaultC)
+ {
+ var indicator = new TukeyBiweight(period, c);
+ TSeries results = Batch(actual, predicted, period, c);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/wmape/Wmape.Tests.cs b/lib/errors/wmape/Wmape.Tests.cs
index 42a9e4a7..98b35813 100644
--- a/lib/errors/wmape/Wmape.Tests.cs
+++ b/lib/errors/wmape/Wmape.Tests.cs
@@ -186,7 +186,7 @@ public class WmapeTests
predictedSeries.Add(bar.Time, bar.Close * (1 + (i % 2 == 0 ? 0.02 : -0.02)));
}
- var batchResults = Wmape.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var batchResults = Wmape.Batch(actualSeries, predictedSeries, DefaultPeriod);
var iterativeResults = new List();
for (int i = 0; i < actualSeries.Count; i++)
@@ -236,7 +236,7 @@ public class WmapeTests
predictedArr[i] = pred;
}
- var tseriesResult = Wmape.Calculate(actualSeries, predictedSeries, DefaultPeriod);
+ var tseriesResult = Wmape.Batch(actualSeries, predictedSeries, DefaultPeriod);
Wmape.Batch(actualArr.AsSpan(), predictedArr.AsSpan(), output.AsSpan(), DefaultPeriod);
for (int i = 0; i < 100; i++)
@@ -285,7 +285,7 @@ public class WmapeTests
predicted.Add(DateTime.UtcNow.Ticks, 98);
- Assert.Throws(() => Wmape.Calculate(actual, predicted, DefaultPeriod));
+ Assert.Throws(() => Wmape.Batch(actual, predicted, DefaultPeriod));
}
[Fact]
diff --git a/lib/errors/wmape/Wmape.cs b/lib/errors/wmape/Wmape.cs
index 9363977d..12fdcd56 100644
--- a/lib/errors/wmape/Wmape.cs
+++ b/lib/errors/wmape/Wmape.cs
@@ -138,7 +138,7 @@ public sealed class Wmape : AbstractBase
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("WMAPE requires two inputs. Use Calculate(actualSeries, predictedSeries, period).");
+ throw new NotSupportedException("WMAPE requires two inputs. Use Batch(actualSeries, predictedSeries, period).");
}
public override void Prime(ReadOnlySpan source, TimeSpan? step = null)
@@ -155,7 +155,7 @@ public sealed class Wmape : AbstractBase
Last = default;
}
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -345,4 +345,11 @@ public sealed class Wmape : AbstractBase
}
}
}
-}
+
+ public static (TSeries Results, Wmape Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Wmape(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/errors/wrmse/Wrmse.Tests.cs b/lib/errors/wrmse/Wrmse.Tests.cs
index dd22b0c2..b84ce29e 100644
--- a/lib/errors/wrmse/Wrmse.Tests.cs
+++ b/lib/errors/wrmse/Wrmse.Tests.cs
@@ -320,7 +320,7 @@ public class WrmseTests
predicted.Add(now.AddMinutes(i), i * 10 + 5);
}
- var results = Wrmse.Calculate(actual, predicted, 3);
+ var results = Wrmse.Batch(actual, predicted, 3);
Assert.Equal(10, results.Count);
// All errors are 5, MSE = 25, RMSE = 5
@@ -342,7 +342,7 @@ public class WrmseTests
weights.Add(now.AddMinutes(i), 2.0); // Weight = 2
}
- var results = Wrmse.Calculate(actual, predicted, weights, 3);
+ var results = Wrmse.Batch(actual, predicted, weights, 3);
Assert.Equal(10, results.Count);
// Weighted error = 2 * 100 = 200 per point, sum weights = 6 (period=3)
@@ -366,7 +366,7 @@ public class WrmseTests
}
}
- Assert.Throws(() => Wrmse.Calculate(actual, predicted, 3));
+ Assert.Throws(() => Wrmse.Batch(actual, predicted, 3));
}
[Fact]
@@ -387,7 +387,7 @@ public class WrmseTests
}
}
- Assert.Throws(() => Wrmse.Calculate(actual, predicted, weights, 3));
+ Assert.Throws(() => Wrmse.Batch(actual, predicted, weights, 3));
}
[Fact]
diff --git a/lib/errors/wrmse/Wrmse.cs b/lib/errors/wrmse/Wrmse.cs
index e917ae8d..397e3706 100644
--- a/lib/errors/wrmse/Wrmse.cs
+++ b/lib/errors/wrmse/Wrmse.cs
@@ -197,7 +197,7 @@ public sealed class Wrmse : AbstractBase
///
public override TSeries Update(TSeries source)
{
- throw new NotSupportedException("WRMSE requires two inputs. Use Calculate(actualSeries, predictedSeries, period) or Calculate(actualSeries, predictedSeries, weightsSeries, period).");
+ throw new NotSupportedException("WRMSE requires two inputs. Use Batch(actualSeries, predictedSeries, period) or Batch(actualSeries, predictedSeries, weightsSeries, period).");
}
///
@@ -221,7 +221,7 @@ public sealed class Wrmse : AbstractBase
///
/// Calculates WRMSE for entire series with uniform weights.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, int period)
{
if (actual.Count != predicted.Count)
{
@@ -246,7 +246,7 @@ public sealed class Wrmse : AbstractBase
///
/// Calculates WRMSE for entire series with custom weights.
///
- public static TSeries Calculate(TSeries actual, TSeries predicted, TSeries weights, int period)
+ public static TSeries Batch(TSeries actual, TSeries predicted, TSeries weights, int period)
{
if (actual.Count != predicted.Count || actual.Count != weights.Count)
{
@@ -332,4 +332,11 @@ public sealed class Wrmse : AbstractBase
ErrorHelpers.ComputeWeightedErrors(actual, predicted, weights, weightedErrors);
ErrorHelpers.ApplyRollingWeightedMeanSqrt(weightedErrors, weights, output, period);
}
-}
+
+ public static (TSeries Results, Wrmse Indicator) Calculate(TSeries actual, TSeries predicted, int period)
+ {
+ var indicator = new Wrmse(period);
+ TSeries results = Batch(actual, predicted, period);
+ return (results, indicator);
+ }
+}
\ No newline at end of file
diff --git a/lib/filters/bessel/Bessel.Tests.cs b/lib/filters/bessel/Bessel.Tests.cs
index beaca57a..5567e84d 100644
--- a/lib/filters/bessel/Bessel.Tests.cs
+++ b/lib/filters/bessel/Bessel.Tests.cs
@@ -28,11 +28,11 @@ public class BesselTests
double[] output = new double[5];
var exLength = Assert.Throws(() =>
- Bessel.Calculate(source.AsSpan(), output.AsSpan(), 1));
+ Bessel.Batch(source.AsSpan(), output.AsSpan(), 1));
Assert.Equal("length", exLength.ParamName);
var exLengthZero = Assert.Throws(() =>
- Bessel.Calculate(source.AsSpan(), output.AsSpan(), 0));
+ Bessel.Batch(source.AsSpan(), output.AsSpan(), 0));
Assert.Equal("length", exLengthZero.ParamName);
}
@@ -43,7 +43,7 @@ public class BesselTests
double[] wrongSizeOutput = new double[3];
var ex = Assert.Throws(() =>
- Bessel.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 14));
+ Bessel.Batch(source.AsSpan(), wrongSizeOutput.AsSpan(), 14));
Assert.Equal("output", ex.ParamName);
}
@@ -252,7 +252,7 @@ public class BesselTests
var tseriesResult = Bessel.Calculate(series, 14).Results;
- Bessel.Calculate(source.AsSpan(), output.AsSpan(), 14);
+ Bessel.Batch(source.AsSpan(), output.AsSpan(), 14);
for (int i = 0; i < 100; i++)
{
@@ -276,7 +276,7 @@ public class BesselTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan(tValues);
var spanOutput = new double[tValues.Length];
- Bessel.Calculate(spanInput, spanOutput, length);
+ Bessel.Batch(spanInput, spanOutput, length);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
diff --git a/lib/filters/bessel/Bessel.Validation.Tests.cs b/lib/filters/bessel/Bessel.Validation.Tests.cs
index ab034657..1ab978ac 100644
--- a/lib/filters/bessel/Bessel.Validation.Tests.cs
+++ b/lib/filters/bessel/Bessel.Validation.Tests.cs
@@ -47,7 +47,7 @@ public sealed class BesselValidationTests : IDisposable
// Same data via Span API
var src = _testData.Data.Values.ToArray();
var outSpan = new double[src.Length];
- Bessel.Calculate(src.AsSpan(), outSpan.AsSpan(), length);
+ Bessel.Batch(src.AsSpan(), outSpan.AsSpan(), length);
// Verify last window for convergence and consistency
ValidationHelper.VerifyData(qResult, outSpan, lookback: 0, skip: length, tolerance: ValidationHelper.DefaultTolerance);
diff --git a/lib/filters/bessel/Bessel.cs b/lib/filters/bessel/Bessel.cs
index ee61f6c0..6e7aa77a 100644
--- a/lib/filters/bessel/Bessel.cs
+++ b/lib/filters/bessel/Bessel.cs
@@ -481,12 +481,6 @@ public sealed class Bessel : AbstractBase
/// Complexity: O(n) where n = source.Count
/// The returned indicator maintains state and can continue processing new values.
///
- public static (TSeries Results, Bessel Indicator) Calculate(TSeries source, int length)
- {
- var bessel = new Bessel(length);
- TSeries results = bessel.Update(source);
- return (results, bessel);
- }
///
/// Calculates filtered values for a span of doubles (stateless batch processing).
@@ -504,7 +498,7 @@ public sealed class Bessel : AbstractBase
/// SIMD optimization is not applicable due to IIR recursive data dependency.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Calculate(ReadOnlySpan source, Span output, int length)
+ public static void Batch(ReadOnlySpan source, Span output, int length)
{
if (length < 2)
{
@@ -531,6 +525,12 @@ public sealed class Bessel : AbstractBase
CalculateCore(source, output, c1, c2, c3, length, ref state);
}
+ public static (TSeries Results, Bessel Indicator) Calculate(TSeries source, int length)
+ {
+ var bessel = new Bessel(length);
+ TSeries results = bessel.Update(source);
+ return (results, bessel);
+ }
///
/// Event handler for reactive updates from subscribed publishers.
@@ -567,4 +567,4 @@ public sealed class Bessel : AbstractBase
}
base.Dispose(disposing);
}
-}
+}
\ No newline at end of file
diff --git a/lib/filters/bilateral/Bilateral.Tests.cs b/lib/filters/bilateral/Bilateral.Tests.cs
index e1d70cd0..b09bf0aa 100644
--- a/lib/filters/bilateral/Bilateral.Tests.cs
+++ b/lib/filters/bilateral/Bilateral.Tests.cs
@@ -151,7 +151,7 @@ public class BilateralTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan(tValues);
var spanOutput = new double[tValues.Length];
- Bilateral.Calculate(spanInput, spanOutput, period);
+ Bilateral.Batch(spanInput, spanOutput, period);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
diff --git a/lib/filters/bilateral/Bilateral.Validation.Tests.cs b/lib/filters/bilateral/Bilateral.Validation.Tests.cs
index e64aed48..0b9563f0 100644
--- a/lib/filters/bilateral/Bilateral.Validation.Tests.cs
+++ b/lib/filters/bilateral/Bilateral.Validation.Tests.cs
@@ -96,7 +96,7 @@ public sealed class BilateralValidationTests : IDisposable
{
// Calculate QuanTAlib Bilateral (Span API)
double[] qOutput = new double[sourceData.Length];
- global::QuanTAlib.Bilateral.Calculate(sourceData.AsSpan(), qOutput.AsSpan(), period, sigmaSRatio, sigmaRMult);
+ global::QuanTAlib.Bilateral.Batch(sourceData.AsSpan(), qOutput.AsSpan(), period, sigmaSRatio, sigmaRMult);
// Calculate Reference Bilateral
var refResult = GetReferenceData(period, sigmaSRatio, sigmaRMult);
diff --git a/lib/filters/bilateral/Bilateral.cs b/lib/filters/bilateral/Bilateral.cs
index 71ffa93a..0c46e767 100644
--- a/lib/filters/bilateral/Bilateral.cs
+++ b/lib/filters/bilateral/Bilateral.cs
@@ -332,17 +332,11 @@ public sealed class Bilateral : AbstractBase
///
/// Calculates bilateral filter values for a TSeries and returns both results and a primed indicator.
///
- public static (TSeries Results, Bilateral Indicator) Calculate(TSeries source, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
- {
- var indicator = new Bilateral(period, sigmaSRatio, sigmaRMult);
- var results = indicator.Update(source);
- return (results, indicator);
- }
///
/// Calculates bilateral filter values using spans (zero allocation in hot path).
///
- public static void Calculate(ReadOnlySpan source, Span destination, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
+ private static void BatchCore(ReadOnlySpan source, Span destination, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
{
if (period <= 0)
{
@@ -520,7 +514,13 @@ public sealed class Bilateral : AbstractBase
///
public static void Batch(ReadOnlySpan source, Span destination, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
{
- Calculate(source, destination, period, sigmaSRatio, sigmaRMult);
+ BatchCore(source, destination, period, sigmaSRatio, sigmaRMult);
+ }
+ public static (TSeries Results, Bilateral Indicator) Calculate(TSeries source, int period, double sigmaSRatio = 0.5, double sigmaRMult = 1.0)
+ {
+ var indicator = new Bilateral(period, sigmaSRatio, sigmaRMult);
+ var results = indicator.Update(source);
+ return (results, indicator);
}
///
@@ -534,4 +534,4 @@ public sealed class Bilateral : AbstractBase
}
base.Dispose(disposing);
}
-}
+}
\ No newline at end of file
diff --git a/lib/filters/bpf/Bpf.Tests.cs b/lib/filters/bpf/Bpf.Tests.cs
index 28abce10..b24dabcc 100644
--- a/lib/filters/bpf/Bpf.Tests.cs
+++ b/lib/filters/bpf/Bpf.Tests.cs
@@ -39,7 +39,7 @@ public class BpfTests
// 1. Span Mode
double[] spanOutput = new double[series.Count];
- Bpf.Calculate(series.Values.ToArray(), spanOutput, lowerPeriod, upperPeriod);
+ Bpf.Batch(series.Values.ToArray(), spanOutput, lowerPeriod, upperPeriod);
// 2. TSeries Batch Mode
var bpfBatch = new Bpf(lowerPeriod, upperPeriod);
@@ -111,7 +111,7 @@ public class BpfTests
double[] input = Enumerable.Repeat(100.0, 500).ToArray();
double[] output = new double[500];
- Bpf.Calculate(input, output, 10, 20);
+ Bpf.Batch(input, output, 10, 20);
// Last value should be close to 0
Assert.True(Math.Abs(output[^1]) < 1e-6);
diff --git a/lib/filters/bpf/Bpf.Validation.Tests.cs b/lib/filters/bpf/Bpf.Validation.Tests.cs
index 32a094ee..159eda8d 100644
--- a/lib/filters/bpf/Bpf.Validation.Tests.cs
+++ b/lib/filters/bpf/Bpf.Validation.Tests.cs
@@ -32,9 +32,9 @@ public class BpfValidationTests
double[] out100 = new double[T];
// Instantiate BPF with LowerPeriod=40 (HP cutoff), UpperPeriod=10 (LP cutoff)
- Bpf.Calculate(sine5, out5, 40, 10);
- Bpf.Calculate(sine15, out15, 40, 10);
- Bpf.Calculate(sine100, out100, 40, 10);
+ Bpf.Batch(sine5, out5, 40, 10);
+ Bpf.Batch(sine15, out15, 40, 10);
+ Bpf.Batch(sine100, out100, 40, 10);
// Analysis of results (last 100 samples to avoid warmup)
double amp5 = GetAmplitude(out5);
diff --git a/lib/filters/bpf/Bpf.cs b/lib/filters/bpf/Bpf.cs
index 80e9ebcd..4cd0bf5a 100644
--- a/lib/filters/bpf/Bpf.cs
+++ b/lib/filters/bpf/Bpf.cs
@@ -115,7 +115,7 @@ public sealed class Bpf : AbstractBase
double[] values = source.Values.ToArray();
double[] results = new double[values.Length];
- Calculate(values, results, LowerPeriod, UpperPeriod);
+ Batch(values, results, LowerPeriod, UpperPeriod);
TSeries output = [];
for (int i = 0; i < values.Length; i++)
@@ -181,24 +181,15 @@ public sealed class Bpf : AbstractBase
return Last;
}
- public override void Reset()
+ public static TSeries Batch(TSeries source, int lowerPeriod, int upperPeriod)
{
- _state = default;
- _state.LastValid = double.NaN;
- _p_state = default;
- Last = default;
+ var indicator = new Bpf(lowerPeriod, upperPeriod);
+ return indicator.Update(source);
}
- public override void Prime(ReadOnlySpan source, TimeSpan? step = null)
- {
- foreach (double val in source)
- {
- Update(new TValue(DateTime.UtcNow, val), isNew: true);
- }
- }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Calculate(ReadOnlySpan source, Span output, int lowerPeriod, int upperPeriod)
+ public static void Batch(ReadOnlySpan source, Span output, int lowerPeriod, int upperPeriod)
{
if (source.Length != output.Length)
{
@@ -267,6 +258,29 @@ public sealed class Bpf : AbstractBase
}
}
+ public override void Reset()
+ {
+ _state = default;
+ _state.LastValid = double.NaN;
+ _p_state = default;
+ Last = default;
+ }
+
+ public override void Prime(ReadOnlySpan source, TimeSpan? step = null)
+ {
+ foreach (double val in source)
+ {
+ Update(new TValue(DateTime.UtcNow, val), isNew: true);
+ }
+ }
+
+ public static (TSeries Results, Bpf Indicator) Calculate(TSeries source, int lowerPeriod, int upperPeriod)
+ {
+ var indicator = new Bpf(lowerPeriod, upperPeriod);
+ TSeries results = indicator.Update(source);
+ return (results, indicator);
+ }
+
///
/// Unsubscribes from the source publisher if one was provided during construction.
///
diff --git a/lib/filters/butter/Butter.Tests.cs b/lib/filters/butter/Butter.Tests.cs
index 67b20dec..4da9bc66 100644
--- a/lib/filters/butter/Butter.Tests.cs
+++ b/lib/filters/butter/Butter.Tests.cs
@@ -21,7 +21,7 @@ public class ButterTests
{
var source = new double[10];
var destination = new double[5];
- Assert.Throws(() => Butter.Calculate(source, destination, 5, double.NaN));
+ Assert.Throws(() => Butter.Batch(source, destination, 5, double.NaN));
}
[Fact]
@@ -79,7 +79,7 @@ public class ButterTests
var tValues = series.Values.ToArray();
var spanInput = new ReadOnlySpan(tValues);
var spanOutput = new double[tValues.Length];
- Butter.Calculate(spanInput, spanOutput, period, double.NaN);
+ Butter.Batch(spanInput, spanOutput, period, double.NaN);
double spanResult = spanOutput[^1];
// 3. Streaming Mode
diff --git a/lib/filters/butter/Butter.cs b/lib/filters/butter/Butter.cs
index 3daa1054..0d7fff7f 100644
--- a/lib/filters/butter/Butter.cs
+++ b/lib/filters/butter/Butter.cs
@@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
+[SkipLocalsInit]
public sealed class Butter : AbstractBase
{
private readonly int _period;
@@ -146,7 +147,7 @@ public sealed class Butter : AbstractBase
{
var result = new TSeries();
Span