Files
QuanTAlib/lib/cycles/ccyc/ccyc.pine
T

62 lines
2.6 KiB
Plaintext

// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
indicator("Ehlers Cyber Cycle (CCYC)", "CCYC", overlay=false)
//@function Computes Ehlers Cyber Cycle — a 2-pole high-pass IIR filter applied to a 4-element
// FIR-smoothed price, isolating the dominant cycle component with minimal lag.
// Includes a one-bar-delayed trigger line for crossover signals.
//@param source Series to analyze
//@param alpha Damping factor controlling the high-pass cutoff (lower = smoother, typical 0.07)
//@returns [cycle, trigger] — cycle oscillator and one-bar-delayed trigger line
//@reference John F. Ehlers, "Cybernetic Analysis for Stocks and Futures" (Wiley, 2004), Chapter 4
//@optimized O(1) per bar; 2 IIR state variables + 4-tap FIR smoother
ccyc(series float source, simple float alpha) =>
if alpha <= 0.0 or alpha >= 1.0
runtime.error("Alpha must be between 0 and 1 (exclusive)")
float price = nz(source)
// --- 4-element FIR smoother (eliminates 2-bar and 3-bar cycle noise) ---
float smooth = (price + 2.0 * nz(source[1], price) + 2.0 * nz(source[2], price) + nz(source[3], price)) / 6.0
// --- 2-pole high-pass IIR filter (Ehlers Cyber Cycle) ---
// Coefficients derived from alpha:
// c_hp = (1 - 0.5*alpha)^2
// c_fb1 = 2*(1 - alpha)
// c_fb2 = -(1 - alpha)^2
float c_hp = math.pow(1.0 - 0.5 * alpha, 2)
float c_fb1 = 2.0 * (1.0 - alpha)
float c_fb2 = -math.pow(1.0 - alpha, 2)
var float cycle = 0.0
var int bar_count = 0
bar_count += 1
if bar_count < 7
// Initialization: simple second-difference of raw price (bootstraps convergence)
cycle := (price - 2.0 * nz(source[1], price) + nz(source[2], price)) / 4.0
else
// Steady-state: high-pass filter on smoothed input
// cycle = c_hp * (smooth - 2*smooth[1] + smooth[2]) + c_fb1 * cycle[1] + c_fb2 * cycle[2]
cycle := c_hp * (smooth - 2.0 * nz(smooth[1], smooth) + nz(smooth[2], smooth)) + c_fb1 * nz(cycle[1]) + c_fb2 * nz(cycle[2])
// --- Trigger line: one-bar delay for crossover detection ---
float trigger = nz(cycle[1])
[cycle, trigger]
// ---------- Main loop ----------
// Inputs
i_alpha = input.float(0.07, "Alpha (damping)", minval=0.01, maxval=0.99, step=0.01)
i_source = input.source(hl2, "Source")
// Calculation
[cycle_out, trigger_out] = ccyc(i_source, i_alpha)
// Plots
plot(cycle_out, "Cycle", color=color.new(color.yellow, 0), linewidth=2)
plot(trigger_out, "Trigger", color=color.new(color.red, 0), linewidth=1)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)