pine files

This commit is contained in:
Miha Kralj
2026-01-31 14:05:53 -08:00
parent 51e885a4a6
commit 5ed4b6c0fc
102 changed files with 2883 additions and 593 deletions
+1 -1
View File
@@ -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
+178
View File
@@ -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<float> volWindow = array.new_float(10, 0.0)
// 128-bar volatility distribution
var int distIndex = 0
var int distCount = 0
var array<float> distWindow = array.new_float(128, 0.0)
var array<float> 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")
+4 -17
View File
@@ -6,25 +6,12 @@ namespace QuanTAlib;
/// ADX: Average Directional Index
/// </summary>
/// <remarks>
/// 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: <c>ADX = RMA(DX)</c> where <c>DX = |+DI - -DI| / (+DI + -DI) × 100</c>.
/// </remarks>
/// <seealso href="Adx.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Adx : ITValuePublisher
{
+4 -8
View File
@@ -7,16 +7,12 @@ namespace QuanTAlib;
/// ADXR: Average Directional Movement Rating
/// </summary>
/// <remarks>
/// 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: <c>ADXR = (ADX + ADX[Period]) / 2</c>.
/// </remarks>
/// <seealso href="Adxr.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Adxr : ITValuePublisher
{
+4 -17
View File
@@ -8,25 +8,12 @@ namespace QuanTAlib;
/// AMAT: Archer Moving Averages Trends
/// </summary>
/// <remarks>
/// 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 &lt; 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: <c>+1</c> when FastEMA > SlowEMA and both rising; <c>-1</c> when FastEMA &lt; SlowEMA and both falling.
/// </remarks>
/// <seealso href="Amat.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Amat : ITValuePublisher, IDisposable
{
+5 -13
View File
@@ -4,23 +4,15 @@ using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Aroon Indicator
/// AROON: Aroon Indicator
/// </summary>
/// <remarks>
/// 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: <c>Up = (Period - DaysSinceHigh) / Period × 100</c>; <c>Down = (Period - DaysSinceLow) / Period × 100</c>.
/// </remarks>
/// <seealso href="Aroon.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Aroon : ITValuePublisher
{
+5 -13
View File
@@ -3,23 +3,15 @@ using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// Aroon Oscillator
/// AROONOSC: Aroon Oscillator
/// </summary>
/// <remarks>
/// 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: <c>AroonOsc = AroonUp - AroonDown</c>.
/// </remarks>
/// <seealso href="AroonOsc.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class AroonOsc : ITValuePublisher
{
+8 -3
View File
@@ -5,10 +5,15 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// DMX Jurik Directional Movement Index
/// A smoother, lower-lag alternative to Welles Wilders DMI/ADX.
/// Uses Jurik Moving Average (JMA) for smoothing directional movement components.
/// DMX: Jurik Directional Movement Index
/// </summary>
/// <remarks>
/// Smoother DMI alternative using JMA instead of Wilder smoothing (Jurik).
/// Lower lag than ADX while maintaining directional trend detection.
///
/// Calculation: <c>DMX = DI+ - DI-</c> where DI values use JMA-smoothed +DM/-DM/TR.
/// </remarks>
/// <seealso href="Dmx.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Dmx : ITValuePublisher
{
+8 -2
View File
@@ -4,9 +4,15 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// SuperTrend Indicator
/// A trend-following indicator that uses ATR to define upper and lower bands.
/// SUPER: SuperTrend Indicator
/// </summary>
/// <remarks>
/// ATR-based trend follower that switches between upper/lower bands on price breakouts.
/// Returns current SuperTrend level plus bullish/bearish state.
///
/// Calculation: <c>Bands = HL2 ± Multiplier × ATR</c>; trend flips when price crosses opposite band.
/// </remarks>
/// <seealso href="Super.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Super : ITValuePublisher
{
+47
View File
@@ -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)
+38
View File
@@ -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)
+45
View File
@@ -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)
+45
View File
@@ -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)
+45
View File
@@ -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)
+56
View File
@@ -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)
+33
View File
@@ -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)
+37
View File
@@ -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)
+45
View File
@@ -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)
+45
View File
@@ -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)
+38
View File
@@ -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)
+46
View File
@@ -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)
+45
View File
@@ -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)
+41
View File
@@ -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)
+36
View File
@@ -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)
+75
View File
@@ -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)
+45
View File
@@ -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)
+45
View File
@@ -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)
+74
View File
@@ -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)
+75
View File
@@ -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)
+48
View File
@@ -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)
+45
View File
@@ -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)
+49
View File
@@ -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)
+41
View File
@@ -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)
+53
View File
@@ -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)
+44
View File
@@ -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)
+47
View File
@@ -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)
+43
View File
@@ -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)
+44
View File
@@ -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)
+4 -14
View File
@@ -8,22 +8,12 @@ namespace QuanTAlib;
/// BOP: Balance of Power
/// </summary>
/// <remarks>
/// 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: <c>BOP = (Close - Open) / (High - Low)</c>.
/// </remarks>
/// <seealso href="Bop.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Bop : ITValuePublisher
{
+45
View File
@@ -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)
+4 -16
View File
@@ -25,24 +25,12 @@ file static class CfbDefaults
/// CFB: Jurik Composite Fractal Behavior (Trend Duration Index)
/// </summary>
/// <remarks>
/// 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: <c>CFB = Σ(L×Ratio) / Σ(Ratio)</c> for lengths where <c>NetMove/TotalVol ≥ 0.25</c>.
/// </remarks>
/// <seealso href="Cfb.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Cfb : ITValuePublisher, IDisposable
{
+79
View File
@@ -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<float> 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)
+4 -8
View File
@@ -8,16 +8,12 @@ namespace QuanTAlib;
/// MACD: Moving Average Convergence Divergence
/// </summary>
/// <remarks>
/// 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: <c>MACD = FastEMA - SlowEMA</c>, <c>Signal = EMA(MACD)</c>, <c>Histogram = MACD - Signal</c>.
/// </remarks>
/// <seealso href="Macd.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Macd : ITValuePublisher, IDisposable
{
+75
View File
@@ -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)
+5 -10
View File
@@ -1,22 +1,17 @@
// ROC: Rate of Change (Absolute)
// Calculates absolute price change: current - past
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// ROC: Rate of Change (Absolute)
/// Calculates the absolute difference between current value and value N periods ago.
/// Formula: current - past
/// </summary>
/// <remarks>
/// 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: <c>ROC = Price - Price[N]</c>.
/// </remarks>
/// <seealso href="Roc.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Roc : AbstractBase
{
+4 -9
View File
@@ -8,17 +8,12 @@ namespace QuanTAlib;
/// RSI: Relative Strength Index
/// </summary>
/// <remarks>
/// 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: <c>RSI = 100 - 100/(1 + RS)</c> where <c>RS = AvgGain/AvgLoss</c>.
/// </remarks>
/// <seealso href="Rsi.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Rsi : AbstractBase
{
+36
View File
@@ -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)
+4 -11
View File
@@ -7,19 +7,12 @@ namespace QuanTAlib;
/// RSX: Jurik Relative Strength Index (Jurik's RSI Variant)
/// </summary>
/// <remarks>
/// 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 → <c>RSX = (ratio + 1) × 50</c>.
/// </remarks>
/// <seealso href="Rsx.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Rsx : ITValuePublisher
{
+90
View File
@@ -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)
+4 -6
View File
@@ -8,14 +8,12 @@ namespace QuanTAlib;
/// VEL: Jurik Velocity
/// </summary>
/// <remarks>
/// 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: <c>VEL = PWMA(Period) - WMA(Period)</c>.
/// </remarks>
/// <seealso href="Vel.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Vel : ITValuePublisher, IDisposable
{
+64
View File
@@ -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)
+55
View File
@@ -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<float> fastBuf = array.new_float(fastLength, na)
var array<float> 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)
+50
View File
@@ -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)
+92
View File
@@ -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<float> bp_fast_buffer = array.new_float(fastPeriod, na)
var array<float> tr_fast_buffer = array.new_float(fastPeriod, na)
var array<float> bp_medium_buffer = array.new_float(mediumPeriod, na)
var array<float> tr_medium_buffer = array.new_float(mediumPeriod, na)
var array<float> bp_slow_buffer = array.new_float(slowPeriod, na)
var array<float> 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)
+72
View File
@@ -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)
+35
View File
@@ -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)
+56
View File
@@ -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<float> buffer1 = array.new_float(p, na)
var array<float> 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)
+59
View File
@@ -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<float> 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)
+42
View File
@@ -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<float> 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)
+84
View File
@@ -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<float> 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<float> 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<float> 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)
+42
View File
@@ -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<float> 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
+59
View File
@@ -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)
+41
View File
@@ -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<float> 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)
+4 -6
View File
@@ -8,14 +8,12 @@ namespace QuanTAlib;
/// ALMA: Arnaud Legoux Moving Average
/// </summary>
/// <remarks>
/// 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: <c>W_i = exp(-(i - m)² / (2s²))</c> where <c>m = offset × (period-1)</c>.
/// </remarks>
/// <seealso href="Alma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Alma : AbstractBase
{
+7 -1
View File
@@ -4,8 +4,14 @@ namespace QuanTAlib;
/// <summary>
/// BLMA: Blackman Moving Average
/// A weighted moving average using the Blackman window function for smoother transitions.
/// </summary>
/// <remarks>
/// 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: <c>W_i = 0.42 - 0.5×cos(2πi/(n-1)) + 0.08×cos(4πi/(n-1))</c>.
/// </remarks>
/// <seealso href="Blma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Blma : AbstractBase
{
+5 -5
View File
@@ -8,12 +8,12 @@ namespace QuanTAlib;
/// BWMA: Bessel-Weighted Moving Average
/// </summary>
/// <remarks>
/// BWMA applies a Bessel window over the last N samples (FIR).
/// <para>Window coefficient definition:</para>
/// <para>x(i) = 2*i/(p-1) - 1 (maps i to [-1, 1]), arg = 1 - x(i)^2</para>
/// <para>w(i) = arg^(order/2 + 0.5) (with PineScript special-cases for order 0 and 1)</para>
/// <para>Output = sum(window[i] * w(i)) / sum(w(i))</para>
/// FIR MA using Bessel window coefficients with adjustable order.
/// Higher order produces sharper window; order 0 = parabolic.
///
/// Calculation: <c>W_i = (1 - x²)^(order/2 + 0.5)</c> where <c>x = 2i/(n-1) - 1</c>.
/// </remarks>
/// <seealso href="Bwma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Bwma : AbstractBase
{
+5 -12
View File
@@ -4,22 +4,15 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// Convolution Indicator
/// CONV: Convolution Filter
/// </summary>
/// <remarks>
/// 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: <c>Result = Σ(kernel[i] × data[i])</c> where kernel[0] weights oldest sample.
/// </remarks>
/// <seealso href="Conv.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Conv : AbstractBase
{
+4 -4
View File
@@ -8,12 +8,12 @@ namespace QuanTAlib;
/// DWMA: Double Weighted Moving Average
/// </summary>
/// <remarks>
/// 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: <c>DWMA = WMA(WMA(source, n), n)</c>.
/// </remarks>
/// <seealso href="Dwma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Dwma : AbstractBase
{
+4 -7
View File
@@ -8,15 +8,12 @@ namespace QuanTAlib;
/// GWMA: Gaussian-Weighted Moving Average
/// </summary>
/// <remarks>
/// 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: <c>W_i = exp(-0.5×((i - center)/(σ×n))²)</c> centered at <c>(n-1)/2</c>.
/// </remarks>
/// <seealso href="Gwma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Gwma : AbstractBase
{
+4 -21
View File
@@ -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;
/// <summary>
/// HAMMA: Hamming Moving Average
/// A weighted moving average using Hamming window coefficients, providing good
/// spectral characteristics with reduced side lobes compared to simple windowing.
/// </summary>
/// <remarks>
/// <b>Key characteristics</b>
/// <list type="bullet">
/// <item><description>Hamming window: w[i] = 0.54 - 0.46 × cos(2πi/(period-1))</description></item>
/// <item><description>Raised-cosine window with specific coefficients for optimal side-lobe suppression</description></item>
/// <item><description>First side lobe is approximately -43 dB down from main lobe</description></item>
/// <item><description>Widely used in digital signal processing and spectral analysis</description></item>
/// </list>
/// Window-based MA using Hamming raised-cosine coefficients (0.54/0.46).
/// -43 dB first side lobe for superior spectral characteristics.
///
/// <b>Calculation</b>
/// <code>
/// w[i] = 0.54 - 0.46 × cos(2π × i / (period - 1))
/// HAMMA = Σ(price[i] × w[i]) / Σ(w[i])
/// </code>
///
/// <b>Sources</b>
/// Richard W. Hamming - "Digital Filters" (1977)
/// Oppenheim, Schafer - "Discrete-Time Signal Processing"
/// Calculation: <c>W_i = 0.54 - 0.46×cos(2πi/(n-1))</c>.
/// </remarks>
/// <seealso href="Hamma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Hamma : AbstractBase
{
+4 -21
View File
@@ -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;
/// <summary>
/// HANMA: Hanning Moving Average
/// A weighted moving average using Hanning (Hann) window coefficients, providing
/// excellent spectral characteristics with smooth transitions at window edges.
/// </summary>
/// <remarks>
/// <b>Key characteristics</b>
/// <list type="bullet">
/// <item><description>Hanning window: w[i] = 0.5 × (1 - cos(2πi/(period-1)))</description></item>
/// <item><description>Raised-cosine window that reaches zero at both endpoints</description></item>
/// <item><description>First side lobe is approximately -32 dB down from main lobe</description></item>
/// <item><description>Also known as Hann window (after Julius von Hann)</description></item>
/// </list>
/// Window-based MA using Hanning (Hann) raised-cosine coefficients.
/// Zero at endpoints for smooth spectral transition; -32 dB first side lobe.
///
/// <b>Calculation</b>
/// <code>
/// w[i] = 0.5 × (1 - cos(2π × i / (period - 1)))
/// HANMA = Σ(price[i] × w[i]) / Σ(w[i])
/// </code>
///
/// <b>Sources</b>
/// Julius von Hann - Austrian meteorologist
/// Blackman, Tukey - "The Measurement of Power Spectra" (1958)
/// Calculation: <c>W_i = 0.5×(1 - cos(2πi/(n-1)))</c>.
/// </remarks>
/// <seealso href="Hanma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Hanma : AbstractBase
{
+4 -6
View File
@@ -10,14 +10,12 @@ namespace QuanTAlib;
/// HMA: Hull Moving Average
/// </summary>
/// <remarks>
/// 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: <c>HMA = WMA(√n, 2×WMA(n/2) - WMA(n))</c>.
/// </remarks>
/// <seealso href="Hma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Hma : AbstractBase
{
+4 -23
View File
@@ -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;
/// <summary>
/// HWMA: Holt-Winters Moving Average
/// A triple exponential smoothing filter that tracks level (F), velocity (V), and
/// acceleration (A) components for adaptive trend following.
/// </summary>
/// <remarks>
/// <b>Key characteristics</b>
/// <list type="bullet">
/// <item><description>Triple exponential smoothing with level, velocity, and acceleration</description></item>
/// <item><description>Adapts quickly to trend changes via higher-order derivatives</description></item>
/// <item><description>When period specified: α = 2/(period+1), β = γ = 1/period</description></item>
/// <item><description>O(1) complexity per bar - no windowing required</description></item>
/// </list>
/// Triple exponential smoothing tracking level (F), velocity (V), and acceleration (A).
/// O(1) adaptive trend follower responding quickly via higher-order derivatives.
///
/// <b>Calculation</b>
/// <code>
/// 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
/// </code>
///
/// <b>Sources</b>
/// 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: <c>Output = F + V + 0.5×A</c> with recursive updates.
/// </remarks>
/// <seealso href="Hwma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Hwma : AbstractBase
{
+5 -20
View File
@@ -4,30 +4,15 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// LSMA: Least Squares Moving Average
/// LSMA: Least Squares Moving Average (Linear Regression)
/// </summary>
/// <remarks>
/// 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: <c>LSMA = b - m × offset</c> where <c>m = (n×Σxy - Σx×Σy) / denom</c>.
/// </remarks>
/// <seealso href="Lsma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Lsma : AbstractBase
{
+3 -16
View File
@@ -7,24 +7,11 @@ namespace QuanTAlib;
/// PWMA: Parabolic Weighted Moving Average
/// </summary>
/// <remarks>
/// 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: <c>PWMA = Σ(i²×P_i) / Σ(i²)</c> with efficient incremental updates.
/// </remarks>
/// <seealso href="Pwma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Pwma : AbstractBase
{
+4 -22
View File
@@ -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;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// <b>Key characteristics</b>
/// <list type="bullet">
/// <item><description>Uses polynomial fitting for smoothing</description></item>
/// <item><description>Preserves peak shapes better than standard MAs</description></item>
/// <item><description>Period must be odd (even periods are adjusted to next odd)</description></item>
/// <item><description>Polynomial degree (0-4) controls smoothing vs shape preservation</description></item>
/// <item><description>O(N) complexity per bar due to window convolution</description></item>
/// </list>
/// Polynomial-fitting FIR filter preserving peaks and inflection points.
/// Superior shape preservation vs standard MAs; odd period required.
///
/// <b>Weight calculation</b>
/// For polynomial degree d, weights are based on:
/// <code>
/// w_i = 1 - |norm_x|^d where norm_x = (i - half_window) / half_window
/// </code>
///
/// <b>Sources</b>
/// Savitzky, A., Golay, M.J.E. (1964) - "Smoothing and Differentiation of Data by Simplified Least Squares Procedures"
/// Analytical Chemistry 36(8): 1627-1639
/// Calculation: <c>W_i = 1 - |norm_x|^d</c> with degree 0-4 controlling smoothing.
/// </remarks>
/// <seealso href="Sgma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Sgma : AbstractBase
{
+4 -13
View File
@@ -8,21 +8,12 @@ namespace QuanTAlib;
/// SINEMA: Sine-Weighted Moving Average
/// </summary>
/// <remarks>
/// <para>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.</para>
/// <para>Calculation:
/// w[i] = sin(π * (i+1) / period)
/// SINEMA = Σ(P[i] * w[i]) / Σ(w[i])</para>
/// 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: <c>W_i = sin(π×(i+1)/n)</c>; <c>SINEMA = Σ(P_i×W_i) / Σ(W_i)</c>.
/// </remarks>
/// <seealso href="Sinema.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Sinema : AbstractBase
{
+4 -10
View File
@@ -12,18 +12,12 @@ namespace QuanTAlib;
/// SMA: Simple Moving Average
/// </summary>
/// <remarks>
/// <para>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.</para>
/// <para>Calculation:
/// SMA = (P_n + P_(n-1) + ... + P_1) / n</para>
/// 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: <c>SMA = Σ(values) / n</c>.
/// </remarks>
/// <seealso href="Sma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Sma : AbstractBase
{
+3 -12
View File
@@ -8,20 +8,11 @@ namespace QuanTAlib;
/// TRIMA: Triangular Moving Average
/// </summary>
/// <remarks>
/// 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: <c>TRIMA = SMA(SMA(p1), p2)</c> where <c>p1 = (n+1)/2</c>, <c>p2 = n/2+1</c>.
/// </remarks>
/// <seealso href="Trima.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Trima : AbstractBase
{
+4 -10
View File
@@ -10,18 +10,12 @@ namespace QuanTAlib;
/// WMA: Weighted Moving Average
/// </summary>
/// <remarks>
/// <para>WMA applies linear weighting to data points, giving more weight to recent values.
/// Uses dual running sums for O(1) complexity per update.</para>
/// <para>Calculation:
/// WMA = (n*P_n + (n-1)*P_(n-1) + ... + 1*P_1) / (n*(n+1)/2)</para>
/// 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: <c>WMA = Σ(w_i × P_i) / Σ(w_i)</c> where <c>w_i = i</c>.
/// </remarks>
/// <seealso href="Wma.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Wma : AbstractBase
{
+5 -11
View File
@@ -8,19 +8,13 @@ namespace QuanTAlib;
/// DEMA: Double Exponential Moving Average
/// </summary>
/// <remarks>
/// 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: <c>DEMA = 2×EMA(p) - EMA(EMA(p))</c>.
/// </remarks>
/// <seealso href="Dema.md">Detailed documentation</seealso>
/// <seealso href="dema.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Dema : AbstractBase
{
+7 -22
View File
@@ -4,31 +4,16 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// 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
/// </summary>
/// <remarks>
/// 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: <c>α = scaleFactor×5/period × |filt|/RMS; DSMA = α×P + (1-α)×DSMA_{t-1}</c>.
/// </remarks>
/// <seealso href="Dsma.md">Detailed documentation</seealso>
/// <seealso href="dsma.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Dsma : AbstractBase
{
+5 -15
View File
@@ -9,23 +9,13 @@ namespace QuanTAlib;
/// EMA: Exponential Moving Average
/// </summary>
/// <remarks>
/// 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: <c>EMA_t = α × Price_t + (1-α) × EMA_{t-1}</c>, where <c>α = 2/(period+1)</c>.
/// </remarks>
/// <seealso href="Ema.md">Detailed documentation</seealso>
/// <seealso href="ema.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Ema : AbstractBase
{
+7 -6
View File
@@ -6,15 +6,16 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// FRAMA: Ehlers Fractal Adaptive Moving Average
/// FRAMA: Fractal Adaptive Moving Average
/// </summary>
/// <remarks>
/// 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: <c>D = ln(N1+N2)-ln(N3) / ln(2); α = exp(-4.6×(D-1))</c>, clamped [0.01,1].
/// </remarks>
/// <seealso href="Frama.md">Detailed documentation</seealso>
/// <seealso href="frama.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Frama : ITValuePublisher, IDisposable
{
+7 -6
View File
@@ -4,15 +4,16 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// 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
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <seealso href="Htit.md">Detailed documentation</seealso>
/// <seealso href="htit.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Htit : AbstractBase
{
+9 -6
View File
@@ -4,13 +4,16 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// 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
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <seealso href="Jma.md">Detailed documentation</seealso>
/// <seealso href="jma.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Jma : AbstractBase
{
+5 -8
View File
@@ -7,16 +7,13 @@ namespace QuanTAlib;
/// KAMA: Kaufman's Adaptive Moving Average
/// </summary>
/// <remarks>
/// 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: <c>ER = |Change|/Volatility; SC = (ER×(αfast-αslow)+αslow)²; KAMA += SC×(P-KAMA)</c>.
/// </remarks>
/// <seealso href="Kama.md">Detailed documentation</seealso>
/// <seealso href="kama.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Kama : AbstractBase
{
+9 -2
View File
@@ -4,9 +4,16 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// MESA Adaptive Moving Average (MAMA)
/// A trend-following indicator that adapts to the market's phase rate of change.
/// MAMA: MESA Adaptive Moving Average
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <seealso href="Mama.md">Detailed documentation</seealso>
/// <seealso href="mama.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Mama : AbstractBase
{
+5 -8
View File
@@ -5,17 +5,14 @@ namespace QuanTAlib;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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: <c>MGDI = MGDI_{t-1} + (P - MGDI_{t-1}) / (k×N×(P/MGDI)^4)</c>.
/// </remarks>
/// <seealso href="Mgdi.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Mgdi : AbstractBase
{
+5 -16
View File
@@ -4,26 +4,15 @@ using System.Runtime.InteropServices;
namespace QuanTAlib;
/// <summary>
/// QEMA: Quad Exponential Moving Average with Progressive Alphas and Zero-Lag Weighting
/// QEMA: Quad Exponential Moving Average
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <seealso href="Qema.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Qema : AbstractBase
{
+4 -18
View File
@@ -9,26 +9,12 @@ namespace QuanTAlib;
/// REMA: Regularized Exponential Moving Average
/// </summary>
/// <remarks>
/// 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: <c>REMA = λ×(EMA_comp - REG_comp) + REG_comp</c>.
/// </remarks>
/// <seealso href="Rema.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Rema : AbstractBase
{
+6 -9
View File
@@ -3,19 +3,16 @@ using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// RMA: Running Moving Average (also known as Wilder's Moving Average or SMMA)
/// RMA: Running Moving Average (Wilder's Moving Average)
/// </summary>
/// <remarks>
/// 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: <c>RMA_t = α×Price + (1-α)×RMA_{t-1}</c>, where <c>α = 1/period</c>.
/// </remarks>
/// <seealso href="Rma.md">Detailed documentation</seealso>
/// <seealso href="rma.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Rma : AbstractBase
{
+5 -14
View File
@@ -7,22 +7,13 @@ namespace QuanTAlib;
/// T3: Tillson T3 Moving Average
/// </summary>
/// <remarks>
/// 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: <c>T3 = c1×e6 + c2×e5 + c3×e4 + c4×e3</c> (six EMAs with polynomial weights).
/// </remarks>
/// <seealso href="T3.md">Detailed documentation</seealso>
/// <seealso href="t3.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class T3 : AbstractBase
{
+5 -14
View File
@@ -7,22 +7,13 @@ namespace QuanTAlib;
/// TEMA: Triple Exponential Moving Average
/// </summary>
/// <remarks>
/// 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: <c>TEMA = 3×EMA1 - 3×EMA2 + EMA3</c> (cascaded EMAs).
/// </remarks>
/// <seealso href="Tema.md">Detailed documentation</seealso>
/// <seealso href="tema.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Tema : AbstractBase
{
+4 -12
View File
@@ -7,20 +7,12 @@ namespace QuanTAlib;
/// VAMA: Volatility Adjusted Moving Average
/// </summary>
/// <remarks>
/// 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: <c>length = baseLength × (LongATR/ShortATR)</c>, clamped to [min, max].
/// </remarks>
/// <seealso href="Vama.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Vama : AbstractBase
{
+5 -14
View File
@@ -7,22 +7,13 @@ namespace QuanTAlib;
/// VIDYA: Variable Index Dynamic Average
/// </summary>
/// <remarks>
/// 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: <c>VI = |CMO|; α' = α×VI; VIDYA = α'×P + (1-α')×VIDYA_{t-1}</c>.
/// </remarks>
/// <seealso href="Vidya.md">Detailed documentation</seealso>
/// <seealso href="vidya.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Vidya : AbstractBase
{
+4 -8
View File
@@ -7,16 +7,12 @@ namespace QuanTAlib;
/// YZVAMA: Yang-Zhang Volatility Adjusted Moving Average
/// </summary>
/// <remarks>
/// 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: <c>length = max - percentile×(max-min)</c>.
/// </remarks>
/// <seealso href="Yzvama.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Yzvama : AbstractBase
{
+6 -3
View File
@@ -9,10 +9,13 @@ namespace QuanTAlib;
/// ZLEMA: Zero-Lag Exponential Moving Average
/// </summary>
/// <remarks>
/// 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: <c>ZLEMA = EMA(2×Price - Price[lag])</c>, where <c>lag = (period-1)/2</c>.
/// </remarks>
/// <seealso href="Zlema.md">Detailed documentation</seealso>
/// <seealso href="zlema.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Zlema : AbstractBase
{
+4 -10
View File
@@ -6,18 +6,12 @@ namespace QuanTAlib;
/// ADR: Average Daily Range
/// </summary>
/// <remarks>
/// 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: <c>ADR = MA(High - Low, period)</c>.
/// </remarks>
/// <seealso href="Adr.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Adr : AbstractBase
{
+4 -9
View File
@@ -6,17 +6,12 @@ namespace QuanTAlib;
/// ATR: Average True Range
/// </summary>
/// <remarks>
/// 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: <c>ATR = RMA(TR, period)</c>.
/// </remarks>
/// <seealso href="Atr.md">Detailed documentation</seealso>
[SkipLocalsInit]
public sealed class Atr : AbstractBase
{

Some files were not shown because too many files have changed in this diff Show More