Remove multiple Pine Script indicators: SSFDSP, STARCHANNEL, STBANDS, STC, UBANDS, UCHANNEL, VWAPBANDS, and VWAPSD. These indicators were deleted to streamline the library and remove unused or redundant code.

This commit is contained in:
Miha Kralj
2026-02-20 18:44:56 -08:00
parent 3dd05f23e4
commit cbeefc9d64
283 changed files with 23963 additions and 3838 deletions
+107
View File
@@ -0,0 +1,107 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Ehlers Correlation Cycle (CCOR)", "CCOR", overlay=false)
//@function Computes Ehlers Correlation Cycle — extracts cycle phase via Pearson correlation of price
// with cosine (Real) and negative-sine (Imag) reference waves of a presumed fixed period.
// Converts to phasor angle with monotonic constraint, and derives market state.
//@param source Series to analyze
//@param period Presumed dominant cycle wavelength
//@param threshold Angle rate-of-change threshold (degrees) for trend/cycle state detection
//@returns [real, imag, angle, state] — correlation components, phasor angle, market state (+1/-1/0)
//@reference John F. Ehlers, "Correlation As A Cycle Indicator" (Stocks & Commodities, TASC Jun 2020)
//@optimized O(period) per bar for dual correlation loops; O(1) state variables
ccor(series float source, simple int period, simple float threshold) =>
if period <= 0
runtime.error("Period must be greater than 0")
if threshold <= 0
runtime.error("Threshold must be greater than 0")
var float prev_angle = 0.0
float price = nz(source)
// --- Correlate price with cosine wave (Real component) ---
float sx_r = 0.0
float sy_r = 0.0
float sxx_r = 0.0
float sxy_r = 0.0
float syy_r = 0.0
for count = 0 to period - 1
float x = nz(source[count], price)
float y = math.cos(2.0 * math.pi * count / float(period))
sx_r += x
sy_r += y
sxx_r += x * x
sxy_r += x * y
syy_r += y * y
float n = float(period)
float denom_r = (n * sxx_r - sx_r * sx_r) * (n * syy_r - sy_r * sy_r)
float real_val = denom_r > 0.0 ? (n * sxy_r - sx_r * sy_r) / math.sqrt(denom_r) : 0.0
// --- Correlate price with negative sine wave (Imaginary component) ---
float sx_i = 0.0
float sy_i = 0.0
float sxx_i = 0.0
float sxy_i = 0.0
float syy_i = 0.0
for count = 0 to period - 1
float x = nz(source[count], price)
float y = -math.sin(2.0 * math.pi * count / float(period))
sx_i += x
sy_i += y
sxx_i += x * x
sxy_i += x * y
syy_i += y * y
float denom_i = (n * sxx_i - sx_i * sx_i) * (n * syy_i - sy_i * sy_i)
float imag_val = denom_i > 0.0 ? (n * sxy_i - sx_i * sy_i) / math.sqrt(denom_i) : 0.0
// --- Compute phasor angle (degrees) with quadrant resolution ---
float angle = 0.0
if imag_val != 0.0
angle := 90.0 + math.todegrees(math.atan(real_val / imag_val))
if imag_val > 0.0
angle -= 180.0
// --- Monotonic constraint: angle cannot go backward ---
float saved_prev = prev_angle
if angle < prev_angle
angle := prev_angle
prev_angle := angle
// --- Market state detection ---
// Small angle change → trending; large angle change → cycling
float angle_change = math.abs(angle - saved_prev)
int state = 0
if angle_change < threshold and angle <= 0.0
state := -1 // downtrend
if angle_change < threshold and angle >= 0.0
state := 1 // uptrend
// state = 0 → cycling mode
[real_val, imag_val, angle, state]
// ---------- Main loop ----------
// Inputs
i_period = input.int(20, "Period", minval=2)
i_threshold = input.float(9.0, "State Threshold (degrees)", minval=0.1, step=0.5)
i_source = input.source(close, "Source")
// Calculation
[real_out, imag_out, angle_out, state_out] = ccor(i_source, i_period, i_threshold)
// Scaled Real/Imag for display: map [-1,+1] → [-100,+100]
float real_scaled = real_out * 100.0
float imag_scaled = imag_out * 100.0
// Colors based on Real vs Imag crossover
color sig_color = real_scaled > imag_scaled ? color.new(color.green, 0) : color.new(color.red, 0)
// Plots
plot(real_scaled, "Real (×100)", color=sig_color, linewidth=2)
plot(imag_scaled, "Imag (×100)", color=color.gray, linewidth=1)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
+61
View File
@@ -0,0 +1,61 @@
// The MIT License (MIT)
// © 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)
+2 -2
View File
@@ -16,8 +16,8 @@ cg(series float src, simple int length) =>
float price = nz(src[count - 1])
num += count * price
den += price
float result = den != 0 ? num / den : (length + 1) / 2.0
result - (length + 1) / 2.0
float result = den != 0 ? -num / den : -(length + 1) / 2.0
result + (length + 1) / 2.0
// ---------- Main loop ----------
+27 -12
View File
@@ -56,7 +56,8 @@ public sealed class Eacp : AbstractBase
double Hp0, double Hp1, double Hp2,
double Filt0, double Filt1, double Filt2,
double Dom, double DomPower, double MaxPwr,
int BarCount, double LastValidValue
int BarCount, double LastValidValue,
double WarmupDecay, bool InWarmup
);
private State _s;
@@ -128,7 +129,7 @@ public sealed class Eacp : AbstractBase
// Initialize state
double initialDom = (minPeriod + maxPeriod) * 0.5;
_s = new State(0, 0, 0, 0, 0, 0, 0, 0, 0, initialDom, 0, 0, 0, 0);
_s = new State(0, 0, 0, 0, 0, 0, 0, 0, 0, initialDom, 0, 0, 0, 0, 1.0, true);
_ps = _s;
}
@@ -205,12 +206,14 @@ public sealed class Eacp : AbstractBase
// Compute power spectrum via DFT
ComputePowerSpectrum();
// Find dominant cycle
var (dom, domPower, maxPwr) = FindDominantCycle(s.Dom, s.MaxPwr);
// Find dominant cycle (with warmup compensation)
var (dom, domPower, maxPwr, warmupDecay, inWarmup) =
FindDominantCycle(s.Dom, s.MaxPwr, s.WarmupDecay, s.InWarmup);
// Update state
_s = new State(price0, price1, price2, hp0, hp1, hp2, filt0, filt1, filt2,
dom, domPower, maxPwr, barCount, s.LastValidValue);
dom, domPower, maxPwr, barCount, s.LastValidValue,
warmupDecay, inWarmup);
Last = new TValue(input.Time, dom);
PubEvent(Last, isNew);
@@ -314,12 +317,14 @@ public sealed class Eacp : AbstractBase
double sq = cosAcc * cosAcc + sinAcc * sinAcc;
// Smooth the power spectrum (EMA-like smoothing)
_smooth[period] = 0.2 * sq + 0.8 * _smooth[period];
// Power squared per Ehlers: emphasizes spectral peaks, suppresses noise
_smooth[period] = Math.FusedMultiplyAdd(0.2, sq * sq, 0.8 * _smooth[period]);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private (double dom, double domPower, double maxPwr) FindDominantCycle(double prevDom, double prevMaxPwr)
private (double dom, double domPower, double maxPwr, double warmupDecay, bool inWarmup)
FindDominantCycle(double prevDom, double prevMaxPwr, double prevWarmupDecay, bool prevInWarmup)
{
// Find local maximum power
double localMaxPwr = 0;
@@ -368,9 +373,19 @@ public sealed class Eacp : AbstractBase
// Calculate dominant cycle - use prevDom as fallback
double baseDom = sumWeight >= 0.25 ? weighted / sumWeight : prevDom;
// Apply EMA smoothing (alpha = 0.2) - this is the PineScript formula
// dom := alpha*(base-dom)+dom which equals dom + alpha*(base-dom)
double dom = prevDom + 0.2 * (baseDom - prevDom);
// Apply EMA smoothing (alpha = 0.2, beta = 0.8)
double dom = Math.FusedMultiplyAdd(0.2, baseDom - prevDom, prevDom);
// Warmup compensation §2: correct EMA bias during early bars
double warmupDecay = prevWarmupDecay;
bool inWarmup = prevInWarmup;
if (inWarmup)
{
warmupDecay *= 0.8; // beta = 1 - alpha = 0.8
double c = 1.0 / (1.0 - warmupDecay);
dom *= c;
inWarmup = warmupDecay > 1e-10;
}
// Ensure dom stays within bounds
dom = Math.Clamp(dom, _minPeriod, _maxPeriod);
@@ -379,13 +394,13 @@ public sealed class Eacp : AbstractBase
int domIdx = Math.Clamp((int)Math.Round(dom), _minPeriod, _maxPeriod);
double domPower = Math.Clamp(_power[domIdx], 0.0, 1.0);
return (dom, domPower, maxPwr);
return (dom, domPower, maxPwr, warmupDecay, inWarmup);
}
public override void Reset()
{
double initialDom = (_minPeriod + _maxPeriod) * 0.5;
_s = new State(0, 0, 0, 0, 0, 0, 0, 0, 0, initialDom, 0, 0, 0, 0);
_s = new State(0, 0, 0, 0, 0, 0, 0, 0, 0, initialDom, 0, 0, 0, 0, 1.0, true);
_ps = _s;
_filtHistory.Clear();
Array.Clear(_corr);
+1 -1
View File
@@ -31,7 +31,7 @@ $$
$$
$$
Filt_t = \frac{1 - 2\alpha_2\cos(\sqrt{2}\pi/SSF) - \alpha_2^2}{2}(HP_t + HP_{t-1}) + 2\alpha_2\cos(\sqrt{2}\pi/SSF) \cdot Filt_{t-1} - \alpha_2^2 \cdot Filt_{t-2}
Filt_t = \frac{1 - 2\alpha_2\cos(\sqrt{2}\pi/SSF) + \alpha_2^2}{2}(HP_t + HP_{t-1}) + 2\alpha_2\cos(\sqrt{2}\pi/SSF) \cdot Filt_{t-1} - \alpha_2^2 \cdot Filt_{t-2}
$$
### 3. Wave & Power Calculation
+3 -4
View File
@@ -240,13 +240,12 @@ public class HomodValidationTests
public void Homod_HandlesVolatileInput()
{
var homod = new Homod(6, 50);
var random = new Random(42);
var bars = new GBM(seed: 42).Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromSeconds(1));
// Highly volatile random input
// Highly volatile GBM input
for (int i = 0; i < 500; i++)
{
double value = 100.0 + (random.NextDouble() - 0.5) * 50;
var result = homod.Update(new TValue(DateTime.UtcNow.AddSeconds(i), value));
var result = homod.Update(bars.Close[i]);
Assert.True(double.IsFinite(result.Value));
if (homod.IsHot)
+8 -2
View File
@@ -72,9 +72,15 @@ homod(series float source,simple float minPeriod,simple float maxPeriod)=>
float candidate=2.0*math.pi/angle
float clamped=math.max(minPeriod,math.min(maxPeriod,math.abs(candidate)))
period:=0.2*clamped+0.8*period
float alpha=0.33
smooth_period:=smooth_period+alpha*(period-smooth_period)
// Rate limiter: ±50% bar-to-bar, then clamp 6..50
float prevPeriod = nz(period[1], 15.0)
period := math.max(period, 0.67 * prevPeriod)
period := math.min(period, 1.5 * prevPeriod)
period := math.max(period, 6.0)
period := math.min(period, 50.0)
smooth_period := 0.2 * period + 0.8 * nz(smooth_period[1], period)
float result=smooth_period
float alpha=0.2
if warmup
warm_decay*=1.0-alpha
float denom=1.0-warm_decay
+45 -37
View File
@@ -3,31 +3,10 @@
//@version=6
indicator("Ehlers Hilbert Transform Dominant Cycle Period (HT_DCPERIOD)", "HT_DCPERIOD", overlay=false)
//@function Numerically stable atan2 implementation for quadrant-aware angle calculation
//@param y Y-coordinate (imaginary/quadrature component)
//@param x X-coordinate (real/in-phase component)
//@returns Angle in radians from -π to π
atan2(series float y, series float x) =>
if y == 0.0 and x == 0.0
runtime.error("atan2: Both y and x cannot be zero")
ay = math.abs(y)
ax = math.abs(x)
angle = 0.0
if ax > ay
angle := math.atan(ay / ax)
else
angle := (math.pi / 2.0) - math.atan(ax / ay)
if x < 0.0
angle := math.pi - angle
if y < 0.0
angle := -angle
angle
//@function Calculates Hilbert Transform Dominant Cycle Period using Ehlers algorithm
//@function Calculates Hilbert Transform Dominant Cycle Period using TA-Lib algorithm
//@param source Series to analyze for dominant cycle
//@returns Dominant cycle period in bars (typically 6-50)
ht_dcperiod(series float source) =>
var float smooth_price = 0.0
var float detrender = 0.0
var float i1 = 0.0
var float q1 = 0.0
@@ -37,30 +16,59 @@ ht_dcperiod(series float source) =>
var float q2 = 0.0
var float re = 0.0
var float im = 0.0
var float period = 15.0
var float smooth_period = 15.0
var float period = 0.0
var float smooth_period = 0.0
float price = nz(source)
float bandwidth = 0.075 * smooth_period + 0.54
smooth_price := (4.0 * price + 3.0 * nz(price[1]) + 2.0 * nz(price[2]) + nz(price[3])) / 10.0
// 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
i2 := i1 - jq
q2 := q1 + ji
i2 := 0.2 * i2 + 0.8 * nz(i2[1])
q2 := 0.2 * q2 + 0.8 * nz(q2[1])
re := i2 * nz(i2[1]) + q2 * nz(q2[1])
im := i2 * nz(q2[1]) - q2 * nz(i2[1])
re := 0.2 * re + 0.8 * nz(re[1])
im := 0.2 * im + 0.8 * nz(im[1])
if im != 0.0 or re != 0.0
float angle = atan2(im, re)
if angle != 0.0
// 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 := 2.0 * math.pi / angle
// 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
smooth_period
// ---------- Main loop ----------
+81 -45
View File
@@ -3,31 +3,10 @@
//@version=6
indicator("Ehlers Hilbert Transform Dominant Cycle Phase (HT_DCPHASE)", "HT_DCPHASE", overlay=false)
//@function Numerically stable atan2 implementation for quadrant-aware angle calculation
//@param y Y-coordinate (imaginary/quadrature component)
//@param x X-coordinate (real/in-phase component)
//@returns Angle in radians from -π to π
atan2(series float y, series float x) =>
if y == 0.0 and x == 0.0
runtime.error("atan2: Both y and x cannot be zero")
ay = math.abs(y)
ax = math.abs(x)
angle = 0.0
if ax > ay
angle := math.atan(ay / ax)
else
angle := (math.pi / 2.0) - math.atan(ax / ay)
if x < 0.0
angle := math.pi - angle
if y < 0.0
angle := -angle
angle
//@function Calculates Hilbert Transform Dominant Cycle Phase using Ehlers algorithm
//@function Calculates Hilbert Transform Dominant Cycle Phase using TA-Lib algorithm
//@param source Series to analyze for dominant cycle phase
//@returns Phase angle in radians (-π to π)
//@returns Phase angle in degrees
ht_dcphase(series float source) =>
var float smooth_price = 0.0
var float detrender = 0.0
var float i1 = 0.0
var float q1 = 0.0
@@ -37,34 +16,91 @@ ht_dcphase(series float source) =>
var float q2 = 0.0
var float re = 0.0
var float im = 0.0
var float period = 15.0
var float smooth_period = 15.0
var float phase = 0.0
var float period = 0.0
var float smooth_period = 0.0
var float dc_phase = 0.0
float price = nz(source)
float bandwidth = 0.075 * smooth_period + 0.54
smooth_price := (4.0 * price + 3.0 * nz(price[1]) + 2.0 * nz(price[2]) + nz(price[3])) / 10.0
// 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
i2 := i1 - jq
q2 := q1 + ji
i2 := 0.2 * i2 + 0.8 * nz(i2[1])
q2 := 0.2 * q2 + 0.8 * nz(q2[1])
re := i2 * nz(i2[1]) + q2 * nz(q2[1])
im := i2 * nz(q2[1]) - q2 * nz(i2[1])
re := 0.2 * re + 0.8 * nz(re[1])
im := 0.2 * im + 0.8 * nz(im[1])
if im != 0.0 or re != 0.0
float angle = atan2(im, re)
if angle != 0.0
period := 2.0 * math.pi / angle
// 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
if i2 != 0.0 or q2 != 0.0
phase := atan2(q2, i2)
phase
// 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 ----------
@@ -77,5 +113,5 @@ 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(1.5708, "π/2", color=color.new(color.gray, 70), linestyle=hline.style_dashed)
hline(-1.5708, "-π/2", color=color.new(color.gray, 70), linestyle=hline.style_dashed)
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)
+79 -108
View File
@@ -1,118 +1,89 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Ehlers Hilbert Transform Phasor Components (HT_PHASOR)", shorttitle="HT_PHASOR", overlay=false)
indicator("Ehlers Hilbert Transform Phasor Components (HT_PHASOR)", "HT_PHASOR", overlay=false)
//@function Calculates the Ehlers Phasor Angle, Derived Period, and Trend State.
//@param src The source series to analyze.
//@param period The fixed cycle period to correlate against. Default is 28.
//@returns A tuple: `[float finalPhasorAngle, float derivedPeriod, int trendState]`.
phasor(series float src, simple int period = 28) =>
float sx_corr = 0.0
float sy_cos_corr = 0.0
float sxx_corr = 0.0
float sxy_cos_corr = 0.0
float syy_cos_corr = 0.0
for i = 0 to period - 1
float x_val = nz(src[i])
float y_val_cos = math.cos(2 * math.pi * i / period)
sx_corr += x_val
sy_cos_corr += y_val_cos
sxx_corr += x_val * x_val
sxy_cos_corr += x_val * y_val_cos
syy_cos_corr += y_val_cos * y_val_cos
float real_part = 0.0
float den_cos = (period * sxx_corr - sx_corr * sx_corr) * (period * syy_cos_corr - sy_cos_corr * sy_cos_corr)
if den_cos > 0
real_part := (period * sxy_cos_corr - sx_corr * sy_cos_corr) / math.sqrt(den_cos)
sx_corr := 0.0
sxx_corr := 0.0
float sy_sin_corr = 0.0
float sxy_sin_corr = 0.0
float syy_sin_corr = 0.0
for i = 0 to period - 1
float x_val = nz(src[i])
float y_val_sin = -math.sin(2 * math.pi * i / period) // Negative sine as per Ehlers
sx_corr += x_val
sxx_corr += x_val * x_val
sy_sin_corr += y_val_sin
sxy_sin_corr += x_val * y_val_sin
syy_sin_corr += y_val_sin * y_val_sin
float imag_part = 0.0
float den_sin = (period * sxx_corr - sx_corr * sx_corr) * (period * syy_sin_corr - sy_sin_corr * sy_sin_corr)
if den_sin > 0
imag_part := (period * sxy_sin_corr - sx_corr * sy_sin_corr) / math.sqrt(den_sin)
float current_raw_phase = 0.0
if real_part != 0.0
current_raw_phase := 90.0 - math.atan(imag_part / real_part) * 180.0 / math.pi
if real_part < 0.0
current_raw_phase -= 180.0
else if imag_part != 0.0
current_raw_phase := imag_part > 0.0 ? 0.0 : 180.0
var float core_Phasor_unwrapped_state = na
if not na(core_Phasor_unwrapped_state[1])
float diff = current_raw_phase - core_Phasor_unwrapped_state[1]
if diff > 180.0
current_raw_phase -= 360.0
else if diff < -180.0
current_raw_phase += 360.0
core_Phasor_unwrapped_state := na(core_Phasor_unwrapped_state[1]) ? current_raw_phase : core_Phasor_unwrapped_state[1] + (current_raw_phase - core_Phasor_unwrapped_state[1])
float calculated_Phasor_val = core_Phasor_unwrapped_state
var float final_Phasor_state = na
if na(final_Phasor_state[1])
final_Phasor_state := calculated_Phasor_val
else
if calculated_Phasor_val < final_Phasor_state[1] and ((calculated_Phasor_val > -135 and final_Phasor_state[1] < 135) or (calculated_Phasor_val < -90 and final_Phasor_state[1] < -90))
final_Phasor_state := final_Phasor_state[1]
else
final_Phasor_state := calculated_Phasor_val
var float derivedPeriod_calc_state = na
float angle_Change_For_Period = final_Phasor_state - nz(final_Phasor_state[1], final_Phasor_state)
if nz(angle_Change_For_Period) == 0 and not na(derivedPeriod_calc_state[1])
if derivedPeriod_calc_state[1] != 0
angle_Change_For_Period := 360.0 / derivedPeriod_calc_state[1]
else
angle_Change_For_Period := 0.0
if nz(angle_Change_For_Period) <= 0 and not na(derivedPeriod_calc_state[1])
if derivedPeriod_calc_state[1] != 0
angle_Change_For_Period := 360.0 / derivedPeriod_calc_state[1]
else
angle_Change_For_Period := 0.0
if nz(angle_Change_For_Period) != 0.0
derivedPeriod_calc_state := 360.0 / angle_Change_For_Period
else if not na(derivedPeriod_calc_state[1])
derivedPeriod_calc_state := derivedPeriod_calc_state[1]
else
derivedPeriod_calc_state := 60.0
derivedPeriod_calc_state := math.max(1.0, math.min(derivedPeriod_calc_state, 60.0))
var int trendState_calc_state = 0
float angle_Change_For_State = final_Phasor_state - nz(final_Phasor_state[1], final_Phasor_state)
int currentTrendState_calc = 0
if angle_Change_For_State <= 6.0
if final_Phasor_state >= 90.0 or final_Phasor_state <= -90.0
currentTrendState_calc := 1
else if final_Phasor_state > -90.0 and final_Phasor_state < 90.0
currentTrendState_calc := -1
trendState_calc_state := currentTrendState_calc
[final_Phasor_state, derivedPeriod_calc_state, trendState_calc_state]
//@function Calculates Hilbert Transform Phasor Components using TA-Lib algorithm
//@param source Series to analyze for phasor components
//@returns Tuple [inphase, quadrature] - raw I1[3] and Q1 components
ht_phasor(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
// ---------- Inputs ----------
i_period = input.int(28, "Period", minval=1, group="Phasor Settings")
i_source = input.source(close, "Source", group="Phasor Settings")
showDerivedPeriod = input.bool(false, "Show Derived Period", group="Optional Plots", inline="derived_period")
showTrendState = input.bool(false, "Show Trend State Variable", group="Optional Plots", inline="trend_state")
float price = nz(source)
// ---------- Calculations ----------
// Call the main function to get all values
[phasorAngle, derivedPeriodValue, trendStateValue] = phasor(i_source, i_period)
// 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
// ---------- Plotting Phasor Angle ----------
plot(phasorAngle, "Phasor Angle", color=color.yellow, linewidth=2)
// 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
// ---------- Optional Plots ----------
// Plot for Derived Period
plot(showDerivedPeriod ? derivedPeriodValue : na, "Derived Period", color=color.yellow, linewidth=2)
// 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
// Plot for Trend State
plot(showTrendState ? trendStateValue : na, "Trend State", color=color.yellow, linewidth=2, style=plot.style_histogram)
// 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 (used internally for period calc)
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 := 2.0 * math.pi / angle
// 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: Output raw I1[3] (inPhase) and Q1 (quadrature) per TA-Lib HT_PHASOR
// TA-Lib outputs the detrender delayed by 3 bars as InPhase, and the raw Q1 as Quadrature
float inphase_out = nz(i1[3])
float quadrature_out = q1
[inphase_out, quadrature_out]
// ---------- Main loop ----------
// Inputs
i_source = input.source(hlc3, "Source")
// Calculation
[inphase, quadrature] = ht_phasor(i_source)
// Plot
plot(inphase, "InPhase", color=color.yellow, linewidth=2)
plot(quadrature, "Quadrature", color=color.blue, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_solid)
+80 -45
View File
@@ -3,31 +3,10 @@
//@version=6
indicator("Ehlers Hilbert Transform SineWave (HT_SINE)", "HT_SINE", overlay=false)
//@function Numerically stable atan2 implementation for quadrant-aware angle calculation
//@param y Y-coordinate (imaginary/quadrature component)
//@param x X-coordinate (real/in-phase component)
//@returns Angle in radians from -π to π
atan2(series float y, series float x) =>
if y == 0.0 and x == 0.0
runtime.error("atan2: Both y and x cannot be zero")
ay = math.abs(y)
ax = math.abs(x)
angle = 0.0
if ax > ay
angle := math.atan(ay / ax)
else
angle := (math.pi / 2.0) - math.atan(ax / ay)
if x < 0.0
angle := math.pi - angle
if y < 0.0
angle := -angle
angle
//@function Calculates Hilbert Transform SineWave and LeadSine
//@function Calculates Hilbert Transform SineWave and LeadSine using TA-Lib algorithm
//@param source Series to analyze for dominant cycle
//@returns Tuple [sine, leadsine] - sine wave and lead sine wave
//@returns Tuple [sine, leadsine] - sine wave and lead sine wave (+45° phase lead)
ht_sine(series float source) =>
var float smooth_price = 0.0
var float detrender = 0.0
var float i1 = 0.0
var float q1 = 0.0
@@ -37,37 +16,93 @@ ht_sine(series float source) =>
var float q2 = 0.0
var float re = 0.0
var float im = 0.0
var float period = 15.0
var float smooth_period = 15.0
var float phase = 0.0
var float sine = 0.0
var float leadsine = 0.0
var float period = 0.0
var float smooth_period = 0.0
var float dc_phase = 0.0
float price = nz(source)
float bandwidth = 0.075 * smooth_period + 0.54
smooth_price := (4.0 * price + 3.0 * nz(price[1]) + 2.0 * nz(price[2]) + nz(price[3])) / 10.0
// 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
i2 := i1 - jq
q2 := q1 + ji
i2 := 0.2 * i2 + 0.8 * nz(i2[1])
q2 := 0.2 * q2 + 0.8 * nz(q2[1])
re := i2 * nz(i2[1]) + q2 * nz(q2[1])
im := i2 * nz(q2[1]) - q2 * nz(i2[1])
re := 0.2 * re + 0.8 * nz(re[1])
im := 0.2 * im + 0.8 * nz(im[1])
if im != 0.0 or re != 0.0
float angle = atan2(im, re)
if angle != 0.0
// 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 := 2.0 * math.pi / angle
// 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
if i2 != 0.0 or q2 != 0.0
phase := atan2(q2, i2)
sine := math.sin(phase)
leadsine := math.sin(phase + math.pi / 4.0)
// Step 11: DFT-based DC Phase extraction (identical to HT_DCPHASE)
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
// Step 12: Output sine and leadsine from DC Phase (in degrees -> radians for sin)
float sine = math.sin(dc_phase * math.pi / 180.0)
float leadsine = math.sin((dc_phase + 45.0) * math.pi / 180.0)
[sine, leadsine]
// ---------- Main loop ----------
+9 -6
View File
@@ -6,7 +6,7 @@ indicator("Schaff Trend Cycle (STC)", "STC", overlay=false)
ema(series float source,simple int period=0,simple float alpha=0)=>
if alpha<=0 and period<=0
runtime.error("Alpha or period must be provided")
float a=alpha>0?alpha:2.0/math.max(period,1)
float a=alpha>0?alpha:2.0/(math.max(period,1)+1)
var float raw_ema=na
var float ema=na
var float e=1.0
@@ -34,7 +34,7 @@ ema(series float source,simple int period=0,simple float alpha=0)=>
//@param slowLength Period for slow EMA calculation
//@param smoothingType Type of smoothing (0:none, 1:ema, 2:sigmoid, 3:digital)
//@returns Smoothed STC value
stc(series float source, simple int cycleLength, simple int fastLength, simple int slowLength, simple int smoothingType = 2) =>
stc(series float source, simple int cycleLength, simple int fastLength, simple int slowLength, simple int smoothingType = 1) =>
float fast_ema = ema(source, fastLength)
float slow_ema = ema(source, slowLength)
float macdLine = fast_ema - slow_ema
@@ -45,8 +45,11 @@ stc(series float source, simple int cycleLength, simple int fastLength, simple i
float stoch1 = ema(stoch1_raw, 3)
h2 = ta.highest(stoch1, cycleLength)
l2 = ta.lowest(stoch1, cycleLength)
float stoch2 = (h2 - l2) > 0 ? 100 * (stoch1 - l2) / (h2 - l2) : 0
float stoch2_raw = (h2 - l2) > 0 ? 100 * (stoch1 - l2) / (h2 - l2) : 0
// Second-stage IIR smoothing: PFF = PFF[1] + 0.5 * (Frac2 - PFF[1])
var float stoch2 = na
stoch2 := na(stoch2[1]) ? stoch2_raw : stoch2[1] + 0.5 * (stoch2_raw - stoch2[1])
float stcValue = stoch2
if smoothingType == 1
@@ -61,10 +64,10 @@ stc(series float source, simple int cycleLength, simple int fastLength, simple i
// Inputs
i_source = input.source(close, title="Source")
i_cycleLength = input.int(12, title="Cycle Length", minval=2)
i_fastLength = input.int(26, title="Fast Length", minval=2)
i_cycleLength = input.int(10, title="Cycle Length", minval=2)
i_fastLength = input.int(23, title="Fast Length", minval=2)
i_slowLength = input.int(50, title="Slow Length", minval=2)
i_smoothingType = input.int(2, title="Smoothing", minval=0, maxval=3, tooltip="0: none, 1:ema, 2:sigmoid, 3:digital")
i_smoothingType = input.int(1, title="Smoothing", minval=0, maxval=3, tooltip="0: none, 1:ema, 2:sigmoid, 3:digital")
// Calculation
stcValue = stc(i_source, i_cycleLength, i_fastLength, i_slowLength, i_smoothingType)