mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
97 lines
3.8 KiB
Plaintext
97 lines
3.8 KiB
Plaintext
// Licensed under the Apache License, Version 2.0
|
|
// © mihakralj
|
|
//@version=6
|
|
indicator("Ehlers Homodyne Discriminator (HOMOD)","HOMOD",overlay=false)
|
|
|
|
//@function Quadrant-aware angle calculation using stable atan2
|
|
//@param y Imaginary component
|
|
//@param x Real 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: y and x cannot both be zero")
|
|
float ay=math.abs(y)
|
|
float ax=math.abs(x)
|
|
float 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 Measures dominant cycle period using Ehlers homodyne discriminator
|
|
//@param source Price input series
|
|
//@param minPeriod Minimum dominant cycle length
|
|
//@param maxPeriod Maximum dominant cycle length
|
|
//@returns Smoothed dominant cycle estimate
|
|
//@optimized Exponential warmup compensation for dominant cycle smoothing
|
|
//@validation wolfram:"atan2(y,x)" external:"TradingView Homodyne Discriminator","Forex-Station Homodyne Discriminator","MQL5 Adaptive Lookback Homodyne","tindicators hd.cc"
|
|
homod(series float source,simple float minPeriod,simple float maxPeriod)=>
|
|
if minPeriod<=0
|
|
runtime.error("Min period must be greater than 0")
|
|
if maxPeriod<=minPeriod
|
|
runtime.error("Max period must be greater than min period")
|
|
var float smooth_price=0.0
|
|
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=15.0
|
|
var float smooth_period=15.0
|
|
var float warm_decay=1.0
|
|
var bool warmup=true
|
|
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
|
|
detrender:=(0.0962*smooth_price+0.5769*nz(smooth_price[2])-0.5769*nz(smooth_price[4])-0.0962*nz(smooth_price[6]))*bandwidth
|
|
q1:=(0.0962*detrender+0.5769*nz(detrender[2])-0.5769*nz(detrender[4])-0.0962*nz(detrender[6]))*bandwidth
|
|
i1:=nz(detrender[3])
|
|
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
|
|
float i2_raw=i1-jq
|
|
float q2_raw=q1+ji
|
|
i2:=0.2*i2_raw+0.8*nz(i2[1])
|
|
q2:=0.2*q2_raw+0.8*nz(q2[1])
|
|
float re_raw=i2*nz(i2[1])+q2*nz(q2[1])
|
|
float im_raw=i2*nz(q2[1])-q2*nz(i2[1])
|
|
re:=0.2*re_raw+0.8*nz(re[1])
|
|
im:=0.2*im_raw+0.8*nz(im[1])
|
|
float magnitude=math.abs(re)+math.abs(im)
|
|
if magnitude>1e-10
|
|
float angle=atan2(im,re)
|
|
if math.abs(angle)>1e-10
|
|
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
|
|
// 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
|
|
result:=denom>1e-10?result/denom:result
|
|
warmup:=warm_decay>1e-10
|
|
result
|
|
|
|
// ---------- Main loop ----------
|
|
i_source=input.source(hlc3,"Source")
|
|
i_minPeriod=input.float(6,"Min Period",minval=1,maxval=5000,step=0.5)
|
|
i_maxPeriod=input.float(50,"Max Period",minval=2,maxval=5000,step=0.5)
|
|
homodPeriod=homod(i_source,i_minPeriod,i_maxPeriod)
|
|
plot(homodPeriod,"Dominant Cycle Period",color=color.yellow,linewidth=2)
|