Files
QuanTAlib/lib/trends_FIR/crma/crma.pine
T

129 lines
4.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Cubic Regression Moving Average (CRMA)", "CRMA", overlay=true)
//@function Computes Cubic Regression Moving Average — fits a degree-3 polynomial
// y = a0 + a1*x + a2*x² + a3*x³ to the most recent `period` bars via
// normal equations with Gaussian elimination, returns the fitted endpoint.
//@param source Series to analyze
//@param period Lookback window for the cubic regression
//@returns Fitted value at the most recent bar (x = 0)
//@reference Polynomial least-squares regression (degree 3), evaluated at endpoint
//@optimized O(period) per bar for accumulating sums; O(1) for 4×4 solve
crma(series float source, simple int period) =>
if period < 4
runtime.error("Period must be at least 4 for cubic regression")
float price = nz(source)
// --- Circular buffer for rolling window ---
var array<float> buffer = array.new_float(period, na)
var int head = 0
array.set(buffer, head, price)
head := (head + 1) % period
int p = math.min(bar_index + 1, period)
if p < 4
price
else
// --- Accumulate power sums and cross-products ---
// Normal equations for degree-3 polynomial: M * a = rhs
// M[i][j] = Σ x^(i+j), rhs[i] = Σ x^i * y for i,j = 0..3
// x = 0 (newest) to p-1 (oldest), so a0 = fitted value at newest bar
float s0 = 0.0, s1 = 0.0, s2 = 0.0, s3 = 0.0
float s4 = 0.0, s5 = 0.0, s6 = 0.0
float r0 = 0.0, r1 = 0.0, r2 = 0.0, r3 = 0.0
int idx = (head - 1 + period) % period
for i = 0 to p - 1
float val = array.get(buffer, idx)
float v = na(val) ? price : val
float x = float(i)
float x2 = x * x
float x3 = x2 * x
s0 += 1.0 // Σ x^0 = count
s1 += x // Σ x^1
s2 += x2 // Σ x^2
s3 += x3 // Σ x^3
s4 += x2 * x2 // Σ x^4
s5 += x2 * x3 // Σ x^5
s6 += x3 * x3 // Σ x^6
r0 += v // Σ y
r1 += x * v // Σ x*y
r2 += x2 * v // Σ x²*y
r3 += x3 * v // Σ x³*y
idx := (idx - 1 + period) % period
// --- Build 4×4 augmented matrix (row-major, 4 rows × 5 cols) ---
var matrix<float> m = matrix.new<float>(4, 5, 0.0)
// Row 0: [s0, s1, s2, s3 | r0]
matrix.set(m, 0, 0, s0), matrix.set(m, 0, 1, s1), matrix.set(m, 0, 2, s2), matrix.set(m, 0, 3, s3), matrix.set(m, 0, 4, r0)
// Row 1: [s1, s2, s3, s4 | r1]
matrix.set(m, 1, 0, s1), matrix.set(m, 1, 1, s2), matrix.set(m, 1, 2, s3), matrix.set(m, 1, 3, s4), matrix.set(m, 1, 4, r1)
// Row 2: [s2, s3, s4, s5 | r2]
matrix.set(m, 2, 0, s2), matrix.set(m, 2, 1, s3), matrix.set(m, 2, 2, s4), matrix.set(m, 2, 3, s5), matrix.set(m, 2, 4, r2)
// Row 3: [s3, s4, s5, s6 | r3]
matrix.set(m, 3, 0, s3), matrix.set(m, 3, 1, s4), matrix.set(m, 3, 2, s5), matrix.set(m, 3, 3, s6), matrix.set(m, 3, 4, r3)
// --- Gaussian elimination with partial pivoting ---
bool singular = false
for col = 0 to 3
// Find pivot row
int pivot_row = col
float pivot_max = math.abs(matrix.get(m, col, col))
for row = col + 1 to 3
float absval = math.abs(matrix.get(m, row, col))
if absval > pivot_max
pivot_max := absval
pivot_row := row
if pivot_max < 1e-12
singular := true
break
// Swap rows if needed
if pivot_row != col
for k = col to 4
float tmp = matrix.get(m, col, k)
matrix.set(m, col, k, matrix.get(m, pivot_row, k))
matrix.set(m, pivot_row, k, tmp)
// Eliminate below
float diag = matrix.get(m, col, col)
for row = col + 1 to 3
float factor = matrix.get(m, row, col) / diag
for k = col to 4
matrix.set(m, row, k, matrix.get(m, row, k) - factor * matrix.get(m, col, k))
float result = price
if not singular
// Back-substitution
var array<float> a = array.new_float(4, 0.0)
for row = 3 to 0
float val = matrix.get(m, row, 4)
for k = row + 1 to 3
val -= matrix.get(m, row, k) * array.get(a, k)
array.set(a, row, val / matrix.get(m, row, row))
// a[0] is the fitted value at x = 0 (most recent bar)
result := array.get(a, 0)
result
// ---------- Main loop ----------
// Inputs
i_period = input.int(14, "Period", minval=4)
i_source = input.source(close, "Source")
// Calculation
crma_value = crma(i_source, i_period)
// Plot
plot(crma_value, "CRMA", color=color.yellow, linewidth=2)