mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
pine files
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 < 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 < SlowEMA and both falling.
|
||||
/// </remarks>
|
||||
/// <seealso href="Amat.md">Detailed documentation</seealso>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Amat : ITValuePublisher, IDisposable
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -5,10 +5,15 @@ using System.Runtime.InteropServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// DMX – Jurik Directional Movement Index
|
||||
/// A smoother, lower-lag alternative to Welles Wilder’s DMI/ADX.
|
||||
/// Uses Jurik Moving Average (JMA) for smoothing directional movement components.
|
||||
/// DMX: Jurik Directional Movement Index
|
||||
/// </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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user