From 5ed4b6c0fc344675826b37928655fa9fc3e6c322 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Sat, 31 Jan 2026 14:05:53 -0800 Subject: [PATCH] pine files --- .github/instructions/codacy.instructions.md | 2 +- lib/channels/jbands/Jbands.pine | 178 ++++++++++++++++++++ lib/dynamics/adx/Adx.cs | 21 +-- lib/dynamics/adxr/Adxr.cs | 12 +- lib/dynamics/amat/Amat.cs | 21 +-- lib/dynamics/aroon/Aroon.cs | 18 +- lib/dynamics/aroonosc/AroonOsc.cs | 18 +- lib/dynamics/dmx/Dmx.cs | 11 +- lib/dynamics/super/Super.cs | 10 +- lib/errors/huber/huber.pine | 47 ++++++ lib/errors/maape/maape.pine | 38 +++++ lib/errors/mae/mae.pine | 45 +++++ lib/errors/mapd/mapd.pine | 45 +++++ lib/errors/mape/mape.pine | 45 +++++ lib/errors/mase/mase.pine | 56 ++++++ lib/errors/mdae/mdae.pine | 33 ++++ lib/errors/mdape/mdape.pine | 37 ++++ lib/errors/me/me.pine | 45 +++++ lib/errors/mpe/mpe.pine | 45 +++++ lib/errors/mrae/mrae.pine | 38 +++++ lib/errors/mse/mse.pine | 46 +++++ lib/errors/msle/msle.pine | 45 +++++ lib/errors/pseudohuber/pseudohuber.pine | 41 +++++ lib/errors/quantile/quantile.pine | 36 ++++ lib/errors/rae/rae.pine | 75 +++++++++ lib/errors/rmse/rmse.pine | 45 +++++ lib/errors/rmsle/rmsle.pine | 45 +++++ lib/errors/rse/rse.pine | 74 ++++++++ lib/errors/rsquared/rsquared.pine | 75 +++++++++ lib/errors/smape/smape.pine | 48 ++++++ lib/errors/theilu/theilu.pine | 45 +++++ lib/errors/tukey/tukey.pine | 49 ++++++ lib/errors/wmape/wmape.pine | 41 +++++ lib/errors/wrmse/wrmse.pine | 53 ++++++ lib/filters/bilateral/Bilateral.pine | 44 +++++ lib/filters/butter/butter.pine | 47 ++++++ lib/filters/ssf/ssf.pine | 43 +++++ lib/filters/usf/usf.pine | 44 +++++ lib/momentum/bop/Bop.cs | 18 +- lib/momentum/bop/bop.pine | 45 +++++ lib/momentum/cfb/Cfb.cs | 20 +-- lib/momentum/cfb/cfb.pine | 79 +++++++++ lib/momentum/macd/Macd.cs | 12 +- lib/momentum/macd/macd.pine | 75 +++++++++ lib/momentum/roc/Roc.cs | 15 +- lib/momentum/rsi/Rsi.cs | 13 +- lib/momentum/rsi/rsi.pine | 36 ++++ lib/momentum/rsx/Rsx.cs | 15 +- lib/momentum/rsx/rsx.pine | 90 ++++++++++ lib/momentum/vel/Vel.cs | 10 +- lib/momentum/vel/vel.pine | 64 +++++++ lib/oscillators/ao/ao.pine | 55 ++++++ lib/oscillators/apo/apo.pine | 50 ++++++ lib/oscillators/ultosc/ultosc.pine | 92 ++++++++++ lib/statistics/beta/beta.pine | 72 ++++++++ lib/statistics/cma/cma.pine | 35 ++++ lib/statistics/covariance/covariance.pine | 56 ++++++ lib/statistics/linreg/linreg.pine | 59 +++++++ lib/statistics/median/median.pine | 42 +++++ lib/statistics/skew/skew.pine | 84 +++++++++ lib/statistics/stddev/stddev.pine | 42 +++++ lib/statistics/sum/sum.pine | 59 +++++++ lib/statistics/variance/variance.pine | 41 +++++ lib/trends_FIR/alma/Alma.cs | 10 +- lib/trends_FIR/blma/Blma.cs | 8 +- lib/trends_FIR/bwma/Bwma.cs | 10 +- lib/trends_FIR/conv/Conv.cs | 17 +- lib/trends_FIR/dwma/Dwma.cs | 8 +- lib/trends_FIR/gwma/Gwma.cs | 11 +- lib/trends_FIR/hamma/Hamma.cs | 25 +-- lib/trends_FIR/hanma/Hanma.cs | 25 +-- lib/trends_FIR/hma/Hma.cs | 10 +- lib/trends_FIR/hwma/Hwma.cs | 27 +-- lib/trends_FIR/lsma/Lsma.cs | 25 +-- lib/trends_FIR/pwma/Pwma.cs | 19 +-- lib/trends_FIR/sgma/Sgma.cs | 26 +-- lib/trends_FIR/sinema/Sinema.cs | 17 +- lib/trends_FIR/sma/Sma.cs | 14 +- lib/trends_FIR/trima/Trima.cs | 15 +- lib/trends_FIR/wma/Wma.cs | 14 +- lib/trends_IIR/dema/Dema.cs | 16 +- lib/trends_IIR/dsma/Dsma.cs | 29 +--- lib/trends_IIR/ema/Ema.cs | 20 +-- lib/trends_IIR/frama/Frama.cs | 13 +- lib/trends_IIR/htit/Htit.cs | 13 +- lib/trends_IIR/jma/Jma.cs | 15 +- lib/trends_IIR/kama/Kama.cs | 13 +- lib/trends_IIR/mama/Mama.cs | 11 +- lib/trends_IIR/mgdi/Mgdi.cs | 13 +- lib/trends_IIR/qema/Qema.cs | 21 +-- lib/trends_IIR/rema/Rema.cs | 22 +-- lib/trends_IIR/rma/Rma.cs | 15 +- lib/trends_IIR/t3/T3.cs | 19 +-- lib/trends_IIR/tema/Tema.cs | 19 +-- lib/trends_IIR/vama/Vama.cs | 16 +- lib/trends_IIR/vidya/Vidya.cs | 19 +-- lib/trends_IIR/yzvama/Yzvama.cs | 12 +- lib/trends_IIR/zlema/Zlema.cs | 9 +- lib/volatility/adr/Adr.cs | 14 +- lib/volatility/atr/Atr.cs | 13 +- lib/volatility/atrn/Atrn.cs | 14 +- lib/volatility/atrp/Atrp.cs | 19 +-- 102 files changed, 2883 insertions(+), 593 deletions(-) create mode 100644 lib/channels/jbands/Jbands.pine create mode 100644 lib/errors/huber/huber.pine create mode 100644 lib/errors/maape/maape.pine create mode 100644 lib/errors/mae/mae.pine create mode 100644 lib/errors/mapd/mapd.pine create mode 100644 lib/errors/mape/mape.pine create mode 100644 lib/errors/mase/mase.pine create mode 100644 lib/errors/mdae/mdae.pine create mode 100644 lib/errors/mdape/mdape.pine create mode 100644 lib/errors/me/me.pine create mode 100644 lib/errors/mpe/mpe.pine create mode 100644 lib/errors/mrae/mrae.pine create mode 100644 lib/errors/mse/mse.pine create mode 100644 lib/errors/msle/msle.pine create mode 100644 lib/errors/pseudohuber/pseudohuber.pine create mode 100644 lib/errors/quantile/quantile.pine create mode 100644 lib/errors/rae/rae.pine create mode 100644 lib/errors/rmse/rmse.pine create mode 100644 lib/errors/rmsle/rmsle.pine create mode 100644 lib/errors/rse/rse.pine create mode 100644 lib/errors/rsquared/rsquared.pine create mode 100644 lib/errors/smape/smape.pine create mode 100644 lib/errors/theilu/theilu.pine create mode 100644 lib/errors/tukey/tukey.pine create mode 100644 lib/errors/wmape/wmape.pine create mode 100644 lib/errors/wrmse/wrmse.pine create mode 100644 lib/filters/bilateral/Bilateral.pine create mode 100644 lib/filters/butter/butter.pine create mode 100644 lib/filters/ssf/ssf.pine create mode 100644 lib/filters/usf/usf.pine create mode 100644 lib/momentum/bop/bop.pine create mode 100644 lib/momentum/cfb/cfb.pine create mode 100644 lib/momentum/macd/macd.pine create mode 100644 lib/momentum/rsi/rsi.pine create mode 100644 lib/momentum/rsx/rsx.pine create mode 100644 lib/momentum/vel/vel.pine create mode 100644 lib/oscillators/ao/ao.pine create mode 100644 lib/oscillators/apo/apo.pine create mode 100644 lib/oscillators/ultosc/ultosc.pine create mode 100644 lib/statistics/beta/beta.pine create mode 100644 lib/statistics/cma/cma.pine create mode 100644 lib/statistics/covariance/covariance.pine create mode 100644 lib/statistics/linreg/linreg.pine create mode 100644 lib/statistics/median/median.pine create mode 100644 lib/statistics/skew/skew.pine create mode 100644 lib/statistics/stddev/stddev.pine create mode 100644 lib/statistics/sum/sum.pine create mode 100644 lib/statistics/variance/variance.pine diff --git a/.github/instructions/codacy.instructions.md b/.github/instructions/codacy.instructions.md index 7429440c..0773cb32 100644 --- a/.github/instructions/codacy.instructions.md +++ b/.github/instructions/codacy.instructions.md @@ -10,7 +10,7 @@ Configuration for AI behavior when interacting with Codacy's MCP Server - ALWAYS use: - provider: gh - organization: mihakralj - - repository: QuanTAlib + - repository: pinescript - Avoid calling `git remote -v` unless really necessary ## CRITICAL: After ANY successful `edit_file` or `reapply` operation diff --git a/lib/channels/jbands/Jbands.pine b/lib/channels/jbands/Jbands.pine new file mode 100644 index 00000000..0d0b3df3 --- /dev/null +++ b/lib/channels/jbands/Jbands.pine @@ -0,0 +1,178 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Jurik Adaptive Envelope Bands", "JBANDS", overlay=true) + +//@function Jurik Adaptive Envelope Bands - Upper/Lower bands from JMA's adaptive envelope tracking +//@doc Bands snap to new extremes instantly but decay smoothly toward price, +//@doc creating volatility-responsive channels with JMA's signature smoothness. +//@param source Series to calculate JBANDS from +//@param period Number of bars used in the calculation (>= 1) +//@param phase Phase shift (-100 to 100). Negative = smoother, positive = more leading +//@returns Array of [middle (JMA), upper, lower] +jbands(series float source, simple int period, simple int phase = 0) => + // ---- Precomputed length/phase parameters (constant per series) ---- + simple float _PHASE = phase < -100 ? 0.5 : phase > 100 ? 2.5 : (phase * 0.01) + 1.5 + simple float _LEN0 = period < 1.0000000002 ? 1e-10 : (period - 1.0) / 2.0 + simple float _LOG_PARAM = math.max(math.log(math.sqrt(_LEN0)) / math.log(2.0) + 2.0, 0.0) + simple float _SQRT_PARAM = math.sqrt(_LEN0) * _LOG_PARAM + simple float _LEN_ADJ = _LEN0 * 0.9 + simple float _LEN_DIV = _LEN_ADJ / (_LEN_ADJ + 2.0) + simple float _SQRT_DIV = _SQRT_PARAM / (_SQRT_PARAM + 1.0) + simple float _P_EXP = math.max(_LOG_PARAM - 2.0, 0.5) + + // ---- Internal state (persists across bars) ---- + var float upperBand = na + var float lowerBand = na + var float lastC0 = na + var float lastC8 = na + var float lastA8 = na + var float lastJma = na + var int bars = 0 + + // 10-bar local deviation window + var float cycleDelta = 0.0 + var int volIndex = 0 + var int volCount = 0 + var array volWindow = array.new_float(10, 0.0) + + // 128-bar volatility distribution + var int distIndex = 0 + var int distCount = 0 + var array distWindow = array.new_float(128, 0.0) + var array sorted = array.new_float(0) + + float current_jma = na + float current_upper = na + float current_lower = na + + if not na(source) + bars += 1 + + // ---- First bar: initialize anchors and filter state ---- + if bars == 1 + upperBand := source + lowerBand := source + lastC0 := source + lastC8 := 0.0 + lastA8 := 0.0 + lastJma := source + current_jma := source + current_upper := source + current_lower := source + else + // 1) Local deviation vs. UpperBand / LowerBand + float diffA = source - upperBand + float diffB = source - lowerBand + float absA = math.abs(diffA) + float absB = math.abs(diffB) + float absValue = absA > absB ? absA : absB + float dLocal = absValue + 1e-10 + + // 2) 10-bar SMA of local deviation -> highD + float oldVol = array.get(volWindow, volIndex) + cycleDelta += dLocal - oldVol + array.set(volWindow, volIndex, dLocal) + volIndex += 1 + if volIndex >= 10 + volIndex := 0 + if volCount < 10 + volCount += 1 + float highD = volCount > 0 ? cycleDelta / (volCount < 10 ? volCount : 10) : dLocal + + // 3) 128-bar volatility distribution + trimmed mean + array.set(distWindow, distIndex, highD) + distIndex += 1 + if distIndex >= 128 + distIndex := 0 + if distCount < 128 + distCount += 1 + + float dRef = highD + if distCount >= 16 + int count = distCount + array.clear(sorted) + for i = 0 to count - 1 + int idx = distIndex - 1 - i + if idx < 0 + idx += 128 + array.push(sorted, array.get(distWindow, idx)) + array.sort(sorted) + + int idxLo = 0 + int idxHi = 0 + if count >= 128 + idxLo := 32 + idxHi := 96 + else + int slice = int(math.max(5.0, math.round(count * 0.5))) + int drop = (count - slice) / 2 + idxLo := drop + idxHi := drop + slice - 1 + + if idxLo < 0 + idxLo := 0 + if idxHi >= count + idxHi := count - 1 + + float sum = 0.0 + for i = idxLo to idxHi + sum += array.get(sorted, i) + dRef := sum / float(idxHi - idxLo + 1) + + if dRef <= 0.0 + dRef := dLocal + + // 4) Jurik dynamic exponent + float ratio = absValue / dRef + if ratio < 0.0 + ratio := 0.0 + float d = math.pow(ratio, _P_EXP) + d := math.min(math.max(d, 1.0), _LOG_PARAM) + + // 5) Update UpperBand / LowerBand via sqrtDivider ^ sqrt(d) + float adapt = math.pow(_SQRT_DIV, math.sqrt(d)) + if source > upperBand + upperBand := source + else + upperBand := source - (source - upperBand) * adapt + if source < lowerBand + lowerBand := source + else + lowerBand := source - (source - lowerBand) * adapt + + // 6) 2-pole IIR core (C0/C8/A8) with Jurik alpha for middle band (JMA) + float prevJma = na(lastJma) ? source : lastJma + float alpha = math.pow(_LEN_DIV, d) + float alpha2 = alpha * alpha + float c0 = (1.0 - alpha) * source + alpha * lastC0 + float c8 = (source - c0) * (1.0 - _LEN_DIV) + _LEN_DIV * lastC8 + float a8 = (_PHASE * c8 + c0 - prevJma) * (alpha * -2.0 + alpha2 + 1.0) + alpha2 * lastA8 + float jmaVal = prevJma + a8 + + lastC0 := c0 + lastC8 := c8 + lastA8 := a8 + lastJma := jmaVal + + current_jma := jmaVal + current_upper := upperBand + current_lower := lowerBand + + [current_jma, current_upper, current_lower] + +// ---------- Main loop ---------- + +// Inputs +i_period = input.int(10, "Period", minval=1, tooltip="Number of bars used in the calculation") +i_phase = input.int(0, "Phase", tooltip="Phase shift (-100 to 100). Negative = smoother, positive = more leading", minval=-100, maxval=100, step=10) +i_source = input.source(close, "Source") + +// Calculation +[jma, upper, lower] = jbands(i_source, i_period, i_phase) + +// Plot +p_upper = plot(upper, "Upper Band", color=color.new(color.red, 50), linewidth=1) +p_lower = plot(lower, "Lower Band", color=color.new(color.green, 50), linewidth=1) +plot(jma, "Middle (JMA)", color=color.yellow, linewidth=2) +fill(p_upper, p_lower, color=color.new(color.blue, 90), title="Band Fill") \ No newline at end of file diff --git a/lib/dynamics/adx/Adx.cs b/lib/dynamics/adx/Adx.cs index 2a209245..ba2b16fb 100644 --- a/lib/dynamics/adx/Adx.cs +++ b/lib/dynamics/adx/Adx.cs @@ -6,25 +6,12 @@ namespace QuanTAlib; /// ADX: Average Directional Index /// /// -/// ADX measures the strength of a trend, regardless of its direction. -/// It is derived from the Smoothed Directional Movement Index (DX). +/// Trend strength indicator [0-100] regardless of direction (Wilder). +/// Derived from smoothed DX using +DI/-DI relationship. Values above 25 indicate strong trend. /// -/// Calculation: -/// 1. Calculate True Range (TR), +DM, and -DM -/// 2. Smooth TR, +DM, -DM using RMA (Wilder's Moving Average) -/// - First value is SMA of first Period values -/// - Subsequent values: Previous + (Input - Previous) / Period -/// 3. Calculate +DI = (+DM_smooth / TR_smooth) * 100 -/// 4. Calculate -DI = (-DM_smooth / TR_smooth) * 100 -/// 5. Calculate DX = |(+DI - -DI) / (+DI + -DI)| * 100 -/// 6. ADX = RMA(DX) -/// - First value is SMA of first Period DX values -/// - Subsequent values: Previous + (Input - Previous) / Period -/// -/// Sources: -/// https://www.investopedia.com/terms/a/adx.asp -/// "New Concepts in Technical Trading Systems" by J. Welles Wilder +/// Calculation: ADX = RMA(DX) where DX = |+DI - -DI| / (+DI + -DI) × 100. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Adx : ITValuePublisher { diff --git a/lib/dynamics/adxr/Adxr.cs b/lib/dynamics/adxr/Adxr.cs index a52f7706..2c3813da 100644 --- a/lib/dynamics/adxr/Adxr.cs +++ b/lib/dynamics/adxr/Adxr.cs @@ -7,16 +7,12 @@ namespace QuanTAlib; /// ADXR: Average Directional Movement Rating /// /// -/// ADXR quantifies the change in momentum of the ADX. It is calculated by averaging -/// the current ADX value and the ADX value from 'Period' bars ago. +/// ADX momentum measure averaging current ADX with ADX from N periods ago (Wilder). +/// Smooths ADX to reduce noise and confirm sustained trend strength changes. /// -/// Calculation: -/// ADXR = (ADX + ADX[Period]) / 2 -/// -/// Sources: -/// https://www.investopedia.com/terms/a/adxr.asp -/// "New Concepts in Technical Trading Systems" by J. Welles Wilder +/// Calculation: ADXR = (ADX + ADX[Period]) / 2. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Adxr : ITValuePublisher { diff --git a/lib/dynamics/amat/Amat.cs b/lib/dynamics/amat/Amat.cs index b4601d77..7dcf571f 100644 --- a/lib/dynamics/amat/Amat.cs +++ b/lib/dynamics/amat/Amat.cs @@ -8,25 +8,12 @@ namespace QuanTAlib; /// AMAT: Archer Moving Averages Trends /// /// -/// AMAT is a trend identification system that uses multiple EMAs to identify -/// trend direction and strength. Unlike simple crossovers, AMAT requires alignment -/// of both fast and slow moving averages in the same direction. +/// Trend system requiring fast/slow EMA alignment in same direction for signals. +/// Returns +1 (bullish), -1 (bearish), or 0 (neutral) with strength percentage. /// -/// Calculation: -/// 1. Calculate Fast and Slow EMAs -/// 2. Bullish (+1): Fast EMA > Slow EMA AND Fast EMA rising AND Slow EMA rising -/// 3. Bearish (-1): Fast EMA < Slow EMA AND Fast EMA falling AND Slow EMA falling -/// 4. Neutral (0): Mixed conditions -/// 5. Strength = |Fast EMA - Slow EMA| / Slow EMA * 100 -/// -/// Key features: -/// - Direction alignment reduces false signals -/// - Trend strength measurement for conviction assessment -/// - Clear +1/-1/0 trend signals -/// -/// Sources: -/// Tom Joseph (2009), based on Mark Whistler (Archer) concepts +/// Signal: +1 when FastEMA > SlowEMA and both rising; -1 when FastEMA < SlowEMA and both falling. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Amat : ITValuePublisher, IDisposable { diff --git a/lib/dynamics/aroon/Aroon.cs b/lib/dynamics/aroon/Aroon.cs index 0b2f33e3..e34b8a3d 100644 --- a/lib/dynamics/aroon/Aroon.cs +++ b/lib/dynamics/aroon/Aroon.cs @@ -4,23 +4,15 @@ using System.Runtime.CompilerServices; namespace QuanTAlib; /// -/// Aroon Indicator +/// AROON: Aroon Indicator /// /// -/// The Aroon indicator is used to identify trend changes in the price of an asset, as well as the strength of that trend. -/// It consists of two lines: Aroon Up and Aroon Down. +/// Trend timing indicator measuring bars since period high/low (Chande). +/// Outputs Up [0-100], Down [0-100], and Oscillator (Up - Down). /// -/// Calculation: -/// Aroon Up = ((Period - Days Since Period High) / Period) * 100 -/// Aroon Down = ((Period - Days Since Period Low) / Period) * 100 -/// Aroon Oscillator = Aroon Up - Aroon Down -/// -/// The indicator requires Period + 1 samples to fully calculate "Period" days ago. -/// -/// Sources: -/// https://www.investopedia.com/terms/a/aroon.asp -/// Tushar Chande (1995) +/// Calculation: Up = (Period - DaysSinceHigh) / Period × 100; Down = (Period - DaysSinceLow) / Period × 100. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Aroon : ITValuePublisher { diff --git a/lib/dynamics/aroonosc/AroonOsc.cs b/lib/dynamics/aroonosc/AroonOsc.cs index 3e9b4bc7..931c409c 100644 --- a/lib/dynamics/aroonosc/AroonOsc.cs +++ b/lib/dynamics/aroonosc/AroonOsc.cs @@ -3,23 +3,15 @@ using System.Runtime.CompilerServices; namespace QuanTAlib; /// -/// Aroon Oscillator +/// AROONOSC: Aroon Oscillator /// /// -/// The Aroon Oscillator is a trend-following indicator that uses aspects of the Aroon Indicator (Aroon Up and Aroon Down) -/// to gauge the strength of a current trend and the likelihood that it will continue. +/// Single-line trend indicator derived from Aroon Up minus Aroon Down (Chande). +/// Range [-100, +100]: positive = uptrend, negative = downtrend. /// -/// Calculation: -/// Aroon Up = ((Period - Days Since Period High) / Period) * 100 -/// Aroon Down = ((Period - Days Since Period Low) / Period) * 100 -/// Aroon Oscillator = Aroon Up - Aroon Down -/// -/// The indicator requires Period + 1 samples to fully calculate "Period" days ago. -/// -/// Sources: -/// https://www.investopedia.com/terms/a/aroonoscillator.asp -/// Tushar Chande (1995) +/// Calculation: AroonOsc = AroonUp - AroonDown. /// +/// Detailed documentation [SkipLocalsInit] public sealed class AroonOsc : ITValuePublisher { diff --git a/lib/dynamics/dmx/Dmx.cs b/lib/dynamics/dmx/Dmx.cs index 27526eed..e504b315 100644 --- a/lib/dynamics/dmx/Dmx.cs +++ b/lib/dynamics/dmx/Dmx.cs @@ -5,10 +5,15 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// DMX – Jurik Directional Movement Index -/// A smoother, lower-lag alternative to Welles Wilder’s DMI/ADX. -/// Uses Jurik Moving Average (JMA) for smoothing directional movement components. +/// DMX: Jurik Directional Movement Index /// +/// +/// Smoother DMI alternative using JMA instead of Wilder smoothing (Jurik). +/// Lower lag than ADX while maintaining directional trend detection. +/// +/// Calculation: DMX = DI+ - DI- where DI values use JMA-smoothed +DM/-DM/TR. +/// +/// Detailed documentation [SkipLocalsInit] public sealed class Dmx : ITValuePublisher { diff --git a/lib/dynamics/super/Super.cs b/lib/dynamics/super/Super.cs index 7eaf1b8e..866fb095 100644 --- a/lib/dynamics/super/Super.cs +++ b/lib/dynamics/super/Super.cs @@ -4,9 +4,15 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// SuperTrend Indicator -/// A trend-following indicator that uses ATR to define upper and lower bands. +/// SUPER: SuperTrend Indicator /// +/// +/// ATR-based trend follower that switches between upper/lower bands on price breakouts. +/// Returns current SuperTrend level plus bullish/bearish state. +/// +/// Calculation: Bands = HL2 ± Multiplier × ATR; trend flips when price crosses opposite band. +/// +/// Detailed documentation [SkipLocalsInit] public sealed class Super : ITValuePublisher { diff --git a/lib/errors/huber/huber.pine b/lib/errors/huber/huber.pine new file mode 100644 index 00000000..2fb34b3d --- /dev/null +++ b/lib/errors/huber/huber.pine @@ -0,0 +1,47 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Huber Loss (HUBER)", "HUBER") + +//@function Calculates Huber Loss between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/huber.md +//@param source1 First series to compare +//@param source2 Second series to compare +//@param period Lookback period for error averaging +//@param delta Threshold that determines switch between MSE and MAE behavior +//@returns Huber loss value averaged over the specified period using SMA +huber(series float source1, series float source2, simple int period, simple float delta = 1.345) => + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + error = source1 - source2 + huber_error = math.abs(error) <= delta ? 0.5 * math.pow(error, 2) : delta * math.abs(error) - 0.5 * math.pow(delta, 2) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(huber_error) + sum := sum + huber_error + valid_count := valid_count + 1 + array.set(buffer, head, huber_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : huber_error + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_delta = input.float(1.345, "Delta", minval=0.1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = huber(i_source1, i_source2, i_period, i_delta) + +// Plot +plot(error, "Huber", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/maape/maape.pine b/lib/errors/maape/maape.pine new file mode 100644 index 00000000..17e565ca --- /dev/null +++ b/lib/errors/maape/maape.pine @@ -0,0 +1,38 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Arctangent Absolute Percentage Error", "MAAPE", overlay=false, format=format.percent) + +//@function Calculates Mean Arctangent Absolute Percentage Error +//@doc Uses arctangent to bound error between 0 and π/2, robust to outliers. +//@doc Handles zero actual values gracefully (approaches π/2). +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for averaging +//@returns MAAPE value (0 to ~1.5708) +maape(series float actual, series float predicted, simple int length) => + float epsilon = 1e-10 + + // Compute arctangent percentage error for current bar + float absActual = math.abs(nz(actual, 0.0)) + float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0)) + float atanError = absActual > epsilon ? math.atan(absError / absActual) : math.pi / 2.0 + + // Rolling mean of arctangent errors + float result = ta.sma(atanError, length) + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +maape_value = maape(i_actual, i_predicted, i_length) + +// Plot +plot(maape_value, "MAAPE", color=color.yellow, linewidth=2) +hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted) +hline(math.pi / 2.0, "Max (π/2)", color=color.red, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/mae/mae.pine b/lib/errors/mae/mae.pine new file mode 100644 index 00000000..5cd7eb9c --- /dev/null +++ b/lib/errors/mae/mae.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Absolute Error (MAE)", "MAE") + +//@function Calculates Mean Absolute Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mae.md +//@param source1 First series to compare +//@param source2 Second series to compare +//@param period Lookback period for error averaging +//@returns MAE value averaged over the specified period using SMA +mae(series float source1, series float source2, simple int period) => + absolute_error = math.abs(source1 - source2) + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(absolute_error) + sum := sum + absolute_error + valid_count := valid_count + 1 + array.set(buffer, head, absolute_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : absolute_error + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = mae(i_source1, i_source2, i_period) + +// Plot +plot(error, "MAE", color.new(color.blue, 60, color=color.yellow, linewidth=2), linewidth = 2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/mapd/mapd.pine b/lib/errors/mapd/mapd.pine new file mode 100644 index 00000000..0c864a95 --- /dev/null +++ b/lib/errors/mapd/mapd.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Absolute %Deviation (MAPD)", "MAPD") + +//@function Calculates Mean Absolute Percentage Deviation between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mapd.md +//@param source1 First series to compare +//@param source2 Second series to compare +//@param period Lookback period for error averaging +//@returns MAPD value averaged over the specified period using SMA +mapd(series float source1, series float source2, simple int period) => + percentage_error = 100 * math.abs((source1 - source2) / source2) + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(percentage_error) + sum := sum + percentage_error + valid_count := valid_count + 1 + array.set(buffer, head, percentage_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : percentage_error + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = mapd(i_source1, i_source2, i_period) + +// Plot +plot(error, "MAPD", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/mape/mape.pine b/lib/errors/mape/mape.pine new file mode 100644 index 00000000..4f4c7139 --- /dev/null +++ b/lib/errors/mape/mape.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Absolute %Error (MAPE)", "MAPE") + +//@function Calculates Mean Absolute Percentage Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mape.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for error averaging +//@returns MAPE value averaged over the specified period using SMA +mape(series float source1, series float source2, simple int period) => + percentage_error = 100 * math.abs((source1 - source2) / source1) + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(percentage_error) + sum := sum + percentage_error + valid_count := valid_count + 1 + array.set(buffer, head, percentage_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : percentage_error + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = mape(i_source1, i_source2, i_period) + +// Plot +plot(error, "MAPE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/mase/mase.pine b/lib/errors/mase/mase.pine new file mode 100644 index 00000000..af114fe1 --- /dev/null +++ b/lib/errors/mase/mase.pine @@ -0,0 +1,56 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Absolute Scaled Error (MASE)", "MASE") + +//@function Calculates Mean Absolute Scaled Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mase.md +//@param source1 First series to compare +//@param source2 Second series to compare +//@param period Lookback period for error averaging +//@returns MASE value averaged over the specified period using SMA +mase(series float source1, series float source2, simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + error = source1 - source2 + abs_error = math.abs(error) + var float scale = na + if na(scale) + sum = 0.0 + count = 0 + for i = 1 to p + if not na(source1[i]) and not na(source1[i-1]) + sum += math.abs(source1[i] - source1[i-1]) + count += 1 + scale := count > 0 ? sum / count : 1.0 + scaled_error = abs_error / (scale == 0 ? 1.0 : scale) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(scaled_error) + sum := sum + scaled_error + valid_count := valid_count + 1 + array.set(buffer, head, scaled_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : scaled_error + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = mase(i_source1, i_source2, i_period) + +// Plot +plot(error, "MASE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/mdae/mdae.pine b/lib/errors/mdae/mdae.pine new file mode 100644 index 00000000..a76482e7 --- /dev/null +++ b/lib/errors/mdae/mdae.pine @@ -0,0 +1,33 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Median Absolute Error", "MdAE", overlay=false) + +//@function Calculates Median Absolute Error +//@doc Median of absolute errors, robust to outliers (50% breakdown point). +//@doc Same units as original data, less sensitive to extreme errors than MAE. +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for median calculation +//@returns MdAE value +mdae(series float actual, series float predicted, simple int length) => + // Compute absolute error for current bar + float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0)) + + // Use ta.median for rolling median of absolute errors + float result = ta.median(absError, length) + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +mdae_value = mdae(i_actual, i_predicted, i_length) + +// Plot +plot(mdae_value, "MdAE", color=color.yellow, linewidth=2) +hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/mdape/mdape.pine b/lib/errors/mdape/mdape.pine new file mode 100644 index 00000000..81c485fa --- /dev/null +++ b/lib/errors/mdape/mdape.pine @@ -0,0 +1,37 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Median Absolute Percentage Error", "MdAPE", overlay=false, format=format.percent) + +//@function Calculates Median Absolute Percentage Error +//@doc Median of absolute percentage errors, robust to outliers. +//@doc Scale-independent (expressed as percentage), handles zero actual with epsilon. +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for median calculation +//@returns MdAPE value as percentage +mdape(series float actual, series float predicted, simple int length) => + float epsilon = 1e-10 + + // Compute absolute percentage error for current bar + float absActual = math.abs(nz(actual, 1.0)) + float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0)) + float pctError = absActual > epsilon ? (absError / absActual) * 100.0 : 0.0 + + // Use ta.median for rolling median of percentage errors + float result = ta.median(pctError, length) + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +mdape_value = mdape(i_actual, i_predicted, i_length) + +// Plot +plot(mdape_value, "MdAPE", color=color.yellow, linewidth=2) +hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/me/me.pine b/lib/errors/me/me.pine new file mode 100644 index 00000000..0959810e --- /dev/null +++ b/lib/errors/me/me.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Error (ME)", "ME") + +//@function Calculates Mean Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/me.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for error averaging +//@returns ME value averaged over the specified period using SMA +me(series float source1, series float source2, simple int period) => + error = source1 - source2 + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(error) + sum := sum + error + valid_count := valid_count + 1 + array.set(buffer, head, error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : error + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = me(i_source1, i_source2, i_period) + +// Plot +plot(error, "ME", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/mpe/mpe.pine b/lib/errors/mpe/mpe.pine new file mode 100644 index 00000000..abf565fd --- /dev/null +++ b/lib/errors/mpe/mpe.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean %Error (MPE)", "MPE") + +//@function Calculates Mean Percentage Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mpe.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for error averaging +//@returns MPE value averaged over the specified period using SMA +mpe(series float source1, series float source2, simple int period) => + percentage_error = 100 * ((source1 - source2) / source1) + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(percentage_error) + sum := sum + percentage_error + valid_count := valid_count + 1 + array.set(buffer, head, percentage_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : percentage_error + +// ---------- Main loop ---------- + + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = mpe(i_source1, i_source2, i_period) + +// Plot +plot(error, "MPE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/mrae/mrae.pine b/lib/errors/mrae/mrae.pine new file mode 100644 index 00000000..7476aad9 --- /dev/null +++ b/lib/errors/mrae/mrae.pine @@ -0,0 +1,38 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Relative Absolute Error", "MRAE", overlay=false) + +//@function Calculates Mean Relative Absolute Error +//@doc Average relative absolute error, normalized by actual value. +//@doc Similar to MAPE but expressed as ratio (0-1) instead of percentage (0-100%). +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for averaging +//@returns MRAE value (0 = perfect, 1 = 100% error) +mrae(series float actual, series float predicted, simple int length) => + float epsilon = 1e-10 + + // Compute relative absolute error for current bar + float absActual = math.abs(nz(actual, 1.0)) + float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0)) + float relError = absActual > epsilon ? absError / absActual : 0.0 + + // Rolling mean of relative errors + float result = ta.sma(relError, length) + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +mrae_value = mrae(i_actual, i_predicted, i_length) + +// Plot +plot(mrae_value, "MRAE", color=color.yellow, linewidth=2) +hline(0, "Perfect", color=color.green, linestyle=hline.style_dotted) +hline(1, "100% Error", color=color.red, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/mse/mse.pine b/lib/errors/mse/mse.pine new file mode 100644 index 00000000..567c1400 --- /dev/null +++ b/lib/errors/mse/mse.pine @@ -0,0 +1,46 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Squared Error (MSE)", "MSE") + + +//@function Calculates Mean Squared Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mse.md +//@param source1 First series to compare +//@param source2 Second series to compare +//@param period Lookback period for error averaging +//@returns MSE value averaged over the specified period using SMA +mse(series float source1, series float source2, simple int period) => + squared_error = math.pow(source1 - source2, 2) + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(squared_error) + sum := sum + squared_error + valid_count := valid_count + 1 + array.set(buffer, head, squared_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : squared_error + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = mse(i_source1, i_source2, i_period) + +// Plot +plot(error, "MSE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/msle/msle.pine b/lib/errors/msle/msle.pine new file mode 100644 index 00000000..db4eeaec --- /dev/null +++ b/lib/errors/msle/msle.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Mean Squared Logarithmic Error (MSLE)", "MSLE") + +//@function Calculates Mean Squared Logarithmic Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/msle.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for error averaging +//@returns MSLE value averaged over the specified period using SMA +msle(series float source1, series float source2, simple int period) => + log_error = math.pow(math.log(1 + source1) - math.log(1 + source2), 2) + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(log_error) + sum := sum + log_error + valid_count := valid_count + 1 + array.set(buffer, head, log_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : log_error + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = msle(i_source1, i_source2, i_period) + +// Plot +plot(error, "MSLE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/pseudohuber/pseudohuber.pine b/lib/errors/pseudohuber/pseudohuber.pine new file mode 100644 index 00000000..03b05a1b --- /dev/null +++ b/lib/errors/pseudohuber/pseudohuber.pine @@ -0,0 +1,41 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Pseudo-Huber Loss", "PseudoHuber", overlay=false) + +//@function Calculates Pseudo-Huber Loss (Charbonnier Loss) +//@doc Smooth approximation to Huber loss, differentiable everywhere. +//@doc Approximates L2 for small errors, L1 for large errors. +//@doc δ (delta) controls the transition point between quadratic and linear behavior. +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for averaging +//@param delta Scale parameter controlling transition smoothness (default 1.0) +//@returns Mean Pseudo-Huber loss over the window +pseudohuber(series float actual, series float predicted, simple int length, simple float delta = 1.0) => + float deltaSquared = delta * delta + + // Compute Pseudo-Huber loss for current bar: δ² * (√(1 + (error/δ)²) - 1) + float diff = nz(actual, 0.0) - nz(predicted, 0.0) + float ratio = diff / delta + float sqrtTerm = math.sqrt(1.0 + ratio * ratio) + float loss = deltaSquared * (sqrtTerm - 1.0) + + // Rolling mean of losses + float result = ta.sma(loss, length) + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_delta = input.float(1.0, "Delta (transition scale)", minval=0.001, step=0.1, tooltip="Controls transition between quadratic and linear behavior") +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +pseudohuber_value = pseudohuber(i_actual, i_predicted, i_length, i_delta) + +// Plot +plot(pseudohuber_value, "Pseudo-Huber", color=color.yellow, linewidth=2) +hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/quantile/quantile.pine b/lib/errors/quantile/quantile.pine new file mode 100644 index 00000000..8440c4c3 --- /dev/null +++ b/lib/errors/quantile/quantile.pine @@ -0,0 +1,36 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Quantile Loss (Pinball Loss)", "QuantileLoss", overlay=false) + +//@function Calculates Quantile Loss (Pinball Loss) +//@doc Used for quantile regression, asymmetrically penalizes over/under-predictions. +//@doc q=0.5 gives MAE; q>0.5 penalizes under-prediction more; q<0.5 penalizes over-prediction more. +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for averaging +//@param quantile Quantile value between 0 and 1 (default 0.5) +//@returns Mean quantile loss over the window +quantile_loss(series float actual, series float predicted, simple int length, simple float quantile = 0.5) => + // Compute quantile loss for current bar + float diff = nz(actual, 0.0) - nz(predicted, 0.0) + float loss = diff >= 0 ? quantile * diff : (quantile - 1.0) * diff + + // Rolling mean of losses + float result = ta.sma(loss, length) + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_quantile = input.float(0.5, "Quantile", minval=0.01, maxval=0.99, step=0.05, tooltip="0.5=median (MAE), >0.5=penalize under-prediction, <0.5=penalize over-prediction") +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +quantile_value = quantile_loss(i_actual, i_predicted, i_length, i_quantile) + +// Plot +plot(quantile_value, "Quantile Loss", color=color.yellow, linewidth=2) +hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/rae/rae.pine b/lib/errors/rae/rae.pine new file mode 100644 index 00000000..fbb77f0b --- /dev/null +++ b/lib/errors/rae/rae.pine @@ -0,0 +1,75 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Relative Absolute Error (RAE)", "RAE") + +//@function Calculates Relative Absolute Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rae.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for error averaging +//@returns RAE value averaged over the specified period using SMA +rae(series float source1, series float source2, simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float sum_source1 = 0.0 + var float[] buffer_source1 = array.new_float(p, na) + var int head_source1 = 0 + var int valid_count_source1 = 0 + float oldest_source1 = array.get(buffer_source1, head_source1) + if not na(oldest_source1) + sum_source1 := sum_source1 - oldest_source1 + valid_count_source1 := valid_count_source1 - 1 + if not na(source1) + sum_source1 := sum_source1 + source1 + valid_count_source1 := valid_count_source1 + 1 + array.set(buffer_source1, head_source1, source1) + head_source1 := (head_source1 + 1) % p + float mean_source1 = valid_count_source1 > 0 ? sum_source1 / valid_count_source1 : source1 + float abs_error = math.abs(source1 - source2) + float abs_baseline_error = math.abs(source1 - mean_source1) + var float sum_abs_error = 0.0 + var float[] buffer_abs_error = array.new_float(p, na) + var int head_abs_error = 0 + var int valid_count_abs_error = 0 + float oldest_abs_error = array.get(buffer_abs_error, head_abs_error) + if not na(oldest_abs_error) + sum_abs_error := sum_abs_error - oldest_abs_error + valid_count_abs_error := valid_count_abs_error - 1 + if not na(abs_error) + sum_abs_error := sum_abs_error + abs_error + valid_count_abs_error := valid_count_abs_error + 1 + array.set(buffer_abs_error, head_abs_error, abs_error) + head_abs_error := (head_abs_error + 1) % p + var float sum_baseline_error = 0.0 + var float[] buffer_baseline_error = array.new_float(p, na) + var int head_baseline_error = 0 + var int valid_count_baseline_error = 0 + float oldest_baseline_error = array.get(buffer_baseline_error, head_baseline_error) + if not na(oldest_baseline_error) + sum_baseline_error := sum_baseline_error - oldest_baseline_error + valid_count_baseline_error := valid_count_baseline_error - 1 + if not na(abs_baseline_error) + sum_baseline_error := sum_baseline_error + abs_baseline_error + valid_count_baseline_error := valid_count_baseline_error + 1 + array.set(buffer_baseline_error, head_baseline_error, abs_baseline_error) + head_baseline_error := (head_baseline_error + 1) % p + float total_abs_error = valid_count_abs_error > 0 ? sum_abs_error : abs_error + float total_baseline_error = valid_count_baseline_error > 0 ? sum_baseline_error : abs_baseline_error + total_baseline_error != 0 ? total_abs_error / total_baseline_error : 1.0 + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = rae(i_source1, i_source2, i_period) + +// Plot +plot(error, "RAE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/rmse/rmse.pine b/lib/errors/rmse/rmse.pine new file mode 100644 index 00000000..c3553301 --- /dev/null +++ b/lib/errors/rmse/rmse.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Root Mean Squared Error (RMSE)", "RMSE") + +//@function Calculates Root Mean Squared Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rmse.md +//@param source1 First series to compare +//@param source2 Second series to compare +//@param period Lookback period for error averaging +//@returns RMSE value averaged over the specified period using SMA +rmse(series float source1, series float source2, simple int period) => + squared_error = math.pow(source1 - source2, 2) + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(squared_error) + sum := sum + squared_error + valid_count := valid_count + 1 + array.set(buffer, head, squared_error) + head := (head + 1) % p + float mse = valid_count > 0 ? sum / valid_count : squared_error + math.sqrt(mse) + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = rmse(i_source1, i_source2, i_period) + +// Plot +plot(error, "RMSE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/rmsle/rmsle.pine b/lib/errors/rmsle/rmsle.pine new file mode 100644 index 00000000..1e542efe --- /dev/null +++ b/lib/errors/rmsle/rmsle.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Root Mean Squared Logarithmic Error (RMSLE)", "RMSLE") + +//@function Calculates Root Mean Squared Logarithmic Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rmsle.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for error averaging +//@returns RMSLE value averaged over the specified period using SMA +rmsle(series float source1, series float source2, simple int period) => + log_squared_error = math.pow(math.log(1 + source1) - math.log(1 + source2), 2) + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(log_squared_error) + sum := sum + log_squared_error + valid_count := valid_count + 1 + array.set(buffer, head, log_squared_error) + head := (head + 1) % p + float msle = valid_count > 0 ? sum / valid_count : log_squared_error + math.sqrt(msle) + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = rmsle(i_source1, i_source2, i_period) + +// Plot +plot(error, "RMSLE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/rse/rse.pine b/lib/errors/rse/rse.pine new file mode 100644 index 00000000..f60c3f96 --- /dev/null +++ b/lib/errors/rse/rse.pine @@ -0,0 +1,74 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Relative Squared Error (RSE)", "RSE") + +//@function Calculates Relative Squared Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rse.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for error averaging +//@returns RSE value averaged over the specified period using SMA +rse(series float source1, series float source2, simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float sum_source1 = 0.0 + var float[] buffer_source1 = array.new_float(p, na) + var int head_source1 = 0 + var int valid_count_source1 = 0 + float oldest_source1 = array.get(buffer_source1, head_source1) + if not na(oldest_source1) + sum_source1 := sum_source1 - oldest_source1 + valid_count_source1 := valid_count_source1 - 1 + if not na(source1) + sum_source1 := sum_source1 + source1 + valid_count_source1 := valid_count_source1 + 1 + array.set(buffer_source1, head_source1, source1) + head_source1 := (head_source1 + 1) % p + float mean_source1 = valid_count_source1 > 0 ? sum_source1 / valid_count_source1 : source1 + float squared_error = math.pow(source1 - source2, 2) + float squared_baseline_error = math.pow(source1 - mean_source1, 2) + var float sum_squared_error = 0.0 + var float[] buffer_squared_error = array.new_float(p, na) + var int head_squared_error = 0 + var int valid_count_squared_error = 0 + float oldest_squared_error = array.get(buffer_squared_error, head_squared_error) + if not na(oldest_squared_error) + sum_squared_error := sum_squared_error - oldest_squared_error + valid_count_squared_error := valid_count_squared_error - 1 + if not na(squared_error) + sum_squared_error := sum_squared_error + squared_error + valid_count_squared_error := valid_count_squared_error + 1 + array.set(buffer_squared_error, head_squared_error, squared_error) + head_squared_error := (head_squared_error + 1) % p + var float sum_baseline_error = 0.0 + var float[] buffer_baseline_error = array.new_float(p, na) + var int head_baseline_error = 0 + var int valid_count_baseline_error = 0 + float oldest_baseline_error = array.get(buffer_baseline_error, head_baseline_error) + if not na(oldest_baseline_error) + sum_baseline_error := sum_baseline_error - oldest_baseline_error + valid_count_baseline_error := valid_count_baseline_error - 1 + if not na(squared_baseline_error) + sum_baseline_error := sum_baseline_error + squared_baseline_error + valid_count_baseline_error := valid_count_baseline_error + 1 + array.set(buffer_baseline_error, head_baseline_error, squared_baseline_error) + head_baseline_error := (head_baseline_error + 1) % p + float total_squared_error = valid_count_squared_error > 0 ? sum_squared_error : squared_error + float total_baseline_error = valid_count_baseline_error > 0 ? sum_baseline_error : squared_baseline_error + total_baseline_error != 0 ? total_squared_error / total_baseline_error : 1.0 + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = rse(i_source1, i_source2, i_period) + +// Plot +plot(error, "RSE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/rsquared/rsquared.pine b/lib/errors/rsquared/rsquared.pine new file mode 100644 index 00000000..f3e448e7 --- /dev/null +++ b/lib/errors/rsquared/rsquared.pine @@ -0,0 +1,75 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("R² Coefficient of Determination (RSQUARED)", "RSQUARED") + +//@function Calculates the R-squared (Coefficient of Determination) between two sources +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rsquared.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for averaging +//@returns R-squared value averaging over the specified period +rsquared(series float source1, series float source2, simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float sum_source1 = 0.0 + var float[] buffer_source1 = array.new_float(p, na) + var int head_source1 = 0 + var int valid_count_source1 = 0 + float oldest_source1 = array.get(buffer_source1, head_source1) + if not na(oldest_source1) + sum_source1 := sum_source1 - oldest_source1 + valid_count_source1 := valid_count_source1 - 1 + if not na(source1) + sum_source1 := sum_source1 + source1 + valid_count_source1 := valid_count_source1 + 1 + array.set(buffer_source1, head_source1, source1) + head_source1 := (head_source1 + 1) % p + float mean_source1 = valid_count_source1 > 0 ? sum_source1 / valid_count_source1 : source1 + float squared_residual = math.pow(source1 - source2, 2) + float total_ss = math.pow(source1 - mean_source1, 2) + var float sum_squared_residual = 0.0 + var float[] buffer_squared_residual = array.new_float(p, na) + var int head_squared_residual = 0 + var int valid_count_squared_residual = 0 + float oldest_squared_residual = array.get(buffer_squared_residual, head_squared_residual) + if not na(oldest_squared_residual) + sum_squared_residual := sum_squared_residual - oldest_squared_residual + valid_count_squared_residual := valid_count_squared_residual - 1 + if not na(squared_residual) + sum_squared_residual := sum_squared_residual + squared_residual + valid_count_squared_residual := valid_count_squared_residual + 1 + array.set(buffer_squared_residual, head_squared_residual, squared_residual) + head_squared_residual := (head_squared_residual + 1) % p + var float sum_total_ss = 0.0 + var float[] buffer_total_ss = array.new_float(p, na) + var int head_total_ss = 0 + var int valid_count_total_ss = 0 + float oldest_total_ss = array.get(buffer_total_ss, head_total_ss) + if not na(oldest_total_ss) + sum_total_ss := sum_total_ss - oldest_total_ss + valid_count_total_ss := valid_count_total_ss - 1 + if not na(total_ss) + sum_total_ss := sum_total_ss + total_ss + valid_count_total_ss := valid_count_total_ss + 1 + array.set(buffer_total_ss, head_total_ss, total_ss) + head_total_ss := (head_total_ss + 1) % p + float rss = valid_count_squared_residual > 0 ? sum_squared_residual : squared_residual + float tss = valid_count_total_ss > 0 ? sum_total_ss : total_ss + tss != 0 ? 1 - (rss / tss) : 1.0 + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +score = rsquared(i_source1, i_source2, i_period) + +// Plot +plot(score, "R²", color.new(color.red, 60, color=color.yellow, linewidth=2), linewidth = 2, style = plot.style_area) +plot(i_source2, "EMA", color.new(color.yellow, 0, linewidth=2), linewidth = 1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/smape/smape.pine b/lib/errors/smape/smape.pine new file mode 100644 index 00000000..f1f058c5 --- /dev/null +++ b/lib/errors/smape/smape.pine @@ -0,0 +1,48 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Symmetric Mean Absolute %Error (SMAPE)", "SMAPE") + +//@function Calculates Symmetric Mean Absolute Percentage Error between two sources using SMA for averaging +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/smape.md +//@param source1 First series to compare (actual) +//@param source2 Second series to compare (predicted) +//@param period Lookback period for error averaging +//@returns SMAPE value averaged over the specified period using SMA (in percentage) +smape(series float source1, series float source2, simple int period) => + // Calculate symmetric absolute percentage error (scaled to 100%) + abs_diff = math.abs(source1 - source2) + sum_abs = math.abs(source1) + math.abs(source2) + symmetric_error = sum_abs != 0 ? 200 * abs_diff / sum_abs : 0 + if period <= 0 + runtime.error("Period must be greater than 0") + int p = math.min(math.max(1, period), 4000) + var float[] buffer = array.new_float(p, na) + var int head = 0 + var float sum = 0.0 + var int valid_count = 0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum := sum - oldest + valid_count := valid_count - 1 + if not na(symmetric_error) + sum := sum + symmetric_error + valid_count := valid_count + 1 + array.set(buffer, head, symmetric_error) + head := (head + 1) % p + valid_count > 0 ? sum / valid_count : symmetric_error + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source") +i_period = input.int(100, "Period", minval=1) +i_source2 = ta.ema(i_source1, i_period) + +// Calculation +error = smape(i_source1, i_source2, i_period) + +// Plot +plot(error, "SMAPE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area) +plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true) diff --git a/lib/errors/theilu/theilu.pine b/lib/errors/theilu/theilu.pine new file mode 100644 index 00000000..34eecd5c --- /dev/null +++ b/lib/errors/theilu/theilu.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Theil's U Statistic", "TheilU", overlay=false) + +//@function Calculates Theil's U Statistic (U1) +//@doc Relative forecast accuracy measure, normalized RMSE. +//@doc U=0: perfect; U=1: naive forecast; U>1: worse than naive. +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for calculation +//@returns Theil's U value (0 to 1+ range) +theil_u(series float actual, series float predicted, simple int length) => + float epsilon = 1e-10 + + // Compute squared values for current bar + float error = nz(predicted, 0.0) - nz(actual, 0.0) + float sqError = error * error + float sqActual = nz(actual, 0.0) * nz(actual, 0.0) + float sqPred = nz(predicted, 0.0) * nz(predicted, 0.0) + + // Rolling sums + float sumSqError = ta.sum(sqError, length) + float sumSqActual = ta.sum(sqActual, length) + float sumSqPred = ta.sum(sqPred, length) + + // TheilU = √(Σ(pred-act)²) / √(Σact² + Σpred²) + float denom = math.sqrt(sumSqActual + sumSqPred) + float result = denom > epsilon ? math.sqrt(sumSqError) / denom : 0.0 + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +theilu_value = theil_u(i_actual, i_predicted, i_length) + +// Plot +plot(theilu_value, "Theil's U", color=color.yellow, linewidth=2) +hline(0, "Perfect", color=color.green, linestyle=hline.style_dotted) +hline(1, "Naive", color=color.red, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/tukey/tukey.pine b/lib/errors/tukey/tukey.pine new file mode 100644 index 00000000..dd73e942 --- /dev/null +++ b/lib/errors/tukey/tukey.pine @@ -0,0 +1,49 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Tukey's Biweight Loss", "TukeyBiweight", overlay=false) + +//@function Calculates Tukey's Biweight (Bisquare) Loss +//@doc Robust loss that completely rejects outliers beyond threshold c. +//@doc ρ(x) = (c²/6) * (1 - (1 - (x/c)²)³) for |x| ≤ c; ρ(x) = c²/6 for |x| > c +//@doc Common c values: 4.685 (95% efficiency), 6.0 (more permissive) +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for averaging +//@param c Threshold for outlier rejection (default 4.685) +//@returns Mean Tukey biweight loss over the window +tukey_biweight(series float actual, series float predicted, simple int length, simple float c = 4.685) => + float cSquaredOver6 = (c * c) / 6.0 + + // Compute Tukey biweight loss for current bar + float error = nz(actual, 0.0) - nz(predicted, 0.0) + float absError = math.abs(error) + + float loss = 0.0 + if absError > c + loss := cSquaredOver6 + else + float ratio = error / c + float ratioSq = ratio * ratio + float oneMinusRatioSq = 1.0 - ratioSq + float cubed = oneMinusRatioSq * oneMinusRatioSq * oneMinusRatioSq + loss := cSquaredOver6 * (1.0 - cubed) + + // Rolling mean of losses + float result = ta.sma(loss, length) + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_c = input.float(4.685, "Threshold c", minval=0.1, step=0.1, tooltip="4.685=95% efficiency for normal; 6.0=more permissive") +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +tukey_value = tukey_biweight(i_actual, i_predicted, i_length, i_c) + +// Plot +plot(tukey_value, "Tukey Biweight", color=color.yellow, linewidth=2) +hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/wmape/wmape.pine b/lib/errors/wmape/wmape.pine new file mode 100644 index 00000000..57abe877 --- /dev/null +++ b/lib/errors/wmape/wmape.pine @@ -0,0 +1,41 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Weighted Mean Absolute Percentage Error", "WMAPE", overlay=false, format=format.percent) + +//@function Calculates Weighted Mean Absolute Percentage Error +//@doc Weights errors by actual value magnitude, industry standard for demand forecasting. +//@doc WMAPE = (Σ|actual - predicted| / Σ|actual|) * 100 +//@doc More stable than MAPE for intermittent data with zero/low values. +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for calculation +//@returns WMAPE value as percentage +wmape(series float actual, series float predicted, simple int length) => + float epsilon = 1e-10 + + // Compute absolute error and absolute actual for current bar + float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0)) + float absActual = math.abs(nz(actual, 0.0)) + + // Rolling sums + float sumAbsError = ta.sum(absError, length) + float sumAbsActual = ta.sum(absActual, length) + + // WMAPE = (Σ|error| / Σ|actual|) * 100 + float result = sumAbsActual > epsilon ? (sumAbsError / sumAbsActual) * 100.0 : 0.0 + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +wmape_value = wmape(i_actual, i_predicted, i_length) + +// Plot +plot(wmape_value, "WMAPE", color=color.yellow, linewidth=2) +hline(0, "Perfect", color=color.green, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/errors/wrmse/wrmse.pine b/lib/errors/wrmse/wrmse.pine new file mode 100644 index 00000000..eedc667a --- /dev/null +++ b/lib/errors/wrmse/wrmse.pine @@ -0,0 +1,53 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Weighted Root Mean Squared Error", "WRMSE", overlay=false) + +//@function Calculates Weighted Root Mean Squared Error +//@doc WRMSE extends RMSE by weighting each error differently. +//@doc WRMSE = √(Σ(w * (actual - predicted)²) / Σ(w)) +//@doc Reduces to RMSE when all weights are equal. +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param weight Series of weights for each observation +//@param length Rolling window for calculation +//@returns WRMSE value in same units as original data +wrmse(series float actual, series float predicted, series float weight, simple int length) => + float epsilon = 1e-10 + + // Compute weighted squared error for current bar + float diff = nz(actual, 0.0) - nz(predicted, 0.0) + float w = math.max(nz(weight, 1.0), 0.0) // Ensure non-negative weight + float weightedSqError = w * diff * diff + + // Rolling sums + float sumWeightedError = ta.sum(weightedSqError, length) + float sumWeight = ta.sum(w, length) + + // WRMSE = √(Σ(w*e²) / Σ(w)) + float result = sumWeight > epsilon ? math.sqrt(sumWeightedError / sumWeight) : 0.0 + result + +//@function Calculates WRMSE with uniform weights (equivalent to RMSE) +//@param actual Series of actual values +//@param predicted Series of predicted/forecast values +//@param length Rolling window for calculation +//@returns WRMSE value (same as RMSE when weights are uniform) +wrmse_uniform(series float actual, series float predicted, simple int length) => + wrmse(actual, predicted, 1.0, length) + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_use_volume_weights = input.bool(false, "Use Volume as Weights", tooltip="When enabled, errors are weighted by volume") +i_actual = input.source(close, "Actual") +i_predicted = input.source(open, "Predicted") + +// Calculation +weight = i_use_volume_weights ? volume : 1.0 +wrmse_value = wrmse(i_actual, i_predicted, weight, i_length) + +// Plot +plot(wrmse_value, "WRMSE", color=color.yellow, linewidth=2) +hline(0, "Perfect", color=color.green, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/filters/bilateral/Bilateral.pine b/lib/filters/bilateral/Bilateral.pine new file mode 100644 index 00000000..18a88908 --- /dev/null +++ b/lib/filters/bilateral/Bilateral.pine @@ -0,0 +1,44 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Bilateral Filter (BILATERAL)", "BILATERAL", overlay=true) + +//@function Calculates Bilateral Filter +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/filters/bilateral.md +//@param src Series to calculate Bilateral Filter from +//@param length Number of bars used in the calculation (spatial domain) +//@param sigma_s_ratio Ratio to determine spatial standard deviation +//@param sigma_r_mult Multiplier for range standard deviation +//@returns Bilateral Filter value +//@optimized Uses edge-preserving bilateral smoothing with O(n) complexity per bar +bilateral(series float src, simple int length, simple float sigma_s_ratio, simple float sigma_r_mult) => + float sigma_s = math.max(length * sigma_s_ratio, 1e-10) + float sigma_r = math.max(ta.stdev(src, length) * sigma_r_mult, 1e-10) + float sum_weights = 0.0 + float sum_weighted_src = 0.0 + float center_val = nz(src[0], src[1]) + for i = 0 to length - 1 + float val = nz(src[i], center_val) + float diff_spatial = float(i) + float diff_range = center_val - val + float weight_spatial = math.exp(-(diff_spatial * diff_spatial) / (2.0 * sigma_s * sigma_s)) + float weight_range = math.exp(-(diff_range * diff_range) / (2.0 * sigma_r * sigma_r)) + float weight = weight_spatial * weight_range + sum_weights += weight + sum_weighted_src += weight * val + float result = sum_weights == 0.0 ? center_val : sum_weighted_src / sum_weights + result + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(20, "Length", minval=2) +i_sigma_s_ratio = input.float(0.5, "Spatial Sigma Ratio", minval=0.01, step=0.05) +i_sigma_r_mult = input.float(1.0, "Range Sigma Multiplier", minval=0.01, step=0.1) +i_source = input.source(close, "Source") + +// Calculation +filtered_value = bilateral(i_source, i_length, i_sigma_s_ratio, i_sigma_r_mult) + +// Plot +plot(filtered_value, "Bilateral", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/filters/butter/butter.pine b/lib/filters/butter/butter.pine new file mode 100644 index 00000000..a9407bb3 --- /dev/null +++ b/lib/filters/butter/butter.pine @@ -0,0 +1,47 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Butterworth 2nd Order Filter (BUTTER)", "BUTTER", overlay=true) + +//@function Calculates 2nd Order Butterworth Lowpass Filter +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/filters/butter.md +//@param src Series to calculate Butterworth filter from +//@param length Cutoff period (related to -3dB frequency) +//@returns Butterworth filter value +//@optimized Uses IIR 2nd order Butterworth filter with O(1) complexity per bar +butter(series float src, simple int length) => + float pi = math.pi + int safe_length = math.max(length, 2) + float omega = 2.0 * pi / safe_length + float sin_omega = math.sin(omega) + float cos_omega = math.cos(omega) + float alpha = sin_omega / math.sqrt(2.0) + float a0 = 1.0 + alpha + float a1 = -2.0 * cos_omega + float a2 = 1.0 - alpha + float b0 = (1.0 - cos_omega) / 2.0 + float b1 = 1.0 - cos_omega + float b2 = (1.0 - cos_omega) / 2.0 + var float filt = na + if bar_index < 2 + filt := nz(src, 0.0) + else + float ssrc = nz(src, src[1]) + float src1 = nz(src[1], ssrc) + float src2 = nz(src[2], src1) + float filt1 = nz(filt[1], ssrc) + float filt2 = nz(filt[2], filt1) + filt := (b0 * ssrc + b1 * src1 + b2 * src2 - a1 * filt1 - a2 * filt2) / a0 + filt + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(20, "Length", minval=2) +i_source = input.source(close, "Source") + +// Calculation +butter_val = butter(i_source, i_length) + +// Plot +plot(butter_val, "Butterworth", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/filters/ssf/ssf.pine b/lib/filters/ssf/ssf.pine new file mode 100644 index 00000000..4b5d3fcc --- /dev/null +++ b/lib/filters/ssf/ssf.pine @@ -0,0 +1,43 @@ +// The MIT License (MIT) +// © mihakralj +// Indicator algorithm (C) 2004-2024 John F. Ehlers +//@version=6 +indicator("Supersmooth Filter (SSF)", "SSF", overlay=true) + +//@function Calculates Supersmooth Lowpass Filter +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/filters/ssf.md +//@param source Series to calculate SSF from +//@param length Number of bars used in the calculation +//@returns SSF value with optimized smoothing +//@optimized Uses 2-pole IIR Butterworth-style filter with O(1) complexity per bar +ssf(series float src, simple int length) => + var float SQRT2_PI = math.sqrt(2.0) * math.pi + var float ssf_internal = 0.0 + var float c1 = 0.0 + var float c2 = 0.0 + var float c3 = 0.0 + var int prev_length = 0 + if prev_length != length + float arg = SQRT2_PI / float(length) + float exp_arg = math.exp(-arg) + c2 := 2.0 * exp_arg * math.cos(arg) + c3 := -exp_arg * exp_arg + c1 := 1.0 - c2 - c3 + prev_length := length + float ssrc = nz(src, src[1]) + float src1 = nz(src[1], ssrc) + float src2 = nz(src[2], src1) + ssf_internal := c1 * ssrc + c2 * nz(ssf_internal[1], src1) + c3 * nz(ssf_internal[2], src2) + ssf_internal + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(20, "Length", minval=1) +i_source = input.source(close, "Source") + +// Calculation +ssf_val = ssf(i_source, i_length) + +// Plot +plot(ssf_val, "SSF", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/filters/usf/usf.pine b/lib/filters/usf/usf.pine new file mode 100644 index 00000000..99c4bde8 --- /dev/null +++ b/lib/filters/usf/usf.pine @@ -0,0 +1,44 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Ultrasmooth Filter (USF)", "USF", overlay=true) + +//@function Calculates Ultrasmooth Filter +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/filters/usf.md +//@param src Series to calculate USF from +//@param length Number of bars used in the calculation +//@returns USF value with optimized smoothing +//@optimized Uses 2-pole IIR filter with momentum enhancement, O(1) complexity per bar +usf(series float src, simple int length) => + var float SQRT2_PI = math.sqrt(2.0) * math.pi + var float usf_val = na + var float c1 = 0.0 + var float c2 = 0.0 + var float c3 = 0.0 + var int prev_length = 0 + if prev_length != length + float arg = SQRT2_PI / float(length) + float exp_arg = math.exp(-arg) + c2 := 2.0 * exp_arg * math.cos(arg) + c3 := -exp_arg * exp_arg + c1 := (1.0 + c2 - c3) / 4.0 + prev_length := length + float ssrc = nz(src, src[1]) + float src1 = nz(src[1], ssrc) + float src2 = nz(src[2], src1) + float us1 = nz(usf_val[1], src1) + float us2 = nz(usf_val[2], src2) + usf_val := (1.0 - c1) * ssrc + (2.0 * c1 - c2) * src1 - (c1 + c3) * src2 + c2 * us1 + c3 * us2 + usf_val + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(20, "Length", minval=1) +i_source = input.source(close, "Source") + +// Calculation +filt = usf(i_source, i_length) + +// Plot +plot(filt, "UltraSmooth", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/momentum/bop/Bop.cs b/lib/momentum/bop/Bop.cs index 41371591..e94b726f 100644 --- a/lib/momentum/bop/Bop.cs +++ b/lib/momentum/bop/Bop.cs @@ -8,22 +8,12 @@ namespace QuanTAlib; /// BOP: Balance of Power /// /// -/// BOP measures the strength of buyers vs sellers by comparing the close price to the open price, -/// relative to the high-low range. +/// Buyer/seller strength oscillator: (Close-Open)/(High-Low). +/// Ranges [-1,1]: positive = buyers dominate, negative = sellers dominate. /// -/// Formula: -/// BOP = (Close - Open) / (High - Low) -/// -/// Key characteristics: -/// - Oscillates between -1 and 1 -/// - 1 indicates buyers dominated (Close = High, Open = Low) -/// - -1 indicates sellers dominated (Close = Low, Open = High) -/// - 0 indicates balance (Close = Open) -/// - Often smoothed with an SMA (though this implementation provides the raw value) -/// -/// Sources: -/// https://www.investopedia.com/terms/b/bop.asp +/// Calculation: BOP = (Close - Open) / (High - Low). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Bop : ITValuePublisher { diff --git a/lib/momentum/bop/bop.pine b/lib/momentum/bop/bop.pine new file mode 100644 index 00000000..592357a0 --- /dev/null +++ b/lib/momentum/bop/bop.pine @@ -0,0 +1,45 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Balance of Power (BOP)", "BOP", overlay=false) + +//@function Calculates Balance of Power with optional smoothing +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/momentum/bop.md +//@param length Smoothing period (0 for no smoothing) +//@returns BOP value measuring buying/selling pressure +bop(simple int length) => + if length < 0 + runtime.error("Length must be non-negative") + float rawBop = high == low ? 0.0 : (close - open) / (high - low) + if length == 0 + rawBop + else + float alpha = 2.0 / (length + 1.0) + var float smoothBop = na + var float e = 1.0 + var bool warmupComplete = false + var float result = na + if na(smoothBop) + smoothBop := rawBop, result := rawBop + else + smoothBop := alpha * (rawBop - smoothBop) + smoothBop + if not warmupComplete + e *= (1.0 - alpha) + float c = e > 1e-10 ? 1.0 / (1.0 - e) : 1.0 + result := smoothBop * c + if e <= 1e-10 + warmupComplete := true + else + result := smoothBop + result + +// ---------- Main loop ---------- + +// Inputs +i_smooth = input.int(14, "Smoothing Length", minval=0, tooltip="0 for no smoothing") + +// Calculation +bop_value = bop(i_smooth) + +// Plot +plot(bop_value, "BOP", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/momentum/cfb/Cfb.cs b/lib/momentum/cfb/Cfb.cs index 027733ec..dc7d41b9 100644 --- a/lib/momentum/cfb/Cfb.cs +++ b/lib/momentum/cfb/Cfb.cs @@ -25,24 +25,12 @@ file static class CfbDefaults /// CFB: Jurik Composite Fractal Behavior (Trend Duration Index) /// /// -/// CFB measures the duration of a trend by analyzing fractal efficiency across multiple time scales. -/// It calculates a composite index based on which lookback periods show "quality" trending behavior. +/// Measures trend duration via fractal efficiency across multiple timescales. +/// Adaptive, zero-lag indicator for modulating other indicator periods. /// -/// Key characteristics: -/// - Adaptive: Adjusts to market fractal patterns. -/// - Granular: Uses a dense array of lookback lengths for smooth transitions. -/// - Composite: Weighted average of qualifying trend lengths. -/// - Zero-lag: Designed to modulate other indicators with minimal latency. -/// -/// Calculation: -/// 1. For each length L: -/// Ratio = NetMove(L) / TotalVolatility(L) -/// where NetMove = Abs(Price - Price[L ago]) -/// and TotalVolatility = Sum(Abs(Price[i] - Price[i-1])) over L bars. -/// 2. Filter: Only consider lengths where Ratio > Threshold (0.25). -/// 3. Composite: Weighted average of qualifying lengths (Weight = Ratio). -/// 4. Decay: If no trend found, decay the previous CFB value. +/// Calculation: CFB = Σ(L×Ratio) / Σ(Ratio) for lengths where NetMove/TotalVol ≥ 0.25. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Cfb : ITValuePublisher, IDisposable { diff --git a/lib/momentum/cfb/cfb.pine b/lib/momentum/cfb/cfb.pine new file mode 100644 index 00000000..492f8483 --- /dev/null +++ b/lib/momentum/cfb/cfb.pine @@ -0,0 +1,79 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Jurik Composite Fractal Behavior", "CFB", overlay=false) + +//@function Calculates Jurik Composite Fractal Behavior (Trend Duration Index) +//@doc Measures trend duration via fractal efficiency across multiple timescales. +//@doc Adaptive, zero-lag indicator for modulating other indicator periods. +//@param source Series to calculate CFB from +//@param maxLength Maximum lookback length (default 192, lengths 2,4,6,...,maxLength used) +//@returns CFB value - weighted average of efficient trend lengths, minimum 1 +cfb(series float source, simple int maxLength = 192) => + // Generate lengths array: 2, 4, 6, ..., maxLength + int numLengths = int(maxLength / 2) + + // Persistent state + var float prevCfb = 1.0 + var array runningSums = array.new_float(numLengths, 0.0) + + float currentVol = bar_index == 0 ? 0.0 : math.abs(source - source[1]) + + float sumWeightedLen = 0.0 + float sumWeights = 0.0 + + // Update running sums and calculate ratios for each length + for i = 0 to numLengths - 1 + int L = (i + 1) * 2 + + // Update running sum of volatility + float oldSum = array.get(runningSums, i) + float volToRemove = bar_index > L ? math.abs(source[L] - source[L + 1]) : 0.0 + float newSum = oldSum + currentVol - volToRemove + array.set(runningSums, i, newSum) + + // Skip if not enough bars + if bar_index < L + continue + + // Skip if very small volatility + if newSum < 1e-12 + continue + + // Net move over L bars + float netMove = math.abs(source - source[L]) + float ratio = netMove / newSum + + if ratio >= 0.25 + sumWeightedLen += float(L) * ratio + sumWeights += ratio + + // Calculate CFB + float cfbVal = 1.0 + if sumWeights > 0.25 + cfbVal := sumWeightedLen / sumWeights + else + cfbVal := prevCfb > 1.0 ? prevCfb * 0.5 : 1.0 + + if cfbVal < 1.0 + cfbVal := 1.0 + + cfbVal := math.round(cfbVal) + if cfbVal < 1.0 + cfbVal := 1.0 + + prevCfb := cfbVal + cfbVal + +// ---------- Main loop ---------- + +// Inputs +i_maxLength = input.int(192, "Max Length", minval=4, step=2, tooltip="Maximum lookback length. Lengths 2,4,6,...,maxLength are used") +i_source = input.source(close, "Source") + +// Calculation +cfb_value = cfb(i_source, i_maxLength) + +// Plot +plot(cfb_value, "CFB", color=color.yellow, linewidth=2) +hline(1, "Min", color=color.gray, linestyle=hline.style_dotted) \ No newline at end of file diff --git a/lib/momentum/macd/Macd.cs b/lib/momentum/macd/Macd.cs index 88174a3d..ec6ae04a 100644 --- a/lib/momentum/macd/Macd.cs +++ b/lib/momentum/macd/Macd.cs @@ -8,16 +8,12 @@ namespace QuanTAlib; /// MACD: Moving Average Convergence Divergence /// /// -/// MACD is a trend-following momentum indicator that shows the relationship between -/// two moving averages of a security's price. +/// Trend-following momentum indicator showing EMA convergence/divergence. +/// Provides three outputs: MACD Line, Signal Line, and Histogram. /// -/// Calculation: -/// MACD Line = Fast EMA - Slow EMA -/// Signal Line = EMA(MACD Line) -/// Histogram = MACD Line - Signal Line -/// -/// Standard parameters: 12, 26, 9 +/// Calculation: MACD = FastEMA - SlowEMA, Signal = EMA(MACD), Histogram = MACD - Signal. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Macd : ITValuePublisher, IDisposable { diff --git a/lib/momentum/macd/macd.pine b/lib/momentum/macd/macd.pine new file mode 100644 index 00000000..d67e9acf --- /dev/null +++ b/lib/momentum/macd/macd.pine @@ -0,0 +1,75 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Moving Average Convergence Divergence (MACD)", "MACD", overlay=false) + +//@function Calculates MACD with fast and slow EMAs and signal line +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/momentum/macd.md +//@param src Source series to calculate MACD from +//@param fast_length Period for fast EMA +//@param slow_length Period for slow EMA +//@param signal_length Period for signal line EMA +//@returns Tuple [macd, signal, histogram] values +//@optimized for performance and dirty data with embedded EMA calculations +macd(series float src, simple int fast_length, simple int slow_length, simple int signal_length) => + if fast_length <= 0 or slow_length <= 0 or signal_length <= 0 + runtime.error("All periods must be greater than 0") + if fast_length >= slow_length + runtime.error("Fast length must be less than slow length") + float alpha_fast = 2.0 / (fast_length + 1) + float alpha_slow = 2.0 / (slow_length + 1) + float alpha_signal = 2.0 / (signal_length + 1) + float beta_fast = 1.0 - alpha_fast + float beta_slow = 1.0 - alpha_slow + float beta_signal = 1.0 - alpha_signal + var bool warmup = true + var float e_fast = 1.0 + var float e_slow = 1.0 + var float e_signal = 1.0 + var float ema_fast = 0.0 + var float ema_slow = 0.0 + var float ema_signal = 0.0 + var float result_fast = src + var float result_slow = src + var float result_signal = 0.0 + ema_fast := alpha_fast * (src - ema_fast) + ema_fast + ema_slow := alpha_slow * (src - ema_slow) + ema_slow + if warmup + e_fast *= beta_fast + e_slow *= beta_slow + e_signal *= beta_signal + float c_fast = 1.0 / (1.0 - e_fast) + float c_slow = 1.0 / (1.0 - e_slow) + float c_signal = 1.0 / (1.0 - e_signal) + result_fast := c_fast * ema_fast + result_slow := c_slow * ema_slow + float macd_line = result_fast - result_slow + ema_signal := alpha_signal * (macd_line - ema_signal) + ema_signal + result_signal := c_signal * ema_signal + warmup := e_fast > 1e-10 or e_slow > 1e-10 or e_signal > 1e-10 + else + result_fast := ema_fast + result_slow := ema_slow + float macd_line = result_fast - result_slow + ema_signal := alpha_signal * (macd_line - ema_signal) + ema_signal + result_signal := ema_signal + float macd_line = result_fast - result_slow + float histogram = macd_line - result_signal + [macd_line, result_signal, histogram] + +// ---------- Main loop ---------- + +// Inputs +i_fast = input.int(12, "Fast Length", minval=1) +i_slow = input.int(26, "Slow Length", minval=2) +i_signal = input.int(9, "Signal Length", minval=1) +i_source = input.source(close, "Source") + +// Calculation +[macd_line, signal_line, histogram] = macd(i_source, i_fast, i_slow, i_signal) + +// Plot +hline(0, "Zero Line", color=color.gray) +plot(histogram, "Histogram", style=plot.style_columns, color=histogram >= 0 ? (histogram[1] < histogram ? color.green : color.green) : (histogram[1] < histogram ? color.red : color.red)) +plot(macd_line, "MACD", color=color.blue, linewidth=2) +plot(signal_line, "Signal", color=color.red, linewidth=2) \ No newline at end of file diff --git a/lib/momentum/roc/Roc.cs b/lib/momentum/roc/Roc.cs index c9c0ed18..b6f02a57 100644 --- a/lib/momentum/roc/Roc.cs +++ b/lib/momentum/roc/Roc.cs @@ -1,22 +1,17 @@ -// ROC: Rate of Change (Absolute) -// Calculates absolute price change: current - past - using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// ROC: Rate of Change (Absolute) -/// Calculates the absolute difference between current value and value N periods ago. -/// Formula: current - past /// /// -/// Key properties: -/// - Returns absolute price movement in price units -/// - Useful for momentum measurement, trend direction -/// - Different from ROCP (percentage) and ROCR (ratio) -/// - Can be validated against TA-Lib MOM function +/// Absolute price momentum: difference between current and N-period-ago value. +/// Also known as Momentum (MOM). See ROCP for percentage, ROCR for ratio. +/// +/// Calculation: ROC = Price - Price[N]. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Roc : AbstractBase { diff --git a/lib/momentum/rsi/Rsi.cs b/lib/momentum/rsi/Rsi.cs index 5b37c2d9..ba1932b8 100644 --- a/lib/momentum/rsi/Rsi.cs +++ b/lib/momentum/rsi/Rsi.cs @@ -8,17 +8,12 @@ namespace QuanTAlib; /// RSI: Relative Strength Index /// /// -/// RSI measures the speed and change of price movements. +/// Momentum oscillator measuring overbought/oversold conditions [0-100]. +/// Uses Wilder's smoothing (RMA) for average gain/loss calculation. /// -/// Calculation: -/// RS = Average Gain / Average Loss -/// RSI = 100 - 100 / (1 + RS) -/// -/// Average Gain/Loss are smoothed using RMA (Wilder's Smoothing). -/// -/// Sources: -/// https://www.investopedia.com/terms/r/rsi.asp +/// Calculation: RSI = 100 - 100/(1 + RS) where RS = AvgGain/AvgLoss. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Rsi : AbstractBase { diff --git a/lib/momentum/rsi/rsi.pine b/lib/momentum/rsi/rsi.pine new file mode 100644 index 00000000..430b004b --- /dev/null +++ b/lib/momentum/rsi/rsi.pine @@ -0,0 +1,36 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Relative Strength Index (RSI)", "RSI", overlay=false) + +//@function Calculates Relative Strength Index using Wilder's smoothing +//@param src Source series to calculate RSI for +//@param len Lookback period for RSI calculation +//@returns RSI value measuring momentum and overbought/oversold conditions +rsi(series float src, simple int len) => + if len <= 0 + runtime.error("Length must be greater than 0") + float u = math.max(src - src[1], 0) + float d = math.max(src[1] - src, 0) + float alpha = 1/len, float smoothUp = 0.0, float smoothDown = 0.0 + if bar_index < len + smoothUp := u + smoothDown := d + else + smoothUp := nz(smoothUp[1]) * (1 - alpha) + u * alpha + smoothDown := nz(smoothDown[1]) * (1 - alpha) + d * alpha + float rs = smoothDown == 0 ? 0 : smoothUp/smoothDown + float rsi = smoothDown == 0 ? 100 : 100 - (100 / (1 + rs)) + rsi + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_source = input.source(close, "Source") + +// Calculation +rsi_value = rsi(i_source, i_length) + +// Plot +plot(rsi_value, "RSI", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/momentum/rsx/Rsx.cs b/lib/momentum/rsx/Rsx.cs index 498e2681..7332bf22 100644 --- a/lib/momentum/rsx/Rsx.cs +++ b/lib/momentum/rsx/Rsx.cs @@ -7,19 +7,12 @@ namespace QuanTAlib; /// RSX: Jurik Relative Strength Index (Jurik's RSI Variant) /// /// -/// RSX is a noise-free version of RSI that eliminates lag and choppiness. -/// It uses a cascading IIR filter structure to achieve smoothness while preserving -/// turning points and the 0-100 range. +/// Noise-free RSI using cascading IIR filters for zero-lag, ultra-smooth output [0-100]. +/// Preserves turning points while eliminating choppiness. /// -/// Key characteristics: -/// - Zero lag (compared to smoothed RSI) -/// - Ultra smooth output -/// - Bounded 0-100 -/// -/// Sources: -/// - https://scribd.com/document/253633684/Jurik-RSX -/// - https://www.prorealcode.com/prorealtime-indicators/jurik-rsx/ +/// Calculation: Triple-cascaded momentum/abs-momentum smoothing → RSX = (ratio + 1) × 50. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Rsx : ITValuePublisher { diff --git a/lib/momentum/rsx/rsx.pine b/lib/momentum/rsx/rsx.pine new file mode 100644 index 00000000..04b318c4 --- /dev/null +++ b/lib/momentum/rsx/rsx.pine @@ -0,0 +1,90 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Relative Strength Xtra (RSX)", "RSX", overlay=false) + +//@function Calculates RSX (Relative Strength Xtra) using integrated JMA smoothing +//@param src Source series to calculate RSX for +//@param len Lookback period for RSX calculation +//@returns RSX value measuring momentum with reduced noise +rsx(series float src,simple int len)=> + if len<=0 + runtime.error("Length must be greater than 0") + float u=math.max(src-src[1],0),d=math.max(src[1]-src,0) + var simple float power=0.5 + var simple float PHASE_VALUE=0.5 + var simple float BETA=power*(len-1)/((power*(len-1))+2) + var simple float LEN1=math.max((math.log(math.sqrt(0.5*(len-1)))/math.log(2.0))+2.0,0) + var simple float POW1=math.max(LEN1-2.0,0.5) + var simple float LEN2=math.sqrt(0.5*(len-1))*LEN1 + var simple float POW1_RECIPROCAL=1.0/POW1 + var simple float AVG_VOLTY_ALPHA=2.0/(math.max(4.0*len,65)+1.0) + var simple float DIV=1.0/(10.0+10.0*(math.min(math.max(len-10,0),100))/100.0) + var float upperBand_state_up=na,var float lowerBand_state_up=na + var float ma1_state_up=na,var float jma_state_up=na + var float vSum_state_up=0.0,var float det0_state_up=0.0,var float det1_state_up=0.0 + var float avgVolty_state_up=na,var float smoothUp=na + var volty_array_state_up=array.new_float(11,0.0) + var float upperBand_state_down=na,var float lowerBand_state_down=na + var float ma1_state_down=na,var float jma_state_down=na + var float vSum_state_down=0.0,var float det0_state_down=0.0,var float det1_state_down=0.0 + var float avgVolty_state_down=na,var float smoothDown=na + var volty_array_state_down=array.new_float(11,0.0) + if not na(u) + float del1_up=u-nz(upperBand_state_up,u),float del2_up=u-nz(lowerBand_state_up,u) + float volty_up=math.abs(del1_up)==math.abs(del2_up)?0.0:math.max(math.abs(del1_up),math.abs(del2_up)) + array.unshift(volty_array_state_up,nz(volty_up,0.0)) + array.pop(volty_array_state_up) + if not na(volty_up) + vSum_state_up:=vSum_state_up+(volty_up-array.get(volty_array_state_up,10))*DIV + avgVolty_state_up:=nz(avgVolty_state_up,vSum_state_up)+AVG_VOLTY_ALPHA*(vSum_state_up-nz(avgVolty_state_up,vSum_state_up)) + float rvolty_up=math.min(math.max(nz(avgVolty_state_up,0)>0?nz(volty_up,0.0)/nz(avgVolty_state_up,1.0):1.0,1.0),math.pow(LEN1,POW1_RECIPROCAL)) + float pow2_up=math.pow(rvolty_up,POW1) + float Kv_up=math.pow(LEN2/(LEN2+1),math.sqrt(pow2_up)) + upperBand_state_up:=del1_up>0?u:u-Kv_up*del1_up + lowerBand_state_up:=del2_up<0?u:u-Kv_up*del2_up + float alpha_up=math.pow(BETA,pow2_up) + float alphaSquared_up=alpha_up*alpha_up,float oneMinusAlpha_up=1.0-alpha_up + float oneMinusAlphaSquared_up=oneMinusAlpha_up*oneMinusAlpha_up + ma1_state_up:=u+(alpha_up*(nz(ma1_state_up,u)-u)) + det0_state_up:=(u-ma1_state_up)*(1-BETA)+BETA*nz(det0_state_up,0) + float ma2_up=ma1_state_up+(PHASE_VALUE*det0_state_up) + det1_state_up:=((ma2_up-nz(jma_state_up,u))*oneMinusAlphaSquared_up)+(alphaSquared_up*nz(det1_state_up,0)) + jma_state_up:=nz(jma_state_up,u)+det1_state_up + smoothUp:=jma_state_up + if not na(d) + float del1_down=d-nz(upperBand_state_down,d),float del2_down=d-nz(lowerBand_state_down,d) + float volty_down=math.abs(del1_down)==math.abs(del2_down)?0.0:math.max(math.abs(del1_down),math.abs(del2_down)) + array.unshift(volty_array_state_down,nz(volty_down,0.0)) + array.pop(volty_array_state_down) + if not na(volty_down) + vSum_state_down:=vSum_state_down+(volty_down-array.get(volty_array_state_down,10))*DIV + avgVolty_state_down:=nz(avgVolty_state_down,vSum_state_down)+AVG_VOLTY_ALPHA*(vSum_state_down-nz(avgVolty_state_down,vSum_state_down)) + float rvolty_down=math.min(math.max(nz(avgVolty_state_down,0)>0?nz(volty_down,0.0)/nz(avgVolty_state_down,1.0):1.0,1.0),math.pow(LEN1,POW1_RECIPROCAL)) + float pow2_down=math.pow(rvolty_down,POW1) + float Kv_down=math.pow(LEN2/(LEN2+1),math.sqrt(pow2_down)) + upperBand_state_down:=del1_down>0?d:d-Kv_down*del1_down + lowerBand_state_down:=del2_down<0?d:d-Kv_down*del2_down + float alpha_down=math.pow(BETA,pow2_down) + float alphaSquared_down=alpha_down*alpha_down,float oneMinusAlpha_down=1.0-alpha_down + float oneMinusAlphaSquared_down=oneMinusAlpha_down*oneMinusAlpha_down + ma1_state_down:=d+(alpha_down*(nz(ma1_state_down,d)-d)) + det0_state_down:=(d-ma1_state_down)*(1-BETA)+BETA*nz(det0_state_down,0) + float ma2_down=ma1_state_down+(PHASE_VALUE*det0_state_down) + det1_state_down:=((ma2_down-nz(jma_state_down,d))*oneMinusAlphaSquared_down)+(alphaSquared_down*nz(det1_state_down,0)) + jma_state_down:=nz(jma_state_down,d)+det1_state_down + smoothDown:=jma_state_down + float rs=smoothDown==0?0:smoothUp/smoothDown + 100-(100/(1+rs)) + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_source = input.source(close, "Source") + +// Calculation +rsx_value = rsx(i_source, i_length) + +// Plot +plot(rsx_value, "RSX", color=color.yellow, linewidth=2) diff --git a/lib/momentum/vel/Vel.cs b/lib/momentum/vel/Vel.cs index 3aae7fe4..ebbbb7bc 100644 --- a/lib/momentum/vel/Vel.cs +++ b/lib/momentum/vel/Vel.cs @@ -8,14 +8,12 @@ namespace QuanTAlib; /// VEL: Jurik Velocity /// /// -/// VEL is a momentum oscillator calculated as the difference between a Parabolic Weighted Moving Average (PWMA) -/// and a Weighted Moving Average (WMA) of the same period. +/// Momentum oscillator measuring smoothed rate of price change using differential weighting. +/// Compares parabolic vs linear weight distributions for trend sensitivity. /// -/// Calculation: -/// VEL = PWMA(Period) - WMA(Period) -/// -/// This indicator measures the rate of change of the price, smoothed by the difference in weighting schemes. +/// Calculation: VEL = PWMA(Period) - WMA(Period). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Vel : ITValuePublisher, IDisposable { diff --git a/lib/momentum/vel/vel.pine b/lib/momentum/vel/vel.pine new file mode 100644 index 00000000..c802fe8e --- /dev/null +++ b/lib/momentum/vel/vel.pine @@ -0,0 +1,64 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Velocity (VEL)", "VEL", overlay=false) + +//@function Calculates zero-lag velocity using JMA smoothing +//@param src Source series to calculate velocity for +//@param period Lookback period for velocity calculation +//@returns Smoothed velocity value measuring rate of price change +vel(series float src, simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + float source = 0.0 + if not na(src) and not na(src[period]) + source := src - src[period] + var simple float phase = 100, var simple float power = 0.2 + var simple float PHASE_VALUE = math.min(math.max((phase * 0.01) + 1.5, 0.5), 2.5) + var simple float BETA = power * (period - 1) / ((power * (period - 1)) + 2) + var simple float LEN1 = math.max((math.log(math.sqrt(0.5*(period-1))) / math.log(2.0)) + 2.0, 0) + var simple float POW1 = math.max(LEN1 - 2.0, 0.5) + var simple float LEN2 = math.sqrt(0.5*(period-1))*LEN1 + var simple float POW1_RECIPROCAL = 1.0 / POW1 + var simple float AVG_VOLTY_ALPHA = 2.0 / (math.max(4.0 * period, 65) + 1.0) + var simple float DIV = 1.0/(10.0 + 10.0*(math.min(math.max(period-10,0),100))/100.0) + var float upperBand_state = na, var float lowerBand_state = na + var float ma1_state = na, var float jma_state = na + var float vSum_state = 0.0, var float det0_state = 0.0, var float det1_state = 0.0 + var float avgVolty_state = na, var float vel = na + var volty_array_state = array.new_float(11, 0.0) + if not na(source) + float del1 = source - nz(upperBand_state, source), float del2 = source - nz(lowerBand_state, source) + float volty = math.abs(del1) == math.abs(del2) ? 0.0 : math.max(math.abs(del1), math.abs(del2)) + array.unshift(volty_array_state, nz(volty, 0.0)) + array.pop(volty_array_state) + if not na(volty) + vSum_state := vSum_state + (volty - array.get(volty_array_state, 10)) * DIV + avgVolty_state := nz(avgVolty_state, vSum_state) + AVG_VOLTY_ALPHA * (vSum_state - nz(avgVolty_state, vSum_state)) + float rvolty = math.min(math.max(nz(avgVolty_state, 0) > 0 ? nz(volty, 0.0) / nz(avgVolty_state, 1.0) : 1.0, 1.0), math.pow(LEN1, POW1_RECIPROCAL)) + float pow2 = math.pow(rvolty, POW1) + float Kv = math.pow(LEN2/(LEN2+1), math.sqrt(pow2)) + upperBand_state := del1 > 0 ? source : source - Kv * del1 + lowerBand_state := del2 < 0 ? source : source - Kv * del2 + float alpha = math.pow(BETA, pow2) + float alphaSquared = alpha * alpha, float oneMinusAlpha = 1.0 - alpha + float oneMinusAlphaSquared = oneMinusAlpha * oneMinusAlpha + ma1_state := source + (alpha * (nz(ma1_state, source) - source)) + det0_state := (source - ma1_state) * (1 - BETA) + BETA * nz(det0_state, 0) + float ma2 = ma1_state + (PHASE_VALUE * det0_state) + det1_state := ((ma2 - nz(jma_state, source)) * oneMinusAlphaSquared) + (alphaSquared * nz(det1_state, 0)) + jma_state := nz(jma_state, source) + det1_state + vel := jma_state + vel + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(10, "Length", minval=1) +i_source = input.source(close, "Source") + +// Calculation +vel_value = vel(i_source, i_length) + +// Plot +plot(vel_value, "VEL", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/oscillators/ao/ao.pine b/lib/oscillators/ao/ao.pine new file mode 100644 index 00000000..c642d9ed --- /dev/null +++ b/lib/oscillators/ao/ao.pine @@ -0,0 +1,55 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Awesome Oscillator (AO)", "AO", overlay=false) + +//@function Calculates Bill Williams' Awesome Oscillator +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/oscillators/ao.md +//@param fastLength Period for fast MA calculation +//@param slowLength Period for slow MA calculation +//@returns AO value measuring market momentum +ao(simple int fastLength, simple int slowLength) => + if fastLength <= 0 or slowLength <= 0 + runtime.error("Lengths must be greater than 0") + if fastLength >= slowLength + runtime.error("Fast length must be less than slow length") + float mp = (high + low) / 2.0 + var array fastBuf = array.new_float(fastLength, na) + var array slowBuf = array.new_float(slowLength, na) + var int fastHead = 0, var int slowHead = 0 + var float fastSum = 0.0, var float slowSum = 0.0 + var int fastCount = 0, var int slowCount = 0 + float fastOldest = array.get(fastBuf, fastHead) + if not na(fastOldest) + fastSum -= fastOldest + fastCount -= 1 + if not na(mp) + fastSum += mp + fastCount += 1 + array.set(fastBuf, fastHead, mp) + fastHead := (fastHead + 1) % fastLength + float slowOldest = array.get(slowBuf, slowHead) + if not na(slowOldest) + slowSum -= slowOldest + slowCount -= 1 + if not na(mp) + slowSum += mp + slowCount += 1 + array.set(slowBuf, slowHead, mp) + slowHead := (slowHead + 1) % slowLength + float fastMA = fastCount > 0 ? fastSum / fastCount : na + float slowMA = slowCount > 0 ? slowSum / slowCount : na + fastMA - slowMA + +// ---------- Main loop ---------- + +// Inputs +i_fastLength = input.int(5, "Fast Length", minval=1) +i_slowLength = input.int(34, "Slow Length", minval=1) + +// Calculation +ao_value = ao(i_fastLength, i_slowLength) +ao_prev = ao_value[1] + +// Plot +plot(ao_value, "AO", ao_value >= ao_prev ? color.green : color.red, linewidth=2) \ No newline at end of file diff --git a/lib/oscillators/apo/apo.pine b/lib/oscillators/apo/apo.pine new file mode 100644 index 00000000..b02feead --- /dev/null +++ b/lib/oscillators/apo/apo.pine @@ -0,0 +1,50 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Absolute Price Oscillator (APO)", "APO", overlay=false) + +//@function Calculates Absolute Price Oscillator (APO) as difference between fast and slow EMAs +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/oscillators/apo.md +//@param source Series to calculate APO from +//@param fastLength Period for fast EMA +//@param slowLength Period for slow EMA +//@returns APO value (fast EMA - slow EMA) +apo(series float source, simple int fastLength, simple int slowLength) => + if fastLength <= 0 or slowLength <= 0 + runtime.error("Lengths must be greater than 0") + if fastLength >= slowLength + runtime.error("Fast length must be less than slow length") + float alphaFast = 2.0 / (fastLength + 1.0) + float alphaSlow = 2.0 / (slowLength + 1.0) + var float emaFast = na var float emaSlow = na, var float e = 1.0 + var bool warmup = true, var float result = na + if not na(source) + if na(emaFast) + emaFast := source, emaSlow := source, result := 0 + else + emaFast := alphaFast * (source - emaFast) + emaFast + emaSlow := alphaSlow * (source - emaSlow) + emaSlow + if warmup + e *= (1.0 - alphaSlow) + float c = e > 1e-10 ? 1.0 / (1.0 - e) : 1.0 + float aFast = emaFast * c + float aSlow = emaSlow * c + result := aFast - aSlow + if e <= 1e-10 + warmup := false + else + result := emaFast - emaSlow + result + +// ---------- Main loop ---------- + +// Inputs +i_source = input.source(close, "Source") +i_fastLength = input.int(12, "Fast Length", minval=1) +i_slowLength = input.int(26, "Slow Length", minval=1) + +// Calculation +apo_value = apo(i_source, i_fastLength, i_slowLength) + +// Plot +plot(apo_value, "APO", color.new(color.yellow, 0), 2) \ No newline at end of file diff --git a/lib/oscillators/ultosc/ultosc.pine b/lib/oscillators/ultosc/ultosc.pine new file mode 100644 index 00000000..5c1676ec --- /dev/null +++ b/lib/oscillators/ultosc/ultosc.pine @@ -0,0 +1,92 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Ultimate Oscillator (ULTOSC)", "ULTOSC", overlay=false) + +//@function Calculates the Ultimate Oscillator using three weighted time periods +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/oscillators/ultosc.md +//@param fastPeriod Short-term period for momentum calculation +//@param mediumPeriod Medium-term period for momentum calculation +//@param slowPeriod Long-term period for momentum calculation +//@param fastWeight Weight applied to fast period calculation +//@param mediumWeight Weight applied to medium period calculation +//@param slowWeight Weight applied to slow period calculation +//@returns Ultimate Oscillator value (0-100 scale) +ultosc(simple int fastPeriod, simple int mediumPeriod, simple int slowPeriod, simple float fastWeight, simple float mediumWeight, simple float slowWeight) => + if fastPeriod <= 0 or mediumPeriod <= 0 or slowPeriod <= 0 + runtime.error("All periods must be positive") + if fastPeriod >= mediumPeriod or mediumPeriod >= slowPeriod + runtime.error("Periods must be in ascending order: fast < medium < slow") + if fastWeight <= 0 or mediumWeight <= 0 or slowWeight <= 0 + runtime.error("All weights must be positive") + prev_close = nz(close[1], close) + true_low = math.min(low, prev_close) + true_high = math.max(high, prev_close) + buying_pressure = close - true_low + true_range = true_high - true_low + var array bp_fast_buffer = array.new_float(fastPeriod, na) + var array tr_fast_buffer = array.new_float(fastPeriod, na) + var array bp_medium_buffer = array.new_float(mediumPeriod, na) + var array tr_medium_buffer = array.new_float(mediumPeriod, na) + var array bp_slow_buffer = array.new_float(slowPeriod, na) + var array tr_slow_buffer = array.new_float(slowPeriod, na) + var int fast_head = 0, var int medium_head = 0, var int slow_head = 0 + var float bp_fast_sum = 0.0, var float tr_fast_sum = 0.0 + var float bp_medium_sum = 0.0, var float tr_medium_sum = 0.0 + var float bp_slow_sum = 0.0, var float tr_slow_sum = 0.0 + var int fast_count = 0, var int medium_count = 0, var int slow_count = 0 + bp_fast_oldest = array.get(bp_fast_buffer, fast_head) + tr_fast_oldest = array.get(tr_fast_buffer, fast_head) + bp_fast_sum := not na(bp_fast_oldest) ? bp_fast_sum - bp_fast_oldest : bp_fast_sum + tr_fast_sum := not na(tr_fast_oldest) ? tr_fast_sum - tr_fast_oldest : tr_fast_sum + fast_count := not na(bp_fast_oldest) ? fast_count - 1 : fast_count + bp_fast_sum := not na(buying_pressure) ? bp_fast_sum + buying_pressure : bp_fast_sum + tr_fast_sum := not na(true_range) ? tr_fast_sum + true_range : tr_fast_sum + fast_count := not na(buying_pressure) ? fast_count + 1 : fast_count + array.set(bp_fast_buffer, fast_head, buying_pressure) + array.set(tr_fast_buffer, fast_head, true_range) + fast_head := (fast_head + 1) % fastPeriod + bp_medium_oldest = array.get(bp_medium_buffer, medium_head) + tr_medium_oldest = array.get(tr_medium_buffer, medium_head) + bp_medium_sum := not na(bp_medium_oldest) ? bp_medium_sum - bp_medium_oldest : bp_medium_sum + tr_medium_sum := not na(tr_medium_oldest) ? tr_medium_sum - tr_medium_oldest : tr_medium_sum + medium_count := not na(bp_medium_oldest) ? medium_count - 1 : medium_count + bp_medium_sum := not na(buying_pressure) ? bp_medium_sum + buying_pressure : bp_medium_sum + tr_medium_sum := not na(true_range) ? tr_medium_sum + true_range : tr_medium_sum + medium_count := not na(buying_pressure) ? medium_count + 1 : medium_count + array.set(bp_medium_buffer, medium_head, buying_pressure) + array.set(tr_medium_buffer, medium_head, true_range) + medium_head := (medium_head + 1) % mediumPeriod + bp_slow_oldest = array.get(bp_slow_buffer, slow_head) + tr_slow_oldest = array.get(tr_slow_buffer, slow_head) + bp_slow_sum := not na(bp_slow_oldest) ? bp_slow_sum - bp_slow_oldest : bp_slow_sum + tr_slow_sum := not na(tr_slow_oldest) ? tr_slow_sum - tr_slow_oldest : tr_slow_sum + slow_count := not na(bp_slow_oldest) ? slow_count - 1 : slow_count + bp_slow_sum := not na(buying_pressure) ? bp_slow_sum + buying_pressure : bp_slow_sum + tr_slow_sum := not na(true_range) ? tr_slow_sum + true_range : tr_slow_sum + slow_count := not na(buying_pressure) ? slow_count + 1 : slow_count + array.set(bp_slow_buffer, slow_head, buying_pressure) + array.set(tr_slow_buffer, slow_head, true_range) + slow_head := (slow_head + 1) % slowPeriod + raw_fast = tr_fast_sum > 0 and fast_count >= fastPeriod ? 100 * bp_fast_sum / tr_fast_sum : 0 + raw_medium = tr_medium_sum > 0 and medium_count >= mediumPeriod ? 100 * bp_medium_sum / tr_medium_sum : 0 + raw_slow = tr_slow_sum > 0 and slow_count >= slowPeriod ? 100 * bp_slow_sum / tr_slow_sum : 0 + total_weight = fastWeight + mediumWeight + slowWeight + weighted_sum = (raw_fast * fastWeight) + (raw_medium * mediumWeight) + (raw_slow * slowWeight) + slow_count >= slowPeriod ? weighted_sum / total_weight : na + +// ---------- Main loop ---------- + +// Inputs +i_fastPeriod = input.int(7, "Fast Period", minval=1, maxval=50, tooltip="Short-term period for momentum calculation") +i_mediumPeriod = input.int(14, "Medium Period", minval=1, maxval=100, tooltip="Medium-term period for momentum calculation") +i_slowPeriod = input.int(28, "Slow Period", minval=1, maxval=200, tooltip="Long-term period for momentum calculation") +i_fastWeight = input.float(4.0, "Fast Weight", minval=0.1, maxval=10.0, step=0.1, tooltip="Weight applied to fast period calculation") +i_mediumWeight = input.float(2.0, "Medium Weight", minval=0.1, maxval=10.0, step=0.1, tooltip="Weight applied to medium period calculation") +i_slowWeight = input.float(1.0, "Slow Weight", minval=0.1, maxval=10.0, step=0.1, tooltip="Weight applied to slow period calculation") + +// Calculation +ultosc_value = ultosc(i_fastPeriod, i_mediumPeriod, i_slowPeriod, i_fastWeight, i_mediumWeight, i_slowWeight) + +// Plots +plot(ultosc_value, "Ultimate Oscillator", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/statistics/beta/beta.pine b/lib/statistics/beta/beta.pine new file mode 100644 index 00000000..b822aa33 --- /dev/null +++ b/lib/statistics/beta/beta.pine @@ -0,0 +1,72 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Beta Function (BETA)", "BETA", overlay=false) + +//@function Calculates the financial Beta indicator comparing src1 volatility to src2 +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/statistics/beta.md +//@param src1 series float Series to analyze +//@param src2 series float src2 series to compare against +//@param period simple int Lookback period for calculation +//@returns float Beta value showing src1 volatility relative to src2 +//@optimized for performance and dirty data +beta(series float src1, series float src2, simple int period) => + if period <= 0 + runtime.error("Period must be greater than 0") + var float last_src1 = na + var float last_src2 = na + src1_return = last_src1 != 0 and not na(last_src1) ? (src1 - last_src1) / last_src1 : na + bench_return = last_src2 != 0 and not na(last_src2) ? (src2 - last_src2) / last_src2 : na + last_src1 := src1 + last_src2 := src2 + var int count = 0 + var float sum_sr = 0.0, var float sum_br = 0.0 + var float sum_sr2 = 0.0, var float sum_br2 = 0.0 + var float sum_sbr = 0.0 + var sr_buf = array.new_float(period) + var br_buf = array.new_float(period) + var int index = 0 + if not na(src1_return) and not na(bench_return) + old_sr = array.get(sr_buf, index) + old_br = array.get(br_buf, index) + if count >= period + sum_sr -= old_sr, sum_br -= old_br + sum_sr2 -= old_sr * old_sr, sum_br2 -= old_br * old_br + sum_sbr -= old_sr * old_br + else + count += 1 + sum_sr += src1_return, sum_br += bench_return + sum_sr2 += src1_return * src1_return + sum_br2 += bench_return * bench_return + sum_sbr += src1_return * bench_return + array.set(sr_buf, index, src1_return) + array.set(br_buf, index, bench_return) + index := (index + 1) % period + if count > 0 + mean_sr = sum_sr / count + mean_br = sum_br / count + cov = (sum_sbr / count) - (mean_sr * mean_br) + var_bench = (sum_br2 / count) - (mean_br * mean_br) + if var_bench > 1e-10 + cov / var_bench + else + na + else + na + + +// ---------- Main loop ---------- + +// Inputs +i_symbol = input.symbol("SPY", "src2 Symbol") +i_period = input.int(14, "Period", minval=1) +i_src1 = input.source(close, "src1") + +// Get src2 data +src2Price = request.security(i_symbol, timeframe.period, close) + +// Calculate beta +beta_value = beta(i_src1, src2Price, i_period) + +// Plot +plot(beta_value, "Beta", color=color.yellow, linewidth=2) diff --git a/lib/statistics/cma/cma.pine b/lib/statistics/cma/cma.pine new file mode 100644 index 00000000..c8ba0477 --- /dev/null +++ b/lib/statistics/cma/cma.pine @@ -0,0 +1,35 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Cumulative Moving Average", "CMA", overlay=true) + +//@function Calculates Cumulative Moving Average (Running Average / Cumulative Mean) +//@doc Calculates the arithmetic mean of ALL data points seen so far. +//@doc Uses Welford's algorithm for numerical stability, O(1) per update. +//@param source Series to calculate CMA from +//@returns CMA value - running mean of all historical values +cma(series float source) => + // Persistent state + var float mean = 0.0 + var int count = 0 + + float val = nz(source, mean) + count += 1 + + // Welford's algorithm: M_n = M_(n-1) + alpha * (x_n - M_(n-1)) + float alpha = 1.0 / count + float delta = val - mean + mean := mean + alpha * delta + + mean + +// ---------- Main loop ---------- + +// Inputs +i_source = input.source(close, "Source") + +// Calculation +cma_value = cma(i_source) + +// Plot +plot(cma_value, "CMA", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/statistics/covariance/covariance.pine b/lib/statistics/covariance/covariance.pine new file mode 100644 index 00000000..b7030ce1 --- /dev/null +++ b/lib/statistics/covariance/covariance.pine @@ -0,0 +1,56 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Covariance (COVARIANCE)", "COVARIANCE", overlay=false) + +//@function Calculates covariance using single pass with circular buffer +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/statistics/covariance.md +//@param src1 series float First series to analyze +//@param src2 series float Second series to analyze +//@param len simple int Lookback period for calculation +//@returns float Covariance between src1 and src2 +//@optimized for performance using circular buffer +covariance(series float src1, series float src2, simple int len) => + if len <= 0 + runtime.error("Period must be greater than 0") + var int p = math.max(1, len) + var array buffer1 = array.new_float(p, na) + var array buffer2 = array.new_float(p, na) + var int head = 0, var int count = 0 + var float sum1 = 0.0, var float sum2 = 0.0 + var float sumProd = 0.0 + float oldest1 = array.get(buffer1, head) + float oldest2 = array.get(buffer2, head) + if not na(oldest1) and not na(oldest2) + sum1 -= oldest1 + sum2 -= oldest2 + sumProd -= oldest1 * oldest2 + count -= 1 + if not na(src1) and not na(src2) + sum1 += src1 + sum2 += src2 + sumProd += src1 * src2 + count += 1 + array.set(buffer1, head, src1) + array.set(buffer2, head, src2) + else + array.set(buffer1, head, na) + array.set(buffer2, head, na) + head := (head + 1) % p + count > 1 ? (sumProd / count) - (sum1 / count) * (sum2 / count) : na + + +// ---------- Main loop ---------- + +// Inputs +i_source1 = input.source(close, "Source 1") +i_source2_ticker = input.symbol("SPY", "Source 2 Ticker (e.g., SPY, AAPL)") +i_period = input.int(20, "Period", minval=2) + +i_source2 = request.security(i_source2_ticker, timeframe.period, close, lookahead=barmerge.lookahead_off) + +// Calculation +variance_value = covariance(i_source1, i_source2, i_period) + +// Plot +plot(variance_value, "Covariance", color=color.yellow, linewidth=2) diff --git a/lib/statistics/linreg/linreg.pine b/lib/statistics/linreg/linreg.pine new file mode 100644 index 00000000..a1da5b88 --- /dev/null +++ b/lib/statistics/linreg/linreg.pine @@ -0,0 +1,59 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Linear Regression (LINREG)", "LINREG", overlay=false, precision=8) + +//@function Calculates linear regression and slope over the specified period +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/statistics/linreg.md +//@param src Source series to calculate linear regression from +//@param len Lookback period for the calculation +//@returns Tuple containing [intercept, slope] +linreg(series float src, simple int len) => + if len < 2 + [na, na] + else + var float lastValid = na + var array buf = array.new_float(len, 0.0) + var int count = 0 + var int head = 0 + float curr = src + if na(curr) and not na(lastValid) + curr := lastValid + if not na(curr) + lastValid := curr + if not na(curr) + array.set(buf, head, curr) + if count < len + count := count + 1 + head := (head + 1) % len + float n = count + if n < 2 + [na, na] + else + int start = count < len ? 0 : head + float sumY = 0.0, sumXY = 0.0 + for i = 0 to int(n) - 1 + int idx = (start + i) % len + float y_val = array.get(buf, idx) + sumY += y_val + sumXY += i * y_val + float sumX = 0.5 * (n - 1) * n + float sumX2 = (n - 1) * n * (2 * n - 1) / 6.0 + float D = n * sumX2 - sumX * sumX + float s = D != 0 ? (n * sumXY - sumX * sumY) / D : na + float intercept = (sumY / n) - s * ((n - 1) / 2) + float lr = intercept + s * (n - 1) + [lr, s] + +// ---------- Main loop ---------- + +// Inputs +i_period = input.int(14, "Period", minval=2) +i_source = input.source(close, "Source") + +// Calculation: Assign tuple elements. +[lr, slope] = linreg(i_source, i_period) + +// Plot +plot(lr, "LinReg", color=color.yellow, linewidth=2) +// plot(slope, "Slope", color=color.orange, linewidth=2, plot.style_histogram) diff --git a/lib/statistics/median/median.pine b/lib/statistics/median/median.pine new file mode 100644 index 00000000..721ed7d6 --- /dev/null +++ b/lib/statistics/median/median.pine @@ -0,0 +1,42 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Median", "MEDIAN", overlay=false, precision=8) + +//@function Calculates the median of a series over a lookback period. +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/statistics/median.md +//@param src series float Input data series. +//@param len simple int Lookback period (must be > 0). +//@returns series float The median of the series over the period, or na if insufficient valid data. +median(series float src, simple int len) => + if len <= 0 + runtime.error("Length must be greater than 0") + var array values_in_window = array.new_float(0) + array.clear(values_in_window) // Clear from previous bar's calculation + for i = 0 to len - 1 + val = src[i] + if not na(val) + array.push(values_in_window, val) + int n = array.size(values_in_window) + float result = na + if n > 0 + array.sort(values_in_window) // Sort the array + if n % 2 == 1 // Odd number of elements + result := array.get(values_in_window, n / 2) + else // Even number of elements + float mid1 = array.get(values_in_window, n / 2 - 1) + float mid2 = array.get(values_in_window, n / 2) + result := (mid1 + mid2) / 2.0 + result + +// ---------- Main loop ---------- + +// Inputs +i_source = input.source(close, "Source") +i_length = input.int(14, "Period", minval=1) + +// Calculation +median_value = median(i_source, i_length) + +// Plot +plot(median_value, "Median", color=color.new(color.orange, 0, color=color.yellow, linewidth=2), linewidth=2) diff --git a/lib/statistics/skew/skew.pine b/lib/statistics/skew/skew.pine new file mode 100644 index 00000000..5410839f --- /dev/null +++ b/lib/statistics/skew/skew.pine @@ -0,0 +1,84 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Skewness (SKEW)", "SKEW", overlay=false, precision=6) + +//@function Calculates the skewness of a source series over a specified period. +// Skewness is a measure of the asymmetry of the probability distribution of a real-valued random variable about its mean. +// This implementation calculates the population skewness (Fisher-Pearson coefficient g1). +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/statistics/skew.md +//@param src The source series. +//@param len The lookback period. Must be > 2. +//@returns The skewness value. +//@optimized Uses efficient rolling calculations for mean, variance, and the third central moment. +skew(series float src, simple int len) => + if len <= 2 + runtime.error("Length must be greater than 2 for Skewness calculation.") + var float sum_m = 0.0 + var array buffer_m = array.new_float(len) + var int head_m = 0 + if bar_index >= len + sum_m -= array.get(buffer_m, head_m) + float current_src_nz = nz(src) + sum_m += current_src_nz + array.set(buffer_m, head_m, current_src_nz) + head_m := (head_m + 1) % len + + float mean_val = na + if bar_index >= len - 1 + mean_val := sum_m / len + else + mean_val := sum_m / (bar_index + 1) + float dev = src - mean_val + var float sum_v = 0.0 + var array buffer_v = array.new_float(len) + var int head_v = 0 + float dev_sq_nz = nz(math.pow(dev, 2)) + if bar_index >= len + sum_v -= array.get(buffer_v, head_v) + sum_v += dev_sq_nz + array.set(buffer_v, head_v, dev_sq_nz) + head_v := (head_v + 1) % len + float variance_val = na + if bar_index >= len - 1 + variance_val := sum_v / len + else + variance_val := sum_v / (bar_index + 1) + float stddev_val = variance_val > 1e-9 ? math.sqrt(variance_val) : 0.0 + var float sum_s3 = 0.0 + var array buffer_s3 = array.new_float(len) + var int head_s3 = 0 + float dev_cubed_nz = nz(math.pow(dev, 3)) + if bar_index >= len + sum_s3 -= array.get(buffer_s3, head_s3) + sum_s3 += dev_cubed_nz + array.set(buffer_s3, head_s3, dev_cubed_nz) + head_s3 := (head_s3 + 1) % len + float m3 = na + if bar_index >= len - 1 + m3 := sum_s3 / len + else + m3 := sum_s3 / (bar_index + 1) + float skew_val = na + if not na(m3) and not na(stddev_val) + if stddev_val > 1e-9 + float stddev_cubed = math.pow(stddev_val, 3) + if stddev_cubed != 0 + skew_val := m3 / stddev_cubed + else + skew_val := 0.0 + else + skew_val := 0.0 + skew_val + +// ---------- Main loop ---------- + +// Inputs +i_source = input.source(close, "Source") +i_length = input.int(20, "Length", minval=3) // Minval 3 for skewness + +// Calculation +skewValue = skew(i_source, i_length) + +// Plot +plot(skewValue, "Skewness", color=color.yellow, linewidth=2) diff --git a/lib/statistics/stddev/stddev.pine b/lib/statistics/stddev/stddev.pine new file mode 100644 index 00000000..a3c429e0 --- /dev/null +++ b/lib/statistics/stddev/stddev.pine @@ -0,0 +1,42 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Standard Deviation (STDDEV)", "STDDEV", overlay=false) + +//@function Calculates the standard deviation using a single pass with a circular buffer. +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/statistics/stddev.md +//@param src {series float} Source series. +//@param len {int} Lookback length. `len` > 0. +//@returns {series float} Standard deviation of `src` for `len` bars back. Returns `na` if not enough data. +stddev(series float src, int len) => + if len <= 0 + runtime.error("Period must be greater than 0") + var int p = math.max(1, len) + var array buffer = array.new_float(p, na) + var int head = 0, var int count = 0 + var float sum = 0.0, var float sumSq = 0.0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum -= oldest + sumSq -= oldest * oldest + count -= 1 + float val = nz(src) + sum += val + sumSq += val * val + count += 1 + array.set(buffer, head, val) + head := (head + 1) % p + count > 1 ? math.sqrt(math.max(0.0, (sumSq / count) - math.pow(sum / count, 2))) : 0.0 + + +// ---------- Main loop ---------- + +// Inputs +i_period = input.int(14, "Period", minval=1) // Default period 14 +i_source = input.source(close, "Source") + +// Calculation +stddev_value = stddev(i_source, i_period) + +// Plot +plot(stddev_value, "StdDev", color=color.yellow, linewidth=2) // Changed color diff --git a/lib/statistics/sum/sum.pine b/lib/statistics/sum/sum.pine new file mode 100644 index 00000000..e90c9ee1 --- /dev/null +++ b/lib/statistics/sum/sum.pine @@ -0,0 +1,59 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Rolling Sum", "SUM", overlay=false) + +//@function Calculates Rolling Sum over a period using Kahan-Babuška algorithm +//@doc Calculates the sum of the last n values with high numerical precision. +//@doc Uses Kahan-Babuška summation for machine-epsilon accuracy. +//@param source Series to calculate sum from +//@param length Number of bars to sum +//@returns Rolling sum of the last 'length' values +rolling_sum(series float source, simple int length) => + // Persistent state + var float sum = 0.0 + var float c = 0.0 // First-order compensation + var float cc = 0.0 // Second-order compensation + + float val = nz(source, 0.0) + float oldVal = bar_index >= length ? nz(source[length], 0.0) : 0.0 + + // Kahan-Babuška subtract old value + if bar_index >= length + float yS = -oldVal - c + float tS = sum + yS + c := tS - sum - yS + sum := tS + + float zS = c - cc + float ttS = sum + zS + cc := ttS - sum - zS + sum := ttS + + // Kahan-Babuška add new value + float yA = val - c + float tA = sum + yA + c := tA - sum - yA + sum := tA + + float zA = c - cc + float ttA = sum + zA + cc := ttA - sum - zA + sum := ttA + + sum + +// ---------- Main loop ---------- + +// Inputs +i_length = input.int(14, "Length", minval=1) +i_source = input.source(close, "Source") + +// Calculation +sum_value = rolling_sum(i_source, i_length) + +// Equivalent built-in for comparison (disabled by default) +// sum_builtin = math.sum(i_source, i_length) + +// Plot +plot(sum_value, "Sum", color=color.yellow, linewidth=2) \ No newline at end of file diff --git a/lib/statistics/variance/variance.pine b/lib/statistics/variance/variance.pine new file mode 100644 index 00000000..75bb41b8 --- /dev/null +++ b/lib/statistics/variance/variance.pine @@ -0,0 +1,41 @@ +// The MIT License (MIT) +// © mihakralj +//@version=6 +indicator("Variance, Dispersion or Spread (VARIANCE)", "VARIANCE", overlay=false) + +//@function variance +//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/statistics/variance.md +//@param src {series float} Source series. +//@param len {int} Lookback length. `len` > 0. +//@returns {series float} Variance of `src` for `len` bars back. Returns 0 if not enough data. +variance(series float src, int len) => + if len <= 0 + runtime.error("Period must be greater than 0") + var int p = math.max(1, len) + var array buffer = array.new_float(p, na) + var int head = 0, var int count = 0 + var float sum = 0.0, var float sumSq = 0.0 + float oldest = array.get(buffer, head) + if not na(oldest) + sum -= oldest + sumSq -= oldest * oldest + count -= 1 + float val = nz(src) + sum += val + sumSq += val * val + count += 1 + array.set(buffer, head, val) + head := (head + 1) % p + count > 1 ? math.max(0.0, (sumSq / count) - math.pow(sum / count, 2)) : 0.0 + +// ---------- Main loop ---------- + +// Inputs +i_period = input.int(14, "Period", minval=1) +i_source = input.source(close, "Source") + +// Calculation +variance_value = variance(i_source, i_period) + +// Plot +plot(variance_value, "Var", color=color.yellow, linewidth=2) diff --git a/lib/trends_FIR/alma/Alma.cs b/lib/trends_FIR/alma/Alma.cs index 8a13e2f0..47163b23 100644 --- a/lib/trends_FIR/alma/Alma.cs +++ b/lib/trends_FIR/alma/Alma.cs @@ -8,14 +8,12 @@ namespace QuanTAlib; /// ALMA: Arnaud Legoux Moving Average /// /// -/// ALMA uses a Gaussian distribution to determine weights for the moving average. -/// Definition: -/// m = offset * (period - 1) -/// s = period / sigma -/// W_i = exp( - (i - m)^2 / (2 * s^2) ) +/// Gaussian-weighted MA with adjustable offset and sigma for responsiveness control. +/// Higher offset (0-1) = more responsive; higher sigma = sharper weights. /// -/// The final ALMA is the weighted sum of the price window divided by the sum of weights. +/// Calculation: W_i = exp(-(i - m)² / (2s²)) where m = offset × (period-1). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Alma : AbstractBase { diff --git a/lib/trends_FIR/blma/Blma.cs b/lib/trends_FIR/blma/Blma.cs index 9f09c406..2c87541d 100644 --- a/lib/trends_FIR/blma/Blma.cs +++ b/lib/trends_FIR/blma/Blma.cs @@ -4,8 +4,14 @@ namespace QuanTAlib; /// /// BLMA: Blackman Moving Average -/// A weighted moving average using the Blackman window function for smoother transitions. /// +/// +/// Window-based MA using Blackman coefficients (a0=0.42, a1=0.5, a2=0.08). +/// Minimizes spectral leakage with smooth taper to zero at edges. +/// +/// Calculation: W_i = 0.42 - 0.5×cos(2πi/(n-1)) + 0.08×cos(4πi/(n-1)). +/// +/// Detailed documentation [SkipLocalsInit] public sealed class Blma : AbstractBase { diff --git a/lib/trends_FIR/bwma/Bwma.cs b/lib/trends_FIR/bwma/Bwma.cs index 0339e0c5..251cfcb6 100644 --- a/lib/trends_FIR/bwma/Bwma.cs +++ b/lib/trends_FIR/bwma/Bwma.cs @@ -8,12 +8,12 @@ namespace QuanTAlib; /// BWMA: Bessel-Weighted Moving Average /// /// -/// BWMA applies a Bessel window over the last N samples (FIR). -/// Window coefficient definition: -/// x(i) = 2*i/(p-1) - 1 (maps i to [-1, 1]), arg = 1 - x(i)^2 -/// w(i) = arg^(order/2 + 0.5) (with PineScript special-cases for order 0 and 1) -/// Output = sum(window[i] * w(i)) / sum(w(i)) +/// FIR MA using Bessel window coefficients with adjustable order. +/// Higher order produces sharper window; order 0 = parabolic. +/// +/// Calculation: W_i = (1 - x²)^(order/2 + 0.5) where x = 2i/(n-1) - 1. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Bwma : AbstractBase { diff --git a/lib/trends_FIR/conv/Conv.cs b/lib/trends_FIR/conv/Conv.cs index 68fb7d75..bd74cbc9 100644 --- a/lib/trends_FIR/conv/Conv.cs +++ b/lib/trends_FIR/conv/Conv.cs @@ -4,22 +4,15 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// Convolution Indicator +/// CONV: Convolution Filter /// /// -/// Applies a custom kernel (weights) to the data window. -/// The kernel is applied such that kernel[0] multiplies the oldest data point in the window, -/// and kernel[n-1] multiplies the newest data point. +/// FIR filter applying custom kernel weights via dot product. +/// Foundation for all window-based moving averages. /// -/// Calculation: -/// Result = Sum(kernel[i] * data[i]) for i = 0 to n-1 -/// -/// Complexity: -/// Update: O(K) where K is kernel length. -/// -/// IMPORTANT: This class implements IDisposable. When using the constructor with ITValuePublisher, -/// you MUST dispose the instance to unsubscribe from the source event and prevent memory leaks. +/// Calculation: Result = Σ(kernel[i] × data[i]) where kernel[0] weights oldest sample. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Conv : AbstractBase { diff --git a/lib/trends_FIR/dwma/Dwma.cs b/lib/trends_FIR/dwma/Dwma.cs index cca70d18..a8108c2c 100644 --- a/lib/trends_FIR/dwma/Dwma.cs +++ b/lib/trends_FIR/dwma/Dwma.cs @@ -8,12 +8,12 @@ namespace QuanTAlib; /// DWMA: Double Weighted Moving Average /// /// -/// DWMA applies a Weighted Moving Average (WMA) twice. -/// It provides a smoother curve than a standard WMA but with slightly more lag. +/// Double-pass WMA for enhanced smoothing with slight additional lag. +/// Triangular-like weighting via cascaded linear filters. /// -/// Formula: -/// DWMA = WMA(WMA(source, period), period) +/// Calculation: DWMA = WMA(WMA(source, n), n). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Dwma : AbstractBase { diff --git a/lib/trends_FIR/gwma/Gwma.cs b/lib/trends_FIR/gwma/Gwma.cs index a872ce4c..801ae984 100644 --- a/lib/trends_FIR/gwma/Gwma.cs +++ b/lib/trends_FIR/gwma/Gwma.cs @@ -8,15 +8,12 @@ namespace QuanTAlib; /// GWMA: Gaussian-Weighted Moving Average /// /// -/// GWMA uses a centered Gaussian window to weight price data. -/// Definition: -/// center = (period - 1) / 2 -/// W_i = exp(-0.5 * ((i - center) / (sigma * period))^2) +/// Centered Gaussian window weighting with sigma-controlled bell curve width. +/// Symmetric smoothing emphasizing center of window. /// -/// The final GWMA is the weighted sum of the price window divided by the sum of weights. -/// Unlike ALMA (which has an offset parameter), GWMA centers the Gaussian peak at the -/// middle of the window and uses sigma to control the bell curve width. +/// Calculation: W_i = exp(-0.5×((i - center)/(σ×n))²) centered at (n-1)/2. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Gwma : AbstractBase { diff --git a/lib/trends_FIR/hamma/Hamma.cs b/lib/trends_FIR/hamma/Hamma.cs index d4d125b5..01f4f4a9 100644 --- a/lib/trends_FIR/hamma/Hamma.cs +++ b/lib/trends_FIR/hamma/Hamma.cs @@ -1,6 +1,3 @@ -// Hamma.cs - Hamming Moving Average -// Finite Impulse Response (FIR) filter using Hamming window weighting. - using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,28 +6,14 @@ namespace QuanTAlib; /// /// HAMMA: Hamming Moving Average -/// A weighted moving average using Hamming window coefficients, providing good -/// spectral characteristics with reduced side lobes compared to simple windowing. /// /// -/// Key characteristics -/// -/// Hamming window: w[i] = 0.54 - 0.46 × cos(2πi/(period-1)) -/// Raised-cosine window with specific coefficients for optimal side-lobe suppression -/// First side lobe is approximately -43 dB down from main lobe -/// Widely used in digital signal processing and spectral analysis -/// +/// Window-based MA using Hamming raised-cosine coefficients (0.54/0.46). +/// -43 dB first side lobe for superior spectral characteristics. /// -/// Calculation -/// -/// w[i] = 0.54 - 0.46 × cos(2π × i / (period - 1)) -/// HAMMA = Σ(price[i] × w[i]) / Σ(w[i]) -/// -/// -/// Sources -/// Richard W. Hamming - "Digital Filters" (1977) -/// Oppenheim, Schafer - "Discrete-Time Signal Processing" +/// Calculation: W_i = 0.54 - 0.46×cos(2πi/(n-1)). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Hamma : AbstractBase { diff --git a/lib/trends_FIR/hanma/Hanma.cs b/lib/trends_FIR/hanma/Hanma.cs index e9d75aa6..626c18f7 100644 --- a/lib/trends_FIR/hanma/Hanma.cs +++ b/lib/trends_FIR/hanma/Hanma.cs @@ -1,6 +1,3 @@ -// Hanma.cs - Hanning Moving Average -// Finite Impulse Response (FIR) filter using Hanning window weighting. - using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,28 +6,14 @@ namespace QuanTAlib; /// /// HANMA: Hanning Moving Average -/// A weighted moving average using Hanning (Hann) window coefficients, providing -/// excellent spectral characteristics with smooth transitions at window edges. /// /// -/// Key characteristics -/// -/// Hanning window: w[i] = 0.5 × (1 - cos(2πi/(period-1))) -/// Raised-cosine window that reaches zero at both endpoints -/// First side lobe is approximately -32 dB down from main lobe -/// Also known as Hann window (after Julius von Hann) -/// +/// Window-based MA using Hanning (Hann) raised-cosine coefficients. +/// Zero at endpoints for smooth spectral transition; -32 dB first side lobe. /// -/// Calculation -/// -/// w[i] = 0.5 × (1 - cos(2π × i / (period - 1))) -/// HANMA = Σ(price[i] × w[i]) / Σ(w[i]) -/// -/// -/// Sources -/// Julius von Hann - Austrian meteorologist -/// Blackman, Tukey - "The Measurement of Power Spectra" (1958) +/// Calculation: W_i = 0.5×(1 - cos(2πi/(n-1))). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Hanma : AbstractBase { diff --git a/lib/trends_FIR/hma/Hma.cs b/lib/trends_FIR/hma/Hma.cs index a5a842e8..e45b5174 100644 --- a/lib/trends_FIR/hma/Hma.cs +++ b/lib/trends_FIR/hma/Hma.cs @@ -10,14 +10,12 @@ namespace QuanTAlib; /// HMA: Hull Moving Average /// /// -/// HMA reduces lag by using a combination of weighted moving averages. +/// Lag-reduced MA combining weighted MAs with square root smoothing. +/// SIMD-accelerated intermediate calculation (AVX-512/AVX2/NEON). /// -/// Calculation: -/// HMA = WMA(sqrt(n), 2 * WMA(n/2, price) - WMA(n, price)) -/// -/// Sources: -/// https://alan.hull.com.au/hma.html +/// Calculation: HMA = WMA(√n, 2×WMA(n/2) - WMA(n)). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Hma : AbstractBase { diff --git a/lib/trends_FIR/hwma/Hwma.cs b/lib/trends_FIR/hwma/Hwma.cs index 53670b83..efb5b4f6 100644 --- a/lib/trends_FIR/hwma/Hwma.cs +++ b/lib/trends_FIR/hwma/Hwma.cs @@ -1,6 +1,3 @@ -// Hwma.cs - Holt-Winters Moving Average -// Triple exponential smoothing with level, velocity, and acceleration components. - using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,30 +6,14 @@ namespace QuanTAlib; /// /// HWMA: Holt-Winters Moving Average -/// A triple exponential smoothing filter that tracks level (F), velocity (V), and -/// acceleration (A) components for adaptive trend following. /// /// -/// Key characteristics -/// -/// Triple exponential smoothing with level, velocity, and acceleration -/// Adapts quickly to trend changes via higher-order derivatives -/// When period specified: α = 2/(period+1), β = γ = 1/period -/// O(1) complexity per bar - no windowing required -/// +/// Triple exponential smoothing tracking level (F), velocity (V), and acceleration (A). +/// O(1) adaptive trend follower responding quickly via higher-order derivatives. /// -/// Calculation -/// -/// F = α × source + (1-α) × (prevF + prevV + 0.5 × prevA) -/// V = β × (F - prevF) + (1-β) × (prevV + prevA) -/// A = γ × (V - prevV) + (1-γ) × prevA -/// output = F + V + 0.5 × A -/// -/// -/// Sources -/// Holt, C.E. (1957) - "Forecasting Seasonals and Trends by Exponentially Weighted Moving Averages" -/// Winters, P.R. (1960) - "Forecasting Sales by Exponentially Weighted Moving Averages" +/// Calculation: Output = F + V + 0.5×A with recursive updates. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Hwma : AbstractBase { diff --git a/lib/trends_FIR/lsma/Lsma.cs b/lib/trends_FIR/lsma/Lsma.cs index e9b7dd05..5ef5c5b0 100644 --- a/lib/trends_FIR/lsma/Lsma.cs +++ b/lib/trends_FIR/lsma/Lsma.cs @@ -4,30 +4,15 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// LSMA: Least Squares Moving Average +/// LSMA: Least Squares Moving Average (Linear Regression) /// /// -/// LSMA calculates the linear regression line for the last n values and returns the value at the current position (or offset). -/// Uses a RingBuffer for storage and O(1) updates for regression sums. +/// Linear regression endpoint with O(1) updates using running sums. +/// Projects trend line value at current bar (or offset position). /// -/// Calculation: -/// Uses linear regression y = mx + b where x=0 is the current bar and x increases into the past. -/// m = (n * sum_xy - sum_x * sum_y) / denominator -/// b = (sum_y - m * sum_x) / n -/// LSMA = b - m * offset -/// -/// O(1) update: -/// sum_y_new = sum_y_old - oldest + newest -/// sum_xy_new = sum_xy_old + sum_y_prev - n * oldest -/// -/// IsHot: -/// Becomes true when the buffer is full (period samples processed). -/// -/// Disposal: -/// When constructed with an ITValuePublisher source, Lsma subscribes to the source's Pub event. -/// Call Dispose() to unsubscribe and prevent memory leaks, especially in long-running applications -/// or when creating many short-lived indicator instances. +/// Calculation: LSMA = b - m × offset where m = (n×Σxy - Σx×Σy) / denom. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Lsma : AbstractBase { diff --git a/lib/trends_FIR/pwma/Pwma.cs b/lib/trends_FIR/pwma/Pwma.cs index 3b51509e..08a7d30a 100644 --- a/lib/trends_FIR/pwma/Pwma.cs +++ b/lib/trends_FIR/pwma/Pwma.cs @@ -7,24 +7,11 @@ namespace QuanTAlib; /// PWMA: Parabolic Weighted Moving Average /// /// -/// PWMA applies parabolic weighting to data points, giving significantly more weight to recent values. -/// Uses triple running sums for O(1) complexity per update. +/// Quadratic weighting (w[i]=i²) emphasizing recent values via O(1) triple running sums. /// -/// Weights: w(i) = i^2 -/// -/// Calculation: -/// PWMA = Sum(i^2 * P_i) / Sum(i^2) -/// -/// O(1) update logic: -/// S1_new = S1_old - oldest + newest -/// S2_new = S2_old - S1_old + n * newest -/// S3_new = S3_old - 2*S2_old + S1_old + n^2 * newest -/// -/// Where: -/// S1 is simple sum -/// S2 is linear weighted sum -/// S3 is parabolic weighted sum +/// Calculation: PWMA = Σ(i²×P_i) / Σ(i²) with efficient incremental updates. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Pwma : AbstractBase { diff --git a/lib/trends_FIR/sgma/Sgma.cs b/lib/trends_FIR/sgma/Sgma.cs index 2457259f..3faedfc4 100644 --- a/lib/trends_FIR/sgma/Sgma.cs +++ b/lib/trends_FIR/sgma/Sgma.cs @@ -1,6 +1,3 @@ -// Sgma.cs - Savitzky-Golay Moving Average -// FIR filter using polynomial fitting for smoothing with shape preservation. - using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,29 +6,14 @@ namespace QuanTAlib; /// /// SGMA: Savitzky-Golay Moving Average -/// A FIR filter that uses polynomial fitting to smooth data while preserving -/// higher moments (peaks, valleys, and inflection points) better than simple averaging. /// /// -/// Key characteristics -/// -/// Uses polynomial fitting for smoothing -/// Preserves peak shapes better than standard MAs -/// Period must be odd (even periods are adjusted to next odd) -/// Polynomial degree (0-4) controls smoothing vs shape preservation -/// O(N) complexity per bar due to window convolution -/// +/// Polynomial-fitting FIR filter preserving peaks and inflection points. +/// Superior shape preservation vs standard MAs; odd period required. /// -/// Weight calculation -/// For polynomial degree d, weights are based on: -/// -/// w_i = 1 - |norm_x|^d where norm_x = (i - half_window) / half_window -/// -/// -/// Sources -/// Savitzky, A., Golay, M.J.E. (1964) - "Smoothing and Differentiation of Data by Simplified Least Squares Procedures" -/// Analytical Chemistry 36(8): 1627-1639 +/// Calculation: W_i = 1 - |norm_x|^d with degree 0-4 controlling smoothing. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Sgma : AbstractBase { diff --git a/lib/trends_FIR/sinema/Sinema.cs b/lib/trends_FIR/sinema/Sinema.cs index 6035819b..d1621ffb 100644 --- a/lib/trends_FIR/sinema/Sinema.cs +++ b/lib/trends_FIR/sinema/Sinema.cs @@ -8,21 +8,12 @@ namespace QuanTAlib; /// SINEMA: Sine-Weighted Moving Average /// /// -/// SINEMA applies sine-wave weighting to data points within the lookback window. -/// Weights are calculated as sin(π * (i+1) / period) for each position i, creating a -/// smooth bell-shaped weighting that emphasizes middle values while gracefully -/// tapering at the edges. -/// Calculation: -/// w[i] = sin(π * (i+1) / period) -/// SINEMA = Σ(P[i] * w[i]) / Σ(w[i]) +/// Sine-wave weighting creating smooth bell-shaped emphasis on middle values. +/// Better noise reduction than SMA while preserving mid-frequency trends. /// -/// Unlike SMA's uniform weighting or WMA's linear ramp, sine weighting provides -/// a smooth transition that can reduce high-frequency noise while preserving -/// mid-frequency trends. -/// -/// IsHot: -/// Becomes true when the buffer is full (period samples processed). +/// Calculation: W_i = sin(π×(i+1)/n); SINEMA = Σ(P_i×W_i) / Σ(W_i). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Sinema : AbstractBase { diff --git a/lib/trends_FIR/sma/Sma.cs b/lib/trends_FIR/sma/Sma.cs index ab014a71..b7d82b95 100644 --- a/lib/trends_FIR/sma/Sma.cs +++ b/lib/trends_FIR/sma/Sma.cs @@ -12,18 +12,12 @@ namespace QuanTAlib; /// SMA: Simple Moving Average /// /// -/// SMA calculates the arithmetic mean of the last n values. -/// Uses a RingBuffer for storage and manual running sum for O(1) complexity per update. -/// Calculation: -/// SMA = (P_n + P_(n-1) + ... + P_1) / n +/// Arithmetic mean of the last n values using running sum for O(1) updates. +/// SIMD-accelerated batch processing (AVX-512/AVX2/NEON). /// -/// O(1) update: -/// S_new = S_old - oldest + newest -/// SMA = S_new / n -/// -/// IsHot: -/// Becomes true when the buffer is full (period samples processed). +/// Calculation: SMA = Σ(values) / n. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Sma : AbstractBase { diff --git a/lib/trends_FIR/trima/Trima.cs b/lib/trends_FIR/trima/Trima.cs index 27b723e9..095d79ed 100644 --- a/lib/trends_FIR/trima/Trima.cs +++ b/lib/trends_FIR/trima/Trima.cs @@ -8,20 +8,11 @@ namespace QuanTAlib; /// TRIMA: Triangular Moving Average /// /// -/// TRIMA applies triangular weighting to data points, emphasizing the middle of the window. -/// Equivalent to a double SMA: SMA(SMA(period1), period2). +/// Triangular weighting emphasizing the middle via double SMA. O(1) updates. /// -/// Calculation: -/// p1 = (period + 1) / 2 -/// p2 = period / 2 + 1 -/// TRIMA = SMA(SMA(input, p1), p2) -/// -/// O(1) update: -/// Uses two SMA instances, each with O(1) update complexity. -/// -/// IsHot: -/// Becomes true when both internal SMAs are hot. +/// Calculation: TRIMA = SMA(SMA(p1), p2) where p1 = (n+1)/2, p2 = n/2+1. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Trima : AbstractBase { diff --git a/lib/trends_FIR/wma/Wma.cs b/lib/trends_FIR/wma/Wma.cs index 4c46685e..09b34682 100644 --- a/lib/trends_FIR/wma/Wma.cs +++ b/lib/trends_FIR/wma/Wma.cs @@ -10,18 +10,12 @@ namespace QuanTAlib; /// WMA: Weighted Moving Average /// /// -/// WMA applies linear weighting to data points, giving more weight to recent values. -/// Uses dual running sums for O(1) complexity per update. -/// Calculation: -/// WMA = (n*P_n + (n-1)*P_(n-1) + ... + 1*P_1) / (n*(n+1)/2) +/// Linear weighting giving more weight to recent values. O(1) via dual running sums. +/// SIMD-accelerated batch processing (AVX-512/AVX2/NEON). /// -/// O(1) update: -/// S_new = S - oldest + newest -/// W_new = W - S_old + n*newest -/// -/// IsHot: -/// Becomes true when the buffer is full (period samples processed). +/// Calculation: WMA = Σ(w_i × P_i) / Σ(w_i) where w_i = i. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Wma : AbstractBase { diff --git a/lib/trends_IIR/dema/Dema.cs b/lib/trends_IIR/dema/Dema.cs index bb6305ca..a5512a71 100644 --- a/lib/trends_IIR/dema/Dema.cs +++ b/lib/trends_IIR/dema/Dema.cs @@ -8,19 +8,13 @@ namespace QuanTAlib; /// DEMA: Double Exponential Moving Average /// /// -/// DEMA reduces the lag of traditional EMA by subtracting the lag from the original EMA. +/// Reduces lag by applying double smoothing and subtracting the extra smoothing. +/// More responsive than EMA while maintaining smoothness. /// -/// Calculation: -/// EMA1 = EMA(input) -/// EMA2 = EMA(EMA1) -/// DEMA = 2 * EMA1 - EMA2 -/// -/// O(1) update: -/// Uses two EMA instances, each with O(1) update complexity. -/// -/// IsHot: -/// Becomes true when the second EMA converges (approx. 2x EMA convergence time). +/// Calculation: DEMA = 2×EMA(p) - EMA(EMA(p)). /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Dema : AbstractBase { diff --git a/lib/trends_IIR/dsma/Dsma.cs b/lib/trends_IIR/dsma/Dsma.cs index 47482c8c..02abe25a 100644 --- a/lib/trends_IIR/dsma/Dsma.cs +++ b/lib/trends_IIR/dsma/Dsma.cs @@ -4,31 +4,16 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// Deviation-Scaled Moving Average (DSMA): -/// An adaptive moving average that uses standard deviation to dynamically adjust -/// its smoothing factor. Combines a 2-pole Super Smoother filter for trend estimation -/// with RMS-based deviation scaling for volatility adaptation. +/// DSMA: Deviation-Scaled Moving Average /// /// -/// Key characteristics: -/// - Uses Super Smoother (Butterworth) 2-pole IIR filter for trend extraction -/// - RMS (Root Mean Square) of filtered deviations for volatility measurement -/// - Dynamic alpha scaling based on deviation ratio (|filtered| / RMS) -/// - O(1) streaming updates via circular buffer for RMS calculation -/// - Adapts smoothing: faster in trending markets, slower in ranging markets -/// -/// Mathematical foundation: -/// 1. Super Smoother: H(z) = c₁(1 + z⁻¹) / (1 - b₁z⁻¹ + a₁²z⁻²) -/// where a₁ = exp(-√2·π/period), b₁ = 2a₁·cos(√2·π/period), c₁ = (1-b₁+a₁²)/2 -/// 2. RMS = √(Σ(filt²)/period) -/// 3. alpha = min(scaleFactor · 5/period · |filt|/RMS, 1) -/// 4. DSMA = alpha·price + (1-alpha)·prevDSMA -/// -/// Performance: -/// - Update: O(1) with FMA optimizations -/// - Memory: O(period) for RMS buffer -/// - SIMD: Calculate method uses vectorized RMS computation +/// Adaptive MA using 2-pole Super Smoother filter with RMS-based deviation scaling. +/// Faster in trending markets, slower in ranging conditions. +/// +/// Calculation: α = scaleFactor×5/period × |filt|/RMS; DSMA = α×P + (1-α)×DSMA_{t-1}. /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Dsma : AbstractBase { diff --git a/lib/trends_IIR/ema/Ema.cs b/lib/trends_IIR/ema/Ema.cs index 1e184b67..a167d6b9 100644 --- a/lib/trends_IIR/ema/Ema.cs +++ b/lib/trends_IIR/ema/Ema.cs @@ -9,23 +9,13 @@ namespace QuanTAlib; /// EMA: Exponential Moving Average /// /// -/// EMA applies exponential weighting to data points, giving more weight to recent values. -/// Uses a single state variable for O(1) complexity per update. +/// Applies exponentially decreasing weights to give more importance to recent values. +/// Faster response to price changes than SMA; commonly used for trend identification. /// -/// Calculation: -/// alpha = 2 / (period + 1) -/// EMA_new = EMA_old + alpha * (newest - EMA_old) -/// -/// Initialization: -/// Uses a compensator factor to correct early-stage bias (when n < period). -/// Output = EMA_state / (1 - (1-alpha)^n) -/// -/// O(1) update: -/// No buffer required, only previous EMA value and compensator state. -/// -/// IsHot: -/// Becomes true when n = ln(0.05) / ln(1 - alpha) +/// Calculation: EMA_t = α × Price_t + (1-α) × EMA_{t-1}, where α = 2/(period+1). /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Ema : AbstractBase { diff --git a/lib/trends_IIR/frama/Frama.cs b/lib/trends_IIR/frama/Frama.cs index 3b206e0c..cdc71a66 100644 --- a/lib/trends_IIR/frama/Frama.cs +++ b/lib/trends_IIR/frama/Frama.cs @@ -6,15 +6,16 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// FRAMA: Ehlers Fractal Adaptive Moving Average +/// FRAMA: Fractal Adaptive Moving Average /// /// -/// Classic Traders' Tips FRAMA: -/// - Ranges are computed from High/Low (not from source). -/// - Smoothed price is HL2. -/// - alpha = exp(-4.6 * (D - 1)), clamped to [0.01, 1]. -/// - Period forced to even, >= 2. +/// Ehlers' adaptive MA using fractal dimension to compute smoothing factor. +/// Alpha derived from High/Low ranges; smoother in trends, reactive at reversals. +/// +/// Calculation: D = ln(N1+N2)-ln(N3) / ln(2); α = exp(-4.6×(D-1)), clamped [0.01,1]. /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Frama : ITValuePublisher, IDisposable { diff --git a/lib/trends_IIR/htit/Htit.cs b/lib/trends_IIR/htit/Htit.cs index 38e852ea..43433f5b 100644 --- a/lib/trends_IIR/htit/Htit.cs +++ b/lib/trends_IIR/htit/Htit.cs @@ -4,15 +4,16 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// HTIT: Ehlers Hilbert Transform Instantaneous Trend -/// A trend-following indicator that uses the Hilbert Transform to measure the dominant cycle period -/// and compute an instantaneous trendline. It adapts to market cycles to reduce lag while maintaining smoothness. +/// HTIT: Hilbert Transform Instantaneous Trendline /// /// -/// Sources: -/// https://github.com/mihakralj/pinescript/blob/main/indicators/trends_IIR/htit.md -/// https://dotnet.stockindicators.dev/indicators/HtTrendline/ +/// Ehlers' adaptive trendline using Hilbert Transform cycle measurement. +/// Averages price over the measured dominant cycle period for cycle-adaptive smoothing. +/// +/// Key features: homodyne discriminator, period-adaptive averaging window. /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Htit : AbstractBase { diff --git a/lib/trends_IIR/jma/Jma.cs b/lib/trends_IIR/jma/Jma.cs index ab665396..20ec63df 100644 --- a/lib/trends_IIR/jma/Jma.cs +++ b/lib/trends_IIR/jma/Jma.cs @@ -4,13 +4,16 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// Jurik Moving Average (JMA): -/// - 10-bar SMA of local deviation -/// - 128-sample volatility distribution -/// - middle-65 trimmed mean as volatility reference -/// - Jurik dynamic exponent and 2-pole IIR core -/// - power parameter kept for API compatibility; ignored (matches Pine reference) +/// JMA: Jurik Moving Average /// +/// +/// Proprietary adaptive filter with minimal lag and overshoot using volatility-based smoothing. +/// Combines 2-pole IIR core with trimmed-mean volatility estimation. +/// +/// Key features: phase control [-100,100], adaptive band tracking, dynamic exponent. +/// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Jma : AbstractBase { diff --git a/lib/trends_IIR/kama/Kama.cs b/lib/trends_IIR/kama/Kama.cs index ca3ec036..86aaac0b 100644 --- a/lib/trends_IIR/kama/Kama.cs +++ b/lib/trends_IIR/kama/Kama.cs @@ -7,16 +7,13 @@ namespace QuanTAlib; /// KAMA: Kaufman's Adaptive Moving Average /// /// -/// KAMA adapts to market volatility by adjusting its smoothing factor based on an Efficiency Ratio (ER). -/// ER is calculated as the ratio of the absolute price change over a period to the sum of absolute price changes (volatility). +/// Adapts smoothing based on efficiency ratio (signal/noise) to reduce whipsaws in ranging markets. +/// Faster in trends, slower during consolidation. /// -/// Formula: -/// ER = Change / Volatility -/// Change = Abs(Price - Price[period]) -/// Volatility = Sum(Abs(Price[i] - Price[i-1]), period) -/// SC = (ER * (fast_alpha - slow_alpha) + slow_alpha)^2 -/// KAMA = KAMA[prev] + SC * (Price - KAMA[prev]) +/// Calculation: ER = |Change|/Volatility; SC = (ER×(αfast-αslow)+αslow)²; KAMA += SC×(P-KAMA). /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Kama : AbstractBase { diff --git a/lib/trends_IIR/mama/Mama.cs b/lib/trends_IIR/mama/Mama.cs index aa806524..c5c8b2af 100644 --- a/lib/trends_IIR/mama/Mama.cs +++ b/lib/trends_IIR/mama/Mama.cs @@ -4,9 +4,16 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// MESA Adaptive Moving Average (MAMA) -/// A trend-following indicator that adapts to the market's phase rate of change. +/// MAMA: MESA Adaptive Moving Average /// +/// +/// Ehlers' dual-output adaptive filter using Hilbert Transform for cycle measurement. +/// MAMA tracks price closely; FAMA provides smoother confirmation signal. +/// +/// Key features: homodyne discriminator, adaptive alpha from phase rate-of-change. +/// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Mama : AbstractBase { diff --git a/lib/trends_IIR/mgdi/Mgdi.cs b/lib/trends_IIR/mgdi/Mgdi.cs index 3907e94b..1c3a149a 100644 --- a/lib/trends_IIR/mgdi/Mgdi.cs +++ b/lib/trends_IIR/mgdi/Mgdi.cs @@ -5,17 +5,14 @@ namespace QuanTAlib; /// /// MGDI: McGinley Dynamic Indicator -/// A moving average that adjusts for shifts in market speed, designed to track the market better than existing indicators. -/// It looks like a moving average line, yet it is a smoothing mechanism for prices that turns out to track far better than any moving average. -/// It minimizes price separation and price hugs to avoid whipsaws. /// /// -/// Sources: -/// https://www.investopedia.com/terms/m/mcginley-dynamic.asp -/// https://dotnet.stockindicators.dev/indicators/Dynamic/ -/// Formula: MGDI = MGDI[1] + (Price - MGDI[1]) / (k * N * (Price/MGDI[1])^4) -/// Default k = 0.6 +/// Self-adjusting MA that tracks price better by adapting to market speed shifts. +/// Uses price-to-MA ratio raised to 4th power for speed adjustment. +/// +/// Calculation: MGDI = MGDI_{t-1} + (P - MGDI_{t-1}) / (k×N×(P/MGDI)^4). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Mgdi : AbstractBase { diff --git a/lib/trends_IIR/qema/Qema.cs b/lib/trends_IIR/qema/Qema.cs index 4d1ba859..1c4048ee 100644 --- a/lib/trends_IIR/qema/Qema.cs +++ b/lib/trends_IIR/qema/Qema.cs @@ -4,26 +4,15 @@ using System.Runtime.InteropServices; namespace QuanTAlib; /// -/// QEMA: Quad Exponential Moving Average with Progressive Alphas and Zero-Lag Weighting +/// QEMA: Quad Exponential Moving Average /// /// -/// QEMA uses four cascaded EMAs with progressively increasing alphas (decreasing responsiveness) -/// and combines them using optimized weights that minimize energy while achieving zero DC lag. +/// Four cascaded EMAs with progressive alphas combined using zero-lag optimized weights. +/// Minimizes energy while achieving zero DC lag through Lagrange optimization. /// -/// Calculation: -/// 1. Base alpha: α₁ = 2 / (period + 1) -/// 2. Progressive alphas: r = (1/α₁)^(1/4), then α₂ = α₁·r, α₃ = α₂·r, α₄ = α₃·r -/// 3. Four cascaded EMAs: EMA1(input), EMA2(EMA1), EMA3(EMA2), EMA4(EMA3) -/// 4. Cumulative lags: L₁ = (1-α₁)/α₁, L₂ = L₁ + (1-α₂)/α₂, etc. -/// 5. Option A weights: Minimize energy subject to Σw=1 and Σw·L=0 (zero DC lag) -/// 6. Output: w₁·EMA1 + w₂·EMA2 + w₃·EMA3 + w₄·EMA4 -/// -/// O(1) update: -/// Uses four EMA state accumulators, each with O(1) update complexity. -/// -/// IsHot: -/// Becomes true when the slowest EMA (stage 1) has converged to within 5% coverage. +/// Key features: progressive alpha ramp (α^(1/4) spacing), bias-corrected EMAs, O(1) streaming. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Qema : AbstractBase { diff --git a/lib/trends_IIR/rema/Rema.cs b/lib/trends_IIR/rema/Rema.cs index b629dbbf..841852d7 100644 --- a/lib/trends_IIR/rema/Rema.cs +++ b/lib/trends_IIR/rema/Rema.cs @@ -9,26 +9,12 @@ namespace QuanTAlib; /// REMA: Regularized Exponential Moving Average /// /// -/// REMA combines exponential smoothing with a regularization term that penalizes -/// deviations from the previous trend direction. This produces a smoother output -/// than standard EMA while maintaining responsiveness to genuine price changes. +/// Combines EMA smoothing with regularization term penalizing trend direction changes. +/// Lambda controls blend: 0 = pure momentum, 1 = standard EMA. /// -/// Calculation: -/// alpha = 2 / (period + 1) -/// ema_component = alpha * (source - rema) + rema -/// reg_component = rema + (rema - prev_rema) // momentum continuation -/// REMA = lambda * (ema_component - reg_component) + reg_component -/// -/// Parameters: -/// - period: Controls the EMA decay rate (alpha = 2/(period+1)) -/// - lambda: Regularization strength (0 = max regularization, 1 = standard EMA) -/// -/// O(1) update: -/// Only requires previous REMA and prev_prev_REMA values. -/// -/// IsHot: -/// Becomes true after sufficient warmup similar to EMA. +/// Calculation: REMA = λ×(EMA_comp - REG_comp) + REG_comp. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Rema : AbstractBase { diff --git a/lib/trends_IIR/rma/Rma.cs b/lib/trends_IIR/rma/Rma.cs index 7e69e78a..4000423d 100644 --- a/lib/trends_IIR/rma/Rma.cs +++ b/lib/trends_IIR/rma/Rma.cs @@ -3,19 +3,16 @@ using System.Runtime.CompilerServices; namespace QuanTAlib; /// -/// RMA: Running Moving Average (also known as Wilder's Moving Average or SMMA) +/// RMA: Running Moving Average (Wilder's Moving Average) /// /// -/// RMA is an Exponential Moving Average (EMA) with a different smoothing factor. -/// While EMA uses alpha = 2 / (period + 1), RMA uses alpha = 1 / period. +/// EMA variant using α=1/period for smoother, slower response than standard EMA. +/// Commonly used in ATR and RSI calculations per Wilder's original methodology. /// -/// Calculation: -/// alpha = 1 / period -/// RMA_new = RMA_old + alpha * (newest - RMA_old) -/// -/// This implementation wraps the EMA implementation to ensure identical behavior and performance, -/// utilizing the same O(1) update complexity and zero-allocation architecture. +/// Calculation: RMA_t = α×Price + (1-α)×RMA_{t-1}, where α = 1/period. /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Rma : AbstractBase { diff --git a/lib/trends_IIR/t3/T3.cs b/lib/trends_IIR/t3/T3.cs index 90caf8eb..f9232f9c 100644 --- a/lib/trends_IIR/t3/T3.cs +++ b/lib/trends_IIR/t3/T3.cs @@ -7,22 +7,13 @@ namespace QuanTAlib; /// T3: Tillson T3 Moving Average /// /// -/// T3 works by running price data through a series of six EMAs, then combining the outputs -/// of these EMAs using carefully calculated weights. +/// Six cascaded EMAs with weighted combination for ultra-smooth trend following. +/// The volume factor controls overshooting behavior; lower values reduce lag. /// -/// Formula: -/// T3 = c1*e6 + c2*e5 + c3*e4 + c4*e3 -/// -/// Where: -/// e1..e6 are cascaded EMAs -/// c1 = -v^3 -/// c2 = 3(v^2 + v^3) -/// c3 = -3(2v^2 + v + v^3) -/// c4 = 1 + 3v + 3v^2 + v^3 -/// -/// v is volume factor (default 0.7) -/// alpha = 2 / (period + 1) +/// Calculation: T3 = c1×e6 + c2×e5 + c3×e4 + c4×e3 (six EMAs with polynomial weights). /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class T3 : AbstractBase { diff --git a/lib/trends_IIR/tema/Tema.cs b/lib/trends_IIR/tema/Tema.cs index 554794d9..1366da1f 100644 --- a/lib/trends_IIR/tema/Tema.cs +++ b/lib/trends_IIR/tema/Tema.cs @@ -7,22 +7,13 @@ namespace QuanTAlib; /// TEMA: Triple Exponential Moving Average /// /// -/// TEMA uses triple smoothing to reduce lag even further than DEMA. +/// Uses triple smoothing to further reduce lag beyond DEMA. +/// Excellent for fast trend identification with minimal overshoot. /// -/// Calculation: -/// EMA1 = EMA(input) -/// EMA2 = EMA(EMA1) -/// EMA3 = EMA(EMA2) -/// TEMA = 3 * EMA1 - 3 * EMA2 + EMA3 -/// -/// O(1) update: -/// Uses three EMA instances, each with O(1) update complexity. -/// -/// IsHot: -/// Becomes true when the TEMA step response converges to within 5% error. -/// This happens when the third EMA's error factor drops below ~9% (approx 2.43/alpha steps), -/// which is faster than the standard EMA convergence (3/alpha steps). +/// Calculation: TEMA = 3×EMA1 - 3×EMA2 + EMA3 (cascaded EMAs). /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Tema : AbstractBase { diff --git a/lib/trends_IIR/vama/Vama.cs b/lib/trends_IIR/vama/Vama.cs index f3644a51..88b4c06d 100644 --- a/lib/trends_IIR/vama/Vama.cs +++ b/lib/trends_IIR/vama/Vama.cs @@ -7,20 +7,12 @@ namespace QuanTAlib; /// VAMA: Volatility Adjusted Moving Average /// /// -/// VAMA dynamically adjusts its smoothing period based on the ratio of long-term -/// to short-term volatility (measured via ATR). During low volatility periods, -/// the effective period increases for smoother output; during high volatility, -/// it decreases for faster response. +/// Adaptive MA that adjusts period based on long/short ATR volatility ratio. +/// Higher volatility → shorter period (faster); lower volatility → longer period (smoother). /// -/// Calculation: -/// 1. Short ATR = RMA(TR, short_period) with bias compensation -/// 2. Long ATR = RMA(TR, long_period) with bias compensation -/// 3. Volatility Ratio = Long_ATR / Short_ATR (clamped to avoid division by zero) -/// 4. Adjusted Length = base_length * volatility_ratio, clamped to [min_length, max_length] -/// 5. VAMA = SMA(source, adjusted_length) -/// -/// O(1) ATR updates via RMA; O(adjusted_length) for SMA over the buffer. +/// Calculation: length = baseLength × (LongATR/ShortATR), clamped to [min, max]. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Vama : AbstractBase { diff --git a/lib/trends_IIR/vidya/Vidya.cs b/lib/trends_IIR/vidya/Vidya.cs index f3f6cb9a..2a77a0c0 100644 --- a/lib/trends_IIR/vidya/Vidya.cs +++ b/lib/trends_IIR/vidya/Vidya.cs @@ -7,22 +7,13 @@ namespace QuanTAlib; /// VIDYA: Variable Index Dynamic Average /// /// -/// VIDYA is an adaptive moving average developed by Tushar Chande. -/// It adjusts the smoothing constant of an Exponential Moving Average (EMA) based on a volatility index. -/// The volatility index used is the Chande Momentum Oscillator (CMO). +/// Tushar Chande's adaptive MA using CMO as volatility index to modulate smoothing. +/// Flat in choppy markets, responsive in trending conditions. /// -/// Formula: -/// alpha = 2 / (period + 1) -/// CMO = (Sum(Up) - Sum(Down)) / (Sum(Up) + Sum(Down)) -/// VI = Abs(CMO) -/// DynamicAlpha = alpha * VI -/// VIDYA = DynamicAlpha * Price + (1 - DynamicAlpha) * VIDYA_prev -/// -/// Key characteristics: -/// - Adapts to market volatility -/// - Flattens in ranging markets (low volatility) -/// - Reacts quickly in trending markets (high volatility) +/// Calculation: VI = |CMO|; α' = α×VI; VIDYA = α'×P + (1-α')×VIDYA_{t-1}. /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Vidya : AbstractBase { diff --git a/lib/trends_IIR/yzvama/Yzvama.cs b/lib/trends_IIR/yzvama/Yzvama.cs index 0bf68e15..a1e5a4c9 100644 --- a/lib/trends_IIR/yzvama/Yzvama.cs +++ b/lib/trends_IIR/yzvama/Yzvama.cs @@ -7,16 +7,12 @@ namespace QuanTAlib; /// YZVAMA: Yang-Zhang Volatility Adjusted Moving Average /// /// -/// YZVAMA adjusts the SMA length based on the percentile rank of short-term -/// Yang-Zhang volatility (YZV) observed over a rolling lookback window. +/// Adaptive MA using Yang-Zhang volatility percentile rank to adjust SMA length. +/// Higher volatility → shorter period; uses OHLC log returns for variance. /// -/// Calculation (per bar): -/// 1) Compute Yang-Zhang daily variance proxy from OHLC (log returns). -/// 2) Smooth variance with bias-compensated RMA for short and long periods (sqrt -> volatility). -/// 3) Compute percentile rank of current short YZV within the lookback window. -/// 4) Map percentile to adjusted SMA length: higher volatility -> shorter length. -/// 5) Output SMA(source, adjusted_length) over a circular buffer. +/// Calculation: length = max - percentile×(max-min). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Yzvama : AbstractBase { diff --git a/lib/trends_IIR/zlema/Zlema.cs b/lib/trends_IIR/zlema/Zlema.cs index 43105dca..631fae6c 100644 --- a/lib/trends_IIR/zlema/Zlema.cs +++ b/lib/trends_IIR/zlema/Zlema.cs @@ -9,10 +9,13 @@ namespace QuanTAlib; /// ZLEMA: Zero-Lag Exponential Moving Average /// /// -/// ZLEMA reduces EMA lag by filtering a zero-lag signal: -/// signal = 2 * price - price_lag -/// zlema = EMA(signal) +/// Reduces lag by applying EMA to a detrended signal that subtracts lagged values. +/// Offers faster trend detection while maintaining smoothness. +/// +/// Calculation: ZLEMA = EMA(2×Price - Price[lag]), where lag = (period-1)/2. /// +/// Detailed documentation +/// Reference Pine Script implementation [SkipLocalsInit] public sealed class Zlema : AbstractBase { diff --git a/lib/volatility/adr/Adr.cs b/lib/volatility/adr/Adr.cs index d346127f..57faeb03 100644 --- a/lib/volatility/adr/Adr.cs +++ b/lib/volatility/adr/Adr.cs @@ -6,18 +6,12 @@ namespace QuanTAlib; /// ADR: Average Daily Range /// /// -/// ADR measures the average price movement range over a specified period. -/// Unlike ATR, ADR uses only the High-Low range without accounting for gaps. +/// Smoothed average of High-Low ranges; simpler than ATR (no gap accounting). +/// Supports SMA/EMA/WMA smoothing methods. /// -/// Calculation: -/// 1. Daily Range = High - Low -/// 2. ADR = MA(Daily Range, period) -/// -/// Supports three smoothing methods: -/// - SMA (Simple Moving Average) - default -/// - EMA (Exponential Moving Average) -/// - WMA (Weighted Moving Average) +/// Calculation: ADR = MA(High - Low, period). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Adr : AbstractBase { diff --git a/lib/volatility/atr/Atr.cs b/lib/volatility/atr/Atr.cs index 35868262..e6918ae2 100644 --- a/lib/volatility/atr/Atr.cs +++ b/lib/volatility/atr/Atr.cs @@ -6,17 +6,12 @@ namespace QuanTAlib; /// ATR: Average True Range /// /// -/// ATR measures the volatility of an asset. -/// It is the moving average (typically RMA/Wilder's) of the True Range. +/// Wilder's volatility measure using RMA-smoothed True Range. +/// Accounts for gaps via max of H-L, |H-PrevClose|, |L-PrevClose|. /// -/// Calculation: -/// 1. True Range (TR) = Max(High - Low, |High - PrevClose|, |Low - PrevClose|) -/// - For the first bar, TR = High - Low -/// 2. ATR = RMA(TR) -/// -/// Sources: -/// "New Concepts in Technical Trading Systems" by J. Welles Wilder +/// Calculation: ATR = RMA(TR, period). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Atr : AbstractBase { diff --git a/lib/volatility/atrn/Atrn.cs b/lib/volatility/atrn/Atrn.cs index df6c644d..5d33a061 100644 --- a/lib/volatility/atrn/Atrn.cs +++ b/lib/volatility/atrn/Atrn.cs @@ -7,18 +7,12 @@ namespace QuanTAlib; /// ATRN: Average True Range Normalized /// /// -/// ATRN normalizes the ATR to a [0,1] range using min-max scaling over a lookback window. -/// This makes volatility comparable across different price scales and time periods. +/// ATR normalized to [0,1] via min-max scaling over 10×period lookback. +/// Enables cross-asset volatility comparison regardless of price scale. /// -/// Calculation: -/// 1. Calculate ATR using RMA smoothing -/// 2. Find min/max ATR over lookback window (10 * period) -/// 3. Normalize: (ATR - minATR) / (maxATR - minATR) -/// 4. If maxATR equals minATR, return 0.5 -/// -/// Sources: -/// Derived from ATR by J. Welles Wilder, normalized for cross-asset comparison. +/// Calculation: ATRN = (ATR - minATR) / (maxATR - minATR). /// +/// Detailed documentation [SkipLocalsInit] public sealed class Atrn : AbstractBase { diff --git a/lib/volatility/atrp/Atrp.cs b/lib/volatility/atrp/Atrp.cs index b741c2c4..293eb992 100644 --- a/lib/volatility/atrp/Atrp.cs +++ b/lib/volatility/atrp/Atrp.cs @@ -7,23 +7,12 @@ namespace QuanTAlib; /// ATRP: Average True Range Percent /// /// -/// ATRP normalizes ATR as a percentage of the closing price, enabling volatility -/// comparison across different assets regardless of their price levels. +/// ATR as percentage of closing price for cross-asset volatility comparison. +/// Higher values indicate greater relative volatility; typical range 0-10%. /// -/// Calculation: -/// 1. True Range (TR) = Max(High - Low, |High - PrevClose|, |Low - PrevClose|) -/// - For the first bar, TR = High - Low -/// 2. ATR = RMA(TR, Period) with warmup compensation -/// 3. ATRP = (ATR / Close) × 100 -/// -/// Key characteristics: -/// - Normalized volatility allows cross-asset comparison -/// - Higher ATRP indicates higher relative volatility -/// - Typical values range from 0 to 10+ depending on asset class -/// -/// Sources: -/// Derived from ATR by J. Welles Wilder, expressed as percentage. +/// Calculation: ATRP = (ATR / Close) × 100. /// +/// Detailed documentation [SkipLocalsInit] public sealed class Atrp : AbstractBase {