// Licensed under the Apache License, Version 2.0 // © mihakralj //@version=6 indicator("Ehlers Hilbert Transform Dominant Cycle Phase (HT_DCPHASE)", "HT_DCPHASE", overlay=false) //@function Calculates Hilbert Transform Dominant Cycle Phase using TA-Lib algorithm //@param source Series to analyze for dominant cycle phase //@returns Phase angle in degrees ht_dcphase(series float source) => var float detrender = 0.0 var float i1 = 0.0 var float q1 = 0.0 var float ji = 0.0 var float jq = 0.0 var float i2 = 0.0 var float q2 = 0.0 var float re = 0.0 var float im = 0.0 var float period = 0.0 var float smooth_period = 0.0 var float dc_phase = 0.0 float price = nz(source) // Step 1: WMA smoothing (4-tap: [4,3,2,1]/10) float smooth_price = (4.0 * price + 3.0 * nz(price[1]) + 2.0 * nz(price[2]) + nz(price[3])) / 10.0 // Bandwidth uses period (not smooth_period) per TA-Lib float bandwidth = 0.075 * period + 0.54 // Step 2: Hilbert FIR detrender detrender := (0.0962 * smooth_price + 0.5769 * nz(smooth_price[2]) - 0.5769 * nz(smooth_price[4]) - 0.0962 * nz(smooth_price[6])) * bandwidth // Step 3: Q1 computation (Hilbert FIR on detrender) q1 := (0.0962 * detrender + 0.5769 * nz(detrender[2]) - 0.5769 * nz(detrender[4]) - 0.0962 * nz(detrender[6])) * bandwidth // Step 4: I1 = detrender delayed 3 bars i1 := nz(detrender[3]) // Step 5: Advance phase via JI/JQ ji := (0.0962 * i1 + 0.5769 * nz(i1[2]) - 0.5769 * nz(i1[4]) - 0.0962 * nz(i1[6])) * bandwidth jq := (0.0962 * q1 + 0.5769 * nz(q1[2]) - 0.5769 * nz(q1[4]) - 0.0962 * nz(q1[6])) * bandwidth // Step 6: Smooth I2/Q2 with 2-bar EMA i2 := 0.2 * (i1 - jq) + 0.8 * nz(i2[1]) q2 := 0.2 * (q1 + ji) + 0.8 * nz(q2[1]) // Step 7: Homodyne discriminator re := 0.2 * (i2 * nz(i2[1]) + q2 * nz(q2[1])) + 0.8 * nz(re[1]) im := 0.2 * (i2 * nz(q2[1]) - q2 * nz(i2[1])) + 0.8 * nz(im[1]) // Step 8: Period from atan (NOT atan2) + rate limiting + clamping float prev_period = period if math.abs(im) > 1e-12 and math.abs(re) > 1e-12 float angle = math.atan(im / re) if math.abs(angle) > 1e-12 period := 360.0 / (angle * (180.0 / math.pi)) // Rate limit: ±50% bar-to-bar if prev_period > 0 period := math.min(period, 1.5 * prev_period) period := math.max(period, 0.67 * prev_period) // Clamp to valid range period := math.max(6.0, math.min(50.0, period)) // Step 9: Smooth period with 0.2/0.8 EMA period := 0.2 * period + 0.8 * prev_period // Step 10: Smooth smoothPeriod with 0.33/0.67 EMA smooth_period := 0.33 * period + 0.67 * smooth_period // Step 11: DFT-based DC Phase extraction int dc_period_int = int(smooth_period + 0.5) float real_part = 0.0 float imag_part = 0.0 for i = 0 to dc_period_int - 1 float temp_angle = i * 2.0 * math.pi / dc_period_int float sp_val = nz(smooth_price[i]) real_part += math.sin(temp_angle) * sp_val imag_part += math.cos(temp_angle) * sp_val // Phase from DFT components float abs_imag = math.abs(imag_part) if abs_imag > 0.0 dc_phase := math.atan(real_part / imag_part) * (180.0 / math.pi) else if abs_imag <= 0.01 if real_part < 0.0 dc_phase -= 90.0 else if real_part > 0.0 dc_phase += 90.0 // Phase adjustments per TA-Lib dc_phase += 90.0 dc_phase += 360.0 / smooth_period if imag_part < 0.0 dc_phase += 180.0 if dc_phase > 315.0 dc_phase -= 360.0 dc_phase // ---------- Main loop ---------- // Inputs i_source = input.source(hlc3, "Source") // Calculation dcphase = ht_dcphase(i_source) // Plot plot(dcphase, "Dominant Cycle Phase", color=color.yellow, linewidth=2) hline(0, "Zero Phase", color=color.gray, linestyle=hline.style_solid) hline(180, "180°", color=color.new(color.gray, 70), linestyle=hline.style_dashed) hline(-180, "-180°", color=color.new(color.gray, 70), linestyle=hline.style_dashed)