mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-19 19:18:05 +00:00
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:
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ----------
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ----------
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user