pine files

This commit is contained in:
Miha Kralj
2026-01-31 14:05:53 -08:00
parent 51e885a4a6
commit 5ed4b6c0fc
102 changed files with 2883 additions and 593 deletions
+47
View File
@@ -0,0 +1,47 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Huber Loss (HUBER)", "HUBER")
//@function Calculates Huber Loss between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/huber.md
//@param source1 First series to compare
//@param source2 Second series to compare
//@param period Lookback period for error averaging
//@param delta Threshold that determines switch between MSE and MAE behavior
//@returns Huber loss value averaged over the specified period using SMA
huber(series float source1, series float source2, simple int period, simple float delta = 1.345) =>
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
error = source1 - source2
huber_error = math.abs(error) <= delta ? 0.5 * math.pow(error, 2) : delta * math.abs(error) - 0.5 * math.pow(delta, 2)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(huber_error)
sum := sum + huber_error
valid_count := valid_count + 1
array.set(buffer, head, huber_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : huber_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_delta = input.float(1.345, "Delta", minval=0.1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = huber(i_source1, i_source2, i_period, i_delta)
// Plot
plot(error, "Huber", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+38
View File
@@ -0,0 +1,38 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Arctangent Absolute Percentage Error", "MAAPE", overlay=false, format=format.percent)
//@function Calculates Mean Arctangent Absolute Percentage Error
//@doc Uses arctangent to bound error between 0 and π/2, robust to outliers.
//@doc Handles zero actual values gracefully (approaches π/2).
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for averaging
//@returns MAAPE value (0 to ~1.5708)
maape(series float actual, series float predicted, simple int length) =>
float epsilon = 1e-10
// Compute arctangent percentage error for current bar
float absActual = math.abs(nz(actual, 0.0))
float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0))
float atanError = absActual > epsilon ? math.atan(absError / absActual) : math.pi / 2.0
// Rolling mean of arctangent errors
float result = ta.sma(atanError, length)
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
maape_value = maape(i_actual, i_predicted, i_length)
// Plot
plot(maape_value, "MAAPE", color=color.yellow, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
hline(math.pi / 2.0, "Max (π/2)", color=color.red, linestyle=hline.style_dotted)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Absolute Error (MAE)", "MAE")
//@function Calculates Mean Absolute Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mae.md
//@param source1 First series to compare
//@param source2 Second series to compare
//@param period Lookback period for error averaging
//@returns MAE value averaged over the specified period using SMA
mae(series float source1, series float source2, simple int period) =>
absolute_error = math.abs(source1 - source2)
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(absolute_error)
sum := sum + absolute_error
valid_count := valid_count + 1
array.set(buffer, head, absolute_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : absolute_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = mae(i_source1, i_source2, i_period)
// Plot
plot(error, "MAE", color.new(color.blue, 60, color=color.yellow, linewidth=2), linewidth = 2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Absolute %Deviation (MAPD)", "MAPD")
//@function Calculates Mean Absolute Percentage Deviation between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mapd.md
//@param source1 First series to compare
//@param source2 Second series to compare
//@param period Lookback period for error averaging
//@returns MAPD value averaged over the specified period using SMA
mapd(series float source1, series float source2, simple int period) =>
percentage_error = 100 * math.abs((source1 - source2) / source2)
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(percentage_error)
sum := sum + percentage_error
valid_count := valid_count + 1
array.set(buffer, head, percentage_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : percentage_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = mapd(i_source1, i_source2, i_period)
// Plot
plot(error, "MAPD", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Absolute %Error (MAPE)", "MAPE")
//@function Calculates Mean Absolute Percentage Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mape.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for error averaging
//@returns MAPE value averaged over the specified period using SMA
mape(series float source1, series float source2, simple int period) =>
percentage_error = 100 * math.abs((source1 - source2) / source1)
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(percentage_error)
sum := sum + percentage_error
valid_count := valid_count + 1
array.set(buffer, head, percentage_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : percentage_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = mape(i_source1, i_source2, i_period)
// Plot
plot(error, "MAPE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+56
View File
@@ -0,0 +1,56 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Absolute Scaled Error (MASE)", "MASE")
//@function Calculates Mean Absolute Scaled Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mase.md
//@param source1 First series to compare
//@param source2 Second series to compare
//@param period Lookback period for error averaging
//@returns MASE value averaged over the specified period using SMA
mase(series float source1, series float source2, simple int period) =>
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
error = source1 - source2
abs_error = math.abs(error)
var float scale = na
if na(scale)
sum = 0.0
count = 0
for i = 1 to p
if not na(source1[i]) and not na(source1[i-1])
sum += math.abs(source1[i] - source1[i-1])
count += 1
scale := count > 0 ? sum / count : 1.0
scaled_error = abs_error / (scale == 0 ? 1.0 : scale)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(scaled_error)
sum := sum + scaled_error
valid_count := valid_count + 1
array.set(buffer, head, scaled_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : scaled_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = mase(i_source1, i_source2, i_period)
// Plot
plot(error, "MASE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+33
View File
@@ -0,0 +1,33 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Median Absolute Error", "MdAE", overlay=false)
//@function Calculates Median Absolute Error
//@doc Median of absolute errors, robust to outliers (50% breakdown point).
//@doc Same units as original data, less sensitive to extreme errors than MAE.
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for median calculation
//@returns MdAE value
mdae(series float actual, series float predicted, simple int length) =>
// Compute absolute error for current bar
float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0))
// Use ta.median for rolling median of absolute errors
float result = ta.median(absError, length)
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
mdae_value = mdae(i_actual, i_predicted, i_length)
// Plot
plot(mdae_value, "MdAE", color=color.yellow, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
+37
View File
@@ -0,0 +1,37 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Median Absolute Percentage Error", "MdAPE", overlay=false, format=format.percent)
//@function Calculates Median Absolute Percentage Error
//@doc Median of absolute percentage errors, robust to outliers.
//@doc Scale-independent (expressed as percentage), handles zero actual with epsilon.
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for median calculation
//@returns MdAPE value as percentage
mdape(series float actual, series float predicted, simple int length) =>
float epsilon = 1e-10
// Compute absolute percentage error for current bar
float absActual = math.abs(nz(actual, 1.0))
float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0))
float pctError = absActual > epsilon ? (absError / absActual) * 100.0 : 0.0
// Use ta.median for rolling median of percentage errors
float result = ta.median(pctError, length)
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
mdape_value = mdape(i_actual, i_predicted, i_length)
// Plot
plot(mdape_value, "MdAPE", color=color.yellow, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Error (ME)", "ME")
//@function Calculates Mean Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/me.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for error averaging
//@returns ME value averaged over the specified period using SMA
me(series float source1, series float source2, simple int period) =>
error = source1 - source2
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(error)
sum := sum + error
valid_count := valid_count + 1
array.set(buffer, head, error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = me(i_source1, i_source2, i_period)
// Plot
plot(error, "ME", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean %Error (MPE)", "MPE")
//@function Calculates Mean Percentage Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mpe.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for error averaging
//@returns MPE value averaged over the specified period using SMA
mpe(series float source1, series float source2, simple int period) =>
percentage_error = 100 * ((source1 - source2) / source1)
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(percentage_error)
sum := sum + percentage_error
valid_count := valid_count + 1
array.set(buffer, head, percentage_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : percentage_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = mpe(i_source1, i_source2, i_period)
// Plot
plot(error, "MPE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+38
View File
@@ -0,0 +1,38 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Relative Absolute Error", "MRAE", overlay=false)
//@function Calculates Mean Relative Absolute Error
//@doc Average relative absolute error, normalized by actual value.
//@doc Similar to MAPE but expressed as ratio (0-1) instead of percentage (0-100%).
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for averaging
//@returns MRAE value (0 = perfect, 1 = 100% error)
mrae(series float actual, series float predicted, simple int length) =>
float epsilon = 1e-10
// Compute relative absolute error for current bar
float absActual = math.abs(nz(actual, 1.0))
float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0))
float relError = absActual > epsilon ? absError / absActual : 0.0
// Rolling mean of relative errors
float result = ta.sma(relError, length)
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
mrae_value = mrae(i_actual, i_predicted, i_length)
// Plot
plot(mrae_value, "MRAE", color=color.yellow, linewidth=2)
hline(0, "Perfect", color=color.green, linestyle=hline.style_dotted)
hline(1, "100% Error", color=color.red, linestyle=hline.style_dotted)
+46
View File
@@ -0,0 +1,46 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Squared Error (MSE)", "MSE")
//@function Calculates Mean Squared Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/mse.md
//@param source1 First series to compare
//@param source2 Second series to compare
//@param period Lookback period for error averaging
//@returns MSE value averaged over the specified period using SMA
mse(series float source1, series float source2, simple int period) =>
squared_error = math.pow(source1 - source2, 2)
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(squared_error)
sum := sum + squared_error
valid_count := valid_count + 1
array.set(buffer, head, squared_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : squared_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = mse(i_source1, i_source2, i_period)
// Plot
plot(error, "MSE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Mean Squared Logarithmic Error (MSLE)", "MSLE")
//@function Calculates Mean Squared Logarithmic Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/msle.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for error averaging
//@returns MSLE value averaged over the specified period using SMA
msle(series float source1, series float source2, simple int period) =>
log_error = math.pow(math.log(1 + source1) - math.log(1 + source2), 2)
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(log_error)
sum := sum + log_error
valid_count := valid_count + 1
array.set(buffer, head, log_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : log_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = msle(i_source1, i_source2, i_period)
// Plot
plot(error, "MSLE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+41
View File
@@ -0,0 +1,41 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Pseudo-Huber Loss", "PseudoHuber", overlay=false)
//@function Calculates Pseudo-Huber Loss (Charbonnier Loss)
//@doc Smooth approximation to Huber loss, differentiable everywhere.
//@doc Approximates L2 for small errors, L1 for large errors.
//@doc δ (delta) controls the transition point between quadratic and linear behavior.
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for averaging
//@param delta Scale parameter controlling transition smoothness (default 1.0)
//@returns Mean Pseudo-Huber loss over the window
pseudohuber(series float actual, series float predicted, simple int length, simple float delta = 1.0) =>
float deltaSquared = delta * delta
// Compute Pseudo-Huber loss for current bar: δ² * (√(1 + (error/δ)²) - 1)
float diff = nz(actual, 0.0) - nz(predicted, 0.0)
float ratio = diff / delta
float sqrtTerm = math.sqrt(1.0 + ratio * ratio)
float loss = deltaSquared * (sqrtTerm - 1.0)
// Rolling mean of losses
float result = ta.sma(loss, length)
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_delta = input.float(1.0, "Delta (transition scale)", minval=0.001, step=0.1, tooltip="Controls transition between quadratic and linear behavior")
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
pseudohuber_value = pseudohuber(i_actual, i_predicted, i_length, i_delta)
// Plot
plot(pseudohuber_value, "Pseudo-Huber", color=color.yellow, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
+36
View File
@@ -0,0 +1,36 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Quantile Loss (Pinball Loss)", "QuantileLoss", overlay=false)
//@function Calculates Quantile Loss (Pinball Loss)
//@doc Used for quantile regression, asymmetrically penalizes over/under-predictions.
//@doc q=0.5 gives MAE; q>0.5 penalizes under-prediction more; q<0.5 penalizes over-prediction more.
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for averaging
//@param quantile Quantile value between 0 and 1 (default 0.5)
//@returns Mean quantile loss over the window
quantile_loss(series float actual, series float predicted, simple int length, simple float quantile = 0.5) =>
// Compute quantile loss for current bar
float diff = nz(actual, 0.0) - nz(predicted, 0.0)
float loss = diff >= 0 ? quantile * diff : (quantile - 1.0) * diff
// Rolling mean of losses
float result = ta.sma(loss, length)
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_quantile = input.float(0.5, "Quantile", minval=0.01, maxval=0.99, step=0.05, tooltip="0.5=median (MAE), >0.5=penalize under-prediction, <0.5=penalize over-prediction")
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
quantile_value = quantile_loss(i_actual, i_predicted, i_length, i_quantile)
// Plot
plot(quantile_value, "Quantile Loss", color=color.yellow, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
+75
View File
@@ -0,0 +1,75 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Relative Absolute Error (RAE)", "RAE")
//@function Calculates Relative Absolute Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rae.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for error averaging
//@returns RAE value averaged over the specified period using SMA
rae(series float source1, series float source2, simple int period) =>
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float sum_source1 = 0.0
var float[] buffer_source1 = array.new_float(p, na)
var int head_source1 = 0
var int valid_count_source1 = 0
float oldest_source1 = array.get(buffer_source1, head_source1)
if not na(oldest_source1)
sum_source1 := sum_source1 - oldest_source1
valid_count_source1 := valid_count_source1 - 1
if not na(source1)
sum_source1 := sum_source1 + source1
valid_count_source1 := valid_count_source1 + 1
array.set(buffer_source1, head_source1, source1)
head_source1 := (head_source1 + 1) % p
float mean_source1 = valid_count_source1 > 0 ? sum_source1 / valid_count_source1 : source1
float abs_error = math.abs(source1 - source2)
float abs_baseline_error = math.abs(source1 - mean_source1)
var float sum_abs_error = 0.0
var float[] buffer_abs_error = array.new_float(p, na)
var int head_abs_error = 0
var int valid_count_abs_error = 0
float oldest_abs_error = array.get(buffer_abs_error, head_abs_error)
if not na(oldest_abs_error)
sum_abs_error := sum_abs_error - oldest_abs_error
valid_count_abs_error := valid_count_abs_error - 1
if not na(abs_error)
sum_abs_error := sum_abs_error + abs_error
valid_count_abs_error := valid_count_abs_error + 1
array.set(buffer_abs_error, head_abs_error, abs_error)
head_abs_error := (head_abs_error + 1) % p
var float sum_baseline_error = 0.0
var float[] buffer_baseline_error = array.new_float(p, na)
var int head_baseline_error = 0
var int valid_count_baseline_error = 0
float oldest_baseline_error = array.get(buffer_baseline_error, head_baseline_error)
if not na(oldest_baseline_error)
sum_baseline_error := sum_baseline_error - oldest_baseline_error
valid_count_baseline_error := valid_count_baseline_error - 1
if not na(abs_baseline_error)
sum_baseline_error := sum_baseline_error + abs_baseline_error
valid_count_baseline_error := valid_count_baseline_error + 1
array.set(buffer_baseline_error, head_baseline_error, abs_baseline_error)
head_baseline_error := (head_baseline_error + 1) % p
float total_abs_error = valid_count_abs_error > 0 ? sum_abs_error : abs_error
float total_baseline_error = valid_count_baseline_error > 0 ? sum_baseline_error : abs_baseline_error
total_baseline_error != 0 ? total_abs_error / total_baseline_error : 1.0
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = rae(i_source1, i_source2, i_period)
// Plot
plot(error, "RAE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Root Mean Squared Error (RMSE)", "RMSE")
//@function Calculates Root Mean Squared Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rmse.md
//@param source1 First series to compare
//@param source2 Second series to compare
//@param period Lookback period for error averaging
//@returns RMSE value averaged over the specified period using SMA
rmse(series float source1, series float source2, simple int period) =>
squared_error = math.pow(source1 - source2, 2)
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(squared_error)
sum := sum + squared_error
valid_count := valid_count + 1
array.set(buffer, head, squared_error)
head := (head + 1) % p
float mse = valid_count > 0 ? sum / valid_count : squared_error
math.sqrt(mse)
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = rmse(i_source1, i_source2, i_period)
// Plot
plot(error, "RMSE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Root Mean Squared Logarithmic Error (RMSLE)", "RMSLE")
//@function Calculates Root Mean Squared Logarithmic Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rmsle.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for error averaging
//@returns RMSLE value averaged over the specified period using SMA
rmsle(series float source1, series float source2, simple int period) =>
log_squared_error = math.pow(math.log(1 + source1) - math.log(1 + source2), 2)
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(log_squared_error)
sum := sum + log_squared_error
valid_count := valid_count + 1
array.set(buffer, head, log_squared_error)
head := (head + 1) % p
float msle = valid_count > 0 ? sum / valid_count : log_squared_error
math.sqrt(msle)
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = rmsle(i_source1, i_source2, i_period)
// Plot
plot(error, "RMSLE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+74
View File
@@ -0,0 +1,74 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Relative Squared Error (RSE)", "RSE")
//@function Calculates Relative Squared Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rse.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for error averaging
//@returns RSE value averaged over the specified period using SMA
rse(series float source1, series float source2, simple int period) =>
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float sum_source1 = 0.0
var float[] buffer_source1 = array.new_float(p, na)
var int head_source1 = 0
var int valid_count_source1 = 0
float oldest_source1 = array.get(buffer_source1, head_source1)
if not na(oldest_source1)
sum_source1 := sum_source1 - oldest_source1
valid_count_source1 := valid_count_source1 - 1
if not na(source1)
sum_source1 := sum_source1 + source1
valid_count_source1 := valid_count_source1 + 1
array.set(buffer_source1, head_source1, source1)
head_source1 := (head_source1 + 1) % p
float mean_source1 = valid_count_source1 > 0 ? sum_source1 / valid_count_source1 : source1
float squared_error = math.pow(source1 - source2, 2)
float squared_baseline_error = math.pow(source1 - mean_source1, 2)
var float sum_squared_error = 0.0
var float[] buffer_squared_error = array.new_float(p, na)
var int head_squared_error = 0
var int valid_count_squared_error = 0
float oldest_squared_error = array.get(buffer_squared_error, head_squared_error)
if not na(oldest_squared_error)
sum_squared_error := sum_squared_error - oldest_squared_error
valid_count_squared_error := valid_count_squared_error - 1
if not na(squared_error)
sum_squared_error := sum_squared_error + squared_error
valid_count_squared_error := valid_count_squared_error + 1
array.set(buffer_squared_error, head_squared_error, squared_error)
head_squared_error := (head_squared_error + 1) % p
var float sum_baseline_error = 0.0
var float[] buffer_baseline_error = array.new_float(p, na)
var int head_baseline_error = 0
var int valid_count_baseline_error = 0
float oldest_baseline_error = array.get(buffer_baseline_error, head_baseline_error)
if not na(oldest_baseline_error)
sum_baseline_error := sum_baseline_error - oldest_baseline_error
valid_count_baseline_error := valid_count_baseline_error - 1
if not na(squared_baseline_error)
sum_baseline_error := sum_baseline_error + squared_baseline_error
valid_count_baseline_error := valid_count_baseline_error + 1
array.set(buffer_baseline_error, head_baseline_error, squared_baseline_error)
head_baseline_error := (head_baseline_error + 1) % p
float total_squared_error = valid_count_squared_error > 0 ? sum_squared_error : squared_error
float total_baseline_error = valid_count_baseline_error > 0 ? sum_baseline_error : squared_baseline_error
total_baseline_error != 0 ? total_squared_error / total_baseline_error : 1.0
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = rse(i_source1, i_source2, i_period)
// Plot
plot(error, "RSE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+75
View File
@@ -0,0 +1,75 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("R² Coefficient of Determination (RSQUARED)", "RSQUARED")
//@function Calculates the R-squared (Coefficient of Determination) between two sources
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/rsquared.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for averaging
//@returns R-squared value averaging over the specified period
rsquared(series float source1, series float source2, simple int period) =>
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float sum_source1 = 0.0
var float[] buffer_source1 = array.new_float(p, na)
var int head_source1 = 0
var int valid_count_source1 = 0
float oldest_source1 = array.get(buffer_source1, head_source1)
if not na(oldest_source1)
sum_source1 := sum_source1 - oldest_source1
valid_count_source1 := valid_count_source1 - 1
if not na(source1)
sum_source1 := sum_source1 + source1
valid_count_source1 := valid_count_source1 + 1
array.set(buffer_source1, head_source1, source1)
head_source1 := (head_source1 + 1) % p
float mean_source1 = valid_count_source1 > 0 ? sum_source1 / valid_count_source1 : source1
float squared_residual = math.pow(source1 - source2, 2)
float total_ss = math.pow(source1 - mean_source1, 2)
var float sum_squared_residual = 0.0
var float[] buffer_squared_residual = array.new_float(p, na)
var int head_squared_residual = 0
var int valid_count_squared_residual = 0
float oldest_squared_residual = array.get(buffer_squared_residual, head_squared_residual)
if not na(oldest_squared_residual)
sum_squared_residual := sum_squared_residual - oldest_squared_residual
valid_count_squared_residual := valid_count_squared_residual - 1
if not na(squared_residual)
sum_squared_residual := sum_squared_residual + squared_residual
valid_count_squared_residual := valid_count_squared_residual + 1
array.set(buffer_squared_residual, head_squared_residual, squared_residual)
head_squared_residual := (head_squared_residual + 1) % p
var float sum_total_ss = 0.0
var float[] buffer_total_ss = array.new_float(p, na)
var int head_total_ss = 0
var int valid_count_total_ss = 0
float oldest_total_ss = array.get(buffer_total_ss, head_total_ss)
if not na(oldest_total_ss)
sum_total_ss := sum_total_ss - oldest_total_ss
valid_count_total_ss := valid_count_total_ss - 1
if not na(total_ss)
sum_total_ss := sum_total_ss + total_ss
valid_count_total_ss := valid_count_total_ss + 1
array.set(buffer_total_ss, head_total_ss, total_ss)
head_total_ss := (head_total_ss + 1) % p
float rss = valid_count_squared_residual > 0 ? sum_squared_residual : squared_residual
float tss = valid_count_total_ss > 0 ? sum_total_ss : total_ss
tss != 0 ? 1 - (rss / tss) : 1.0
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
score = rsquared(i_source1, i_source2, i_period)
// Plot
plot(score, "R²", color.new(color.red, 60, color=color.yellow, linewidth=2), linewidth = 2, style = plot.style_area)
plot(i_source2, "EMA", color.new(color.yellow, 0, linewidth=2), linewidth = 1, style = plot.style_line, force_overlay = true)
+48
View File
@@ -0,0 +1,48 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Symmetric Mean Absolute %Error (SMAPE)", "SMAPE")
//@function Calculates Symmetric Mean Absolute Percentage Error between two sources using SMA for averaging
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/errors/smape.md
//@param source1 First series to compare (actual)
//@param source2 Second series to compare (predicted)
//@param period Lookback period for error averaging
//@returns SMAPE value averaged over the specified period using SMA (in percentage)
smape(series float source1, series float source2, simple int period) =>
// Calculate symmetric absolute percentage error (scaled to 100%)
abs_diff = math.abs(source1 - source2)
sum_abs = math.abs(source1) + math.abs(source2)
symmetric_error = sum_abs != 0 ? 200 * abs_diff / sum_abs : 0
if period <= 0
runtime.error("Period must be greater than 0")
int p = math.min(math.max(1, period), 4000)
var float[] buffer = array.new_float(p, na)
var int head = 0
var float sum = 0.0
var int valid_count = 0
float oldest = array.get(buffer, head)
if not na(oldest)
sum := sum - oldest
valid_count := valid_count - 1
if not na(symmetric_error)
sum := sum + symmetric_error
valid_count := valid_count + 1
array.set(buffer, head, symmetric_error)
head := (head + 1) % p
valid_count > 0 ? sum / valid_count : symmetric_error
// ---------- Main loop ----------
// Inputs
i_source1 = input.source(close, "Source")
i_period = input.int(100, "Period", minval=1)
i_source2 = ta.ema(i_source1, i_period)
// Calculation
error = smape(i_source1, i_source2, i_period)
// Plot
plot(error, "SMAPE", color=color.new(color.red, 60), linewidth=2, style = plot.style_area)
plot(i_source2, "EMA", color=color.yellow, linewidth=1, style = plot.style_line, force_overlay = true)
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Theil's U Statistic", "TheilU", overlay=false)
//@function Calculates Theil's U Statistic (U1)
//@doc Relative forecast accuracy measure, normalized RMSE.
//@doc U=0: perfect; U=1: naive forecast; U>1: worse than naive.
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for calculation
//@returns Theil's U value (0 to 1+ range)
theil_u(series float actual, series float predicted, simple int length) =>
float epsilon = 1e-10
// Compute squared values for current bar
float error = nz(predicted, 0.0) - nz(actual, 0.0)
float sqError = error * error
float sqActual = nz(actual, 0.0) * nz(actual, 0.0)
float sqPred = nz(predicted, 0.0) * nz(predicted, 0.0)
// Rolling sums
float sumSqError = ta.sum(sqError, length)
float sumSqActual = ta.sum(sqActual, length)
float sumSqPred = ta.sum(sqPred, length)
// TheilU = √(Σ(pred-act)²) / √(Σact² + Σpred²)
float denom = math.sqrt(sumSqActual + sumSqPred)
float result = denom > epsilon ? math.sqrt(sumSqError) / denom : 0.0
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
theilu_value = theil_u(i_actual, i_predicted, i_length)
// Plot
plot(theilu_value, "Theil's U", color=color.yellow, linewidth=2)
hline(0, "Perfect", color=color.green, linestyle=hline.style_dotted)
hline(1, "Naive", color=color.red, linestyle=hline.style_dotted)
+49
View File
@@ -0,0 +1,49 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Tukey's Biweight Loss", "TukeyBiweight", overlay=false)
//@function Calculates Tukey's Biweight (Bisquare) Loss
//@doc Robust loss that completely rejects outliers beyond threshold c.
//@doc ρ(x) = (c²/6) * (1 - (1 - (x/c)²)³) for |x| ≤ c; ρ(x) = c²/6 for |x| > c
//@doc Common c values: 4.685 (95% efficiency), 6.0 (more permissive)
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for averaging
//@param c Threshold for outlier rejection (default 4.685)
//@returns Mean Tukey biweight loss over the window
tukey_biweight(series float actual, series float predicted, simple int length, simple float c = 4.685) =>
float cSquaredOver6 = (c * c) / 6.0
// Compute Tukey biweight loss for current bar
float error = nz(actual, 0.0) - nz(predicted, 0.0)
float absError = math.abs(error)
float loss = 0.0
if absError > c
loss := cSquaredOver6
else
float ratio = error / c
float ratioSq = ratio * ratio
float oneMinusRatioSq = 1.0 - ratioSq
float cubed = oneMinusRatioSq * oneMinusRatioSq * oneMinusRatioSq
loss := cSquaredOver6 * (1.0 - cubed)
// Rolling mean of losses
float result = ta.sma(loss, length)
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_c = input.float(4.685, "Threshold c", minval=0.1, step=0.1, tooltip="4.685=95% efficiency for normal; 6.0=more permissive")
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
tukey_value = tukey_biweight(i_actual, i_predicted, i_length, i_c)
// Plot
plot(tukey_value, "Tukey Biweight", color=color.yellow, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
+41
View File
@@ -0,0 +1,41 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Weighted Mean Absolute Percentage Error", "WMAPE", overlay=false, format=format.percent)
//@function Calculates Weighted Mean Absolute Percentage Error
//@doc Weights errors by actual value magnitude, industry standard for demand forecasting.
//@doc WMAPE = (Σ|actual - predicted| / Σ|actual|) * 100
//@doc More stable than MAPE for intermittent data with zero/low values.
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for calculation
//@returns WMAPE value as percentage
wmape(series float actual, series float predicted, simple int length) =>
float epsilon = 1e-10
// Compute absolute error and absolute actual for current bar
float absError = math.abs(nz(actual, 0.0) - nz(predicted, 0.0))
float absActual = math.abs(nz(actual, 0.0))
// Rolling sums
float sumAbsError = ta.sum(absError, length)
float sumAbsActual = ta.sum(absActual, length)
// WMAPE = (Σ|error| / Σ|actual|) * 100
float result = sumAbsActual > epsilon ? (sumAbsError / sumAbsActual) * 100.0 : 0.0
result
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
wmape_value = wmape(i_actual, i_predicted, i_length)
// Plot
plot(wmape_value, "WMAPE", color=color.yellow, linewidth=2)
hline(0, "Perfect", color=color.green, linestyle=hline.style_dotted)
+53
View File
@@ -0,0 +1,53 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Weighted Root Mean Squared Error", "WRMSE", overlay=false)
//@function Calculates Weighted Root Mean Squared Error
//@doc WRMSE extends RMSE by weighting each error differently.
//@doc WRMSE = √(Σ(w * (actual - predicted)²) / Σ(w))
//@doc Reduces to RMSE when all weights are equal.
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param weight Series of weights for each observation
//@param length Rolling window for calculation
//@returns WRMSE value in same units as original data
wrmse(series float actual, series float predicted, series float weight, simple int length) =>
float epsilon = 1e-10
// Compute weighted squared error for current bar
float diff = nz(actual, 0.0) - nz(predicted, 0.0)
float w = math.max(nz(weight, 1.0), 0.0) // Ensure non-negative weight
float weightedSqError = w * diff * diff
// Rolling sums
float sumWeightedError = ta.sum(weightedSqError, length)
float sumWeight = ta.sum(w, length)
// WRMSE = √(Σ(w*e²) / Σ(w))
float result = sumWeight > epsilon ? math.sqrt(sumWeightedError / sumWeight) : 0.0
result
//@function Calculates WRMSE with uniform weights (equivalent to RMSE)
//@param actual Series of actual values
//@param predicted Series of predicted/forecast values
//@param length Rolling window for calculation
//@returns WRMSE value (same as RMSE when weights are uniform)
wrmse_uniform(series float actual, series float predicted, simple int length) =>
wrmse(actual, predicted, 1.0, length)
// ---------- Main loop ----------
// Inputs
i_length = input.int(14, "Length", minval=1)
i_use_volume_weights = input.bool(false, "Use Volume as Weights", tooltip="When enabled, errors are weighted by volume")
i_actual = input.source(close, "Actual")
i_predicted = input.source(open, "Predicted")
// Calculation
weight = i_use_volume_weights ? volume : 1.0
wrmse_value = wrmse(i_actual, i_predicted, weight, i_length)
// Plot
plot(wrmse_value, "WRMSE", color=color.yellow, linewidth=2)
hline(0, "Perfect", color=color.green, linestyle=hline.style_dotted)