Refactor documentation for various filters and indicators to enhance clarity and consistency

- Updated Bessel, Bilateral, Blma, Butter, Conv, Ema, Kama, LSMA, MAMA, MGDI, SSF, USF, ATR, ADL, and ADOSC documentation to use bullet points for key concepts and features.
- Added a new Qodana configuration file for code analysis.
- Removed coverage configuration from Quantower.Tests.csproj to streamline testing setup.
This commit is contained in:
Miha Kralj
2025-12-31 23:39:47 -08:00
parent 11f4ec2497
commit d493bfd42f
175 changed files with 11977 additions and 897 deletions
+1 -4
View File
@@ -1,7 +1,5 @@
# Cycles
Indicators focusing on cycle detection and periodicity in market data.
| Indicator | Full Name | Description |
| :--- | :--- | :--- |
| CFB | Jurik Composite Fractal Behavior | |
@@ -9,15 +7,14 @@ Indicators focusing on cycle detection and periodicity in market data.
| DSP | Detrended Synthetic Price | |
| EACP | Ehlers Autocorrelation Periodogram | |
| EBSW | Ehlers Even Better Sinewave | |
| SSFDSP | SSF-Based Detrended Synthetic Price | |
| HOMOD | Homodyne Discriminator Dominant Cycle | |
| HT_DCPERIOD | Ehlers Hilbert Transform Dominant Cycle Period | |
| HT_DCPHASE | Ehlers Hilbert Transform Dominant Cycle Phase | |
| HT_PHASOR | Ehlers Hilbert Transform Phasor Components | |
| HT_SINE | Ehlers Hilbert Transform SineWave | |
| LUNAR | Lunar Phase | |
| MOON | Moon Phase | |
| PHASOR | Ehlers Phasor Analysis | |
| SINE | Ehlers Sine Wave | |
| SOLAR | Solar Activity Cycle | |
| SSFDSP | Ehlers SSF-Based Detrended Synthetic Price | |
| STC | Schaff Trend Cycle | |
+90
View File
@@ -0,0 +1,90 @@
# CG: Center of Gravity
## Overview and Purpose
The Center of Gravity (CG) indicator, developed by John Ehlers, is a cycle analysis tool that uses the physics concept of center of gravity to identify cycle turning points in financial markets. By calculating the balance point of price data over a specified period, the indicator creates an oscillator that can help traders anticipate potential reversal points in market cycles.
Unlike traditional moving averages that simply smooth price data, the Center of Gravity indicator treats price data as masses distributed over time and calculates where the "balance point" would be. This approach provides insights into the distribution of price momentum within the lookback period.
## Core Concepts
* **Physics-based approach:** Uses the center of gravity concept from physics where each price point represents a mass and the indicator finds the balance point
* **Oscillating indicator:** Provides an oscillator that fluctuates around zero based on price distribution
* **Cycle identification:** Particularly effective at identifying shifts in the dominant cycle within the lookback period
* **Zero-line analysis:** Oscillates around zero with crossovers indicating potential cycle phase changes
The core innovation of this indicator is its ability to measure where the "weight" of price data is concentrated within the lookback period, providing insights into market momentum distribution.
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Length | 10 | Controls the lookback period for the Center of Gravity calculation | Increase for longer cycles and smoother signals, decrease for shorter cycles and more responsive signals |
| Source | close | Price data used for calculation | Use close for trend-following, hlc3 for balanced representation, or hl2 for range-based analysis |
**Pro Tip:** The optimal length setting often correlates with the dominant cycle length in the market. Start with shorter periods (8-14) for active markets and longer periods (20-30) for smoother, longer-term cycle identification.
## Calculation and Mathematical Foundation
**Simplified explanation:**
The Center of Gravity calculates where the "balance point" would be if each price in the lookback period was treated as a mass at its time position. The result is then normalized to oscillate around zero by subtracting the theoretical center point.
**Technical formula:**
The Center of Gravity is calculated as:
CG = [Σ(i × Price[i-1]) / Σ(Price[i-1])] - (Length + 1) / 2
Where:
* i ranges from 1 to Length (representing position weights)
* Price[i-1] is the price at position i-1 bars ago (current bar when i=1)
* The subtraction of (Length + 1) / 2 centers the oscillator around zero
* This represents the "balance point" where price data would be in equilibrium
The calculation process:
```
numerator = Σ(i × Price[i-1]) for i = 1 to Length
denominator = Σ(Price[i-1]) for i = 1 to Length
raw_cg = numerator / denominator
CG = raw_cg - (Length + 1) / 2
```
> 🔍 **Technical Note:** The algorithm calculates the weighted average position of prices, then subtracts the theoretical center point to create an oscillator. When prices are distributed evenly, CG equals zero. When recent prices dominate, CG becomes positive; when older prices dominate, CG becomes negative.
## Interpretation Details
The Center of Gravity indicator provides several analytical perspectives:
* **Zero-line crossovers:**
* Crossing above zero: Suggests recent prices have more weight (potential upward momentum)
* Crossing below zero: Suggests older prices have more weight (potential downward momentum)
* Multiple crossovers may indicate choppy, non-trending conditions
* **Extreme readings:**
* High positive values: Recent prices significantly outweigh older prices
* High negative values: Older prices significantly outweigh recent prices
* The magnitude indicates the strength of the price distribution bias
* **Divergence analysis:**
* Bullish divergence: Price makes lower lows while CG makes higher lows
* Bearish divergence: Price makes higher highs while CG makes lower highs
* These divergences can indicate potential shifts in price momentum
* **Mean reversion characteristics:**
* CG tends to oscillate around zero over time
* Extreme readings often precede moves back toward the center line
* Can be used to identify potential reversal points
## Limitations and Considerations
* **Market conditions:** Most effective in cyclical markets; may provide less clear signals during strong trending periods
* **Whipsaw potential:** Can generate false signals during low-volatility, range-bound conditions
* **Parameter sensitivity:** Length setting significantly affects responsiveness and noise levels
* **Interpretation complexity:** Requires understanding of the balance point concept for proper interpretation
* **Complementary tools:** Best used with trend identification tools and volume confirmation for optimal results
The Center of Gravity works best when combined with other cycle analysis tools and should be part of a broader trading system that includes trend and momentum confirmation.
## References
* Ehlers, J. F. (2002). *Rocket Science for Traders: Digital Signal Processing Applications*. John Wiley & Sons.
* Ehlers, J. F. (2013). *Cycle Analytics for Traders: Advanced Technical Trading Concepts*. John Wiley & Sons.
+33
View File
@@ -0,0 +1,33 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Center of Gravity (CG)", "CG", overlay=false)
//@function Calculates Ehlers' Center of Gravity indicator
//@param src Series to calculate Center of Gravity from
//@param length Period for the Center of Gravity calculation
//@returns Center of Gravity value identifying cycle turning points
//@optimized for performance and dirty data
cg(series float src, simple int length) =>
if length <= 0
runtime.error("Length must be greater than 0")
float num = 0.0, float den = 0.0
for count = 1 to 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
// ---------- Main loop ----------
// Inputs
i_length = input.int(10, "Length", minval=1, tooltip="Period for Center of Gravity calculation")
i_source = input.source(close, "Source")
// Calculation
cg_value = cg(i_source, i_length)
// Plot
plot(cg_value, "CG", color=color.yellow, linewidth=2)
hline(0, "Zero Line", color.gray, linestyle=hline.style_dashed)
+100
View File
@@ -0,0 +1,100 @@
# DSP: Detrended Synthetic Price
## Overview and Purpose
The Detrended Synthetic Price (DSP) is a cycle analysis indicator developed by John Ehlers that isolates the cyclical component of price action by subtracting a slower-period EMA from a faster-period EMA. Introduced in his work on digital signal processing for traders, DSP creates a band-pass filter effect that removes both long-term trends and short-term noise, revealing the dominant market cycle.
Unlike traditional detrending methods that use high-pass filters, Ehlers' DSP uses the difference between a quarter-cycle EMA and a half-cycle EMA relative to the dominant cycle period. This creates an in-phase output that oscillates around zero, with the amplitude and frequency revealing information about cycle strength and timing. The quarter-cycle smoother responds quickly to price changes while the half-cycle smoother provides the baseline reference, and their difference creates the band-pass effect.
DSP serves as both a standalone cycle indicator and a foundational component for more advanced Ehlers indicators. By isolating the dominant cycle component, it provides a clearer view of market rhythms without the contamination of longer-term trends or higher-frequency noise.
## Core Concepts
* **Dual-EMA Structure:** Uses two independent EMAs at quarter-cycle (P/4) and half-cycle (P/2) periods derived from the dominant cycle
* **Band-Pass Effect:** Quarter-cycle minus half-cycle creates a filter that passes the dominant cycle while attenuating trends and noise
* **In-Phase Output:** The resulting oscillator is in-phase with the dominant cycle, providing clear timing signals
* **Zero-Crossing Analysis:** Oscillations around zero line reveal cycle phase and potential reversal points
* **Cycle Isolation:** Mathematically isolates the periodic component that matches the specified dominant cycle period
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Source | hlc3 | Price data used for calculation | Use `close` for end-of-bar analysis, `hlc3` for balanced price representation |
| Dominant Cycle Period | 40 | Period used to calculate quarter-cycle and half-cycle EMAs | Should match actual market cycle: 20-30 for faster cycles, 40-50 for standard, 60-80 for slower cycles |
**Pro Tip:** The Dominant Cycle Period should ideally be obtained from HT_DCPERIOD or other cycle measurement tools for adaptive behavior. For fixed analysis, 40 bars works well for daily charts (approximates a 2-month cycle). The quarter-cycle EMA (P/4 = 10) responds to short-term moves while the half-cycle EMA (P/2 = 20) provides the baseline, creating the band-pass effect.
## Calculation and Mathematical Foundation
**Simplified explanation:**
DSP calculates two EMAs at periods that are fractions of the dominant cycle (quarter and half), then subtracts the slower from the faster to create an oscillator that isolates the cyclical component.
**Technical formula:**
1. Calculate quarter-cycle and half-cycle periods from dominant cycle:
```
Fast_Period = round(Period / 4)
Slow_Period = round(Period / 2)
```
2. Calculate alpha values for both EMAs:
```
Alpha_Fast = 2 / (Fast_Period + 1)
Alpha_Slow = 2 / (Slow_Period + 1)
```
3. Apply exponential smoothing with warmup compensation:
```
EMA_Fast = EMA(Price, Fast_Period)
EMA_Slow = EMA(Price, Slow_Period)
```
4. Calculate DSP as the difference:
```
DSP = EMA_Fast - EMA_Slow
```
> 🔍 **Technical Note:** The implementation uses unified warmup compensation to ensure both EMAs produce valid outputs from bar 1. The quarter-cycle EMA provides rapid response to price changes while the half-cycle EMA establishes the reference baseline. Their difference creates a band-pass filter centered on the dominant cycle period, effectively removing both low-frequency trends (longer than the cycle) and high-frequency noise (shorter than the cycle).
## Interpretation Details
DSP provides cycle-focused market analysis through the isolated cyclical component:
* **Zero-Line Crossovers:**
* Cross above zero: Cycle entering positive phase, potential bullish swing point
* Cross below zero: Cycle entering negative phase, potential bearish swing point
* Frequency of crossings indicates cycle period accuracy
* **Amplitude Analysis:**
* Larger oscillations: Stronger cycle component, more pronounced market rhythm
* Smaller oscillations: Weaker cycle, market transitioning or range-bound
* Amplitude expansion signals increasing cycle strength
* Amplitude contraction signals decreasing cycle strength
* **Cycle Phase Identification:**
* Peak values: Cycle approaching maximum (consider taking profits on longs)
* Trough values: Cycle approaching minimum (consider taking profits on shorts)
* Rate of change indicates cycle acceleration/deceleration
* Zero crossings mark quarter-cycle phase transitions
* **Trend vs Cycle:**
* Regular oscillations with consistent amplitude: Strong cyclic behavior
* Irregular oscillations or bias to one side: Trend component present
* Dampening oscillations: Cycle weakening, possible trend emergence
* Amplifying oscillations: Cycle strengthening, rhythmic behavior dominant
## Limitations and Considerations
* **Period Dependency:** Effectiveness depends on correct Dominant Cycle Period setting relative to actual market cycles
* **Cycle Variability:** Market cycles are not perfectly periodic; DSP reveals approximate rhythms that can shift over time
* **Trend Sensitivity:** During strong trends, the oscillator may show persistent bias rather than symmetric oscillations
* **Lag Component:** EMAs introduce some lag, though the dual-EMA structure minimizes this compared to single moving averages
* **Requires Cycle Knowledge:** Best results when dominant cycle period is known (use HT_DCPERIOD for adaptive approach)
* **Not Predictive Alone:** Shows current cycle state; combine with other tools for timing and confirmation
## References
* Ehlers, J. F. (2013). *Cycle Analytics for Traders: Advanced Technical Trading Concepts*. Wiley Trading.
* Ehlers, J. F. (2001). *Rocket Science for Traders: Digital Signal Processing Applications*. Wiley Trading.
* Ehlers, J. F. (2004). *Cybernetic Analysis for Stocks and Futures: Cutting-Edge DSP Technology to Improve Your Trading*. Wiley Trading.
+50
View File
@@ -0,0 +1,50 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Detrended Synthetic Price (DSP)", "DSP", overlay=false)
//@function Calculates Detrended Synthetic Price using Ehlers dual-EMA algorithm
//@param source Series to detrend
//@param period Dominant cycle period for quarter/half-cycle EMA calculation
//@returns Detrended synthetic price (difference between quarter-cycle and half-cycle EMAs)
dsp(series float source, simple int period) =>
if period <= 0
runtime.error("Period must be greater than 0")
int fast_period = math.max(2, int(math.round(period / 4.0)))
int slow_period = math.max(3, int(math.round(period / 2.0)))
float alpha_fast = 2.0 / (fast_period + 1)
float alpha_slow = 2.0 / (slow_period + 1)
var float ema_fast_raw = 0.0
var float ema_slow_raw = 0.0
float current = nz(source)
ema_fast_raw += alpha_fast * (current - ema_fast_raw)
ema_slow_raw += alpha_slow * (current - ema_slow_raw)
var bool warmup = true
var float e_fast = 1.0
var float e_slow = 1.0
float ema_fast = ema_fast_raw
float ema_slow = ema_slow_raw
if warmup
e_fast *= (1.0 - alpha_fast)
e_slow *= (1.0 - alpha_slow)
float c_fast = 1.0 / (1.0 - e_fast)
float c_slow = 1.0 / (1.0 - e_slow)
ema_fast := c_fast * ema_fast_raw
ema_slow := c_slow * ema_slow_raw
warmup := e_fast > 1e-10 or e_slow > 1e-10
// Return difference (detrended synthetic price)
ema_fast - ema_slow
// ---------- Main loop ----------
// Inputs
i_source = input.source(hlc3, "Source")
i_period = input.int(40, "Dominant Cycle Period", minval=4, maxval=200, tooltip="Dominant cycle period. Quarter-cycle and half-cycle EMAs calculated from this value.")
// Calculation
dsp_val = dsp(i_source, i_period)
// Plot
plot(dsp_val, "DSP", color=color.yellow, linewidth=2)
hline(0, "Zero Line", color=color.gray, linestyle=hline.style_solid)
+127
View File
@@ -0,0 +1,127 @@
# EACP: Ehlers Autocorrelation Periodogram
## Overview and Purpose
Developed by John F. Ehlers (Technical Analysis of Stocks & Commodities, Sep 2016), the Ehlers Autocorrelation Periodogram (EACP) estimates the dominant market cycle by projecting normalized autocorrelation coefficients onto Fourier basis functions. The indicator blends a roofing filter (high-pass + Super Smoother) with a compact periodogram, yielding low-latency dominant cycle detection suitable for adaptive trading systems. Compared with Hilbert-based methods, the autocorrelation approach resists aliasing and maintains stability in noisy price data.
EACP answers a central question in cycle analysis: “What period currently dominates the market?” It prioritizes spectral power concentration, enabling downstream tools (adaptive moving averages, oscillators) to adjust responsively without the lag present in sliding-window techniques.
## Core Concepts
* **Roofing Filter:** High-pass plus Super Smoother combination removes low-frequency drift while limiting aliasing.
* **Pearson Autocorrelation:** Computes normalized lag correlation to remove amplitude bias.
* **Fourier Projection:** Sums cosine and sine terms of autocorrelation to approximate spectral energy.
* **Gain Normalization:** Automatic gain control prevents stale peaks from dominating power estimates.
* **Warmup Compensation:** Exponential correction guarantees valid output from the very first bar.
## Implementation Notes
**This is not a strict implementation of the TASC September 2016 specification.** It is a more advanced evolution combining the core 2016 concept with techniques Ehlers introduced later. The fundamental Wiener-Khinchin theorem (power spectral density = Fourier transform of autocorrelation) is correctly implemented, but key implementation details differ:
### Differences from Original 2016 TASC Article
1. **Dominant Cycle Calculation:**
* **2016 TASC:** Uses peak-finding to identify the period with maximum power
* **This Implementation:** Uses Center of Gravity (COG) weighted average over bins where power ≥ 0.5
* **Rationale:** COG provides smoother transitions and reduces susceptibility to noise spikes
2. **Roofing Filter:**
* **2016 TASC:** Simple first-order high-pass filter
* **This Implementation:** Canonical 2-pole high-pass with √2 factor followed by Super Smoother bandpass
* **Formula:** `hp := (1-α/2)²·(p-2p[1]+p[2]) + 2(1-α)·hp[1] - (1-α)²·hp[2]`
* **Rationale:** Evolved filtering provides better attenuation and phase characteristics
3. **Normalized Power Reporting:**
* **2016 TASC:** Reports peak power across all periods
* **This Implementation:** Reports power specifically at the dominant period
* **Rationale:** Provides more meaningful correlation between dominant cycle strength and normalized power
4. **Automatic Gain Control (AGC):**
* Uses decay factor `K = 10^(-0.15/diff)` where `diff = maxPeriod - minPeriod`
* Ensures K < 1 for proper exponential decay of historical peaks
* Prevents stale peaks from dominating current power estimates
### Performance Characteristics
* **Complexity:** O(N²) where N = (maxPeriod - minPeriod)
* **Implementation:** Uses `var` arrays with native PineScript historical operator `[offset]`
* **Warmup:** Exponential compensation (§2 pattern) ensures valid output from bar 1
### Related Implementations
This refined approach aligns with:
* TradingView TASC 2025.02 implementation by blackcat1402
* Modern Ehlers cycle analysis techniques post-2016
* Evolved filtering methods from *Cycle Analytics for Traders*
The code is mathematically sound and production-ready, representing a refined version of the autocorrelation periodogram concept rather than a literal translation of the 2016 article.
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Min Period | 8 | Lower bound of candidate cycles | Increase to ignore microstructure noise; decrease for scalping. |
| Max Period | 48 | Upper bound of candidate cycles | Increase for swing analysis; decrease for intraday focus. |
| Autocorrelation Length | 3 | Averaging window for Pearson correlation | Set to 0 to match lag, or enlarge for smoother spectra. |
| Enhance Resolution | true | Cubic emphasis to highlight peaks | Disable when a flatter spectrum is desired for diagnostics. |
**Pro Tip:** Keep `(maxPeriod - minPeriod)` ≤ 64 to control $O(n^2)$ inner loops and maintain responsiveness on lower timeframes.
## Calculation and Mathematical Foundation
**Explanation:**
1. Apply roofing filter to `source` using coefficients $\alpha_1$, $a_1$, $b_1$, $c_1$, $c_2$, $c_3$.
2. For each lag $L$ compute Pearson correlation $r_L$ over window $M$ (default $L$).
3. For each period $p$, project onto Fourier basis:
$C_p=\sum_{n=2}^{N} r_n \cos\left(\frac{2\pi n}{p}\right)$ and $S_p=\sum_{n=2}^{N} r_n \sin\left(\frac{2\pi n}{p}\right)$.
4. Power $P_p=C_p^2+S_p^2$, smoothed then normalized via adaptive peak tracking.
5. Dominant cycle $D=\frac{\sum p\,\tilde P_p}{\sum \tilde P_p}$ over bins where $\tilde P_p≥0.5$, warmup-compensated.
**Technical formula:**
```
Step 1: hp_t = ((1-α₁)/2)(src_t - src_{t-1}) + α₁ hp_{t-1}
Step 2: filt_t = c₁(hp_t + hp_{t-1})/2 + c₂ filt_{t-1} + c₃ filt_{t-2}
Step 3: r_L = (M Σxy - Σx Σy) / √[(M Σx² - (Σx)²)(M Σy² - (Σy)²)]
Step 4: P_p = (Σ_{n=2}^{N} r_n cos(2πn/p))² + (Σ_{n=2}^{N} r_n sin(2πn/p))²
Step 5: D = Σ_{p∈Ω} p · ĤP_p / Σ_{p∈Ω} ĤP_p with warmup compensation
```
> 🔍 **Technical Note:** Warmup uses $c = 1 / (1 - (1 - \alpha)^{k})$ to scale early-cycle estimates, preventing low values during initial bars.
## Interpretation Details
* **Primary Dominant Cycle:**
* High $D$ (e.g., > 30) implies slow regime; adaptive MAs should lengthen.
* Low $D$ (e.g., < 15) signals rapid oscillations; shorten lookback windows.
* **Normalized Power:**
* Values > 0.8 indicate strong cycle confidence; consider cyclical strategies.
* Values < 0.3 warn of flat spectra; favor trend or volatility approaches.
* **Regime Shifts:**
* Rapid drop in $D$ alongside rising power often precedes volatility expansion.
* Divergence between $D$ and price swings may highlight upcoming breakouts.
## Limitations and Considerations
* **Spectral Leakage:** Limited lag range can smear peaks during abrupt volatility shifts.
* **O(n²) Segment:** Although constrained (≤ 60 loops), wide period spans increase computation.
* **Stationarity Assumption:** Autocorrelation presumes quasi-stationary cycles; regime changes reduce accuracy.
* **Latency in Noise:** Even with roofing, extremely noisy assets may require higher `avgLength`.
* **Downtrend Bias:** Negative trends may clip high-pass output; ensure preprocessing retains signal.
## References
* Ehlers, J. F. (2016). “Past Market Cycles.” *Technical Analysis of Stocks & Commodities*, 34(9), 52-55.
* Thinkorswim Learning Center. “Ehlers Autocorrelation Periodogram.”
* Fab MacCallini. “autocorrPeriodogram.R.” GitHub repository.
* QuantStrat TradeR Blog. “Autocorrelation Periodogram for Adaptive Lookbacks.”
* TradingView Script by blackcat1402. “Ehlers Autocorrelation Periodogram (Updated).”
``` mcp
Validation Sources:
Patterns: §2, §3, §7, §21
Wolfram: "Wiener-Khinchin theorem"
External: "Thinkorswim Ehlers Autocorrelation Periodogram","fabmaccallini autocorrPeriodogram","QuantStrat Autocorrelation Periodogram","TradingView blackcat Autocorrelation Periodogram"
API: ref-tools confirmed input.source/int/bool usage, plot defaults
Planning: phases=design,warmup,validation,docs
+145
View File
@@ -0,0 +1,145 @@
// The MIT License (MIT)1
// © mihakralj
//@version=6
indicator("EACP: Ehlers Autocorrelation Periodogram","EACP",overlay=false)
//@function Autocorrelation periodogram dominant cycle estimator
//@param source Price input series
//@param minPeriod Minimum period to evaluate
//@param maxPeriod Maximum period to evaluate
//@param avgLength Averaging length for Pearson correlation (0 uses lag length)
//@param enhance Apply cubic emphasis to highlight dominant peaks
//@returns Smoothed dominant cycle estimate
//@optimized Removed buffer complexity, uses native PineScript historical operator for O(n) correlation
//@validation wolfram:"Wiener-Khinchin theorem","Pearson correlation coefficient" external:"TradingView TASC 2025.02 Autocorrelation","ImmortalFreedom Ehlers ACP","QuantStrat autocorrPeriodogram"
eacp(series float source,simple int minPeriod,simple int maxPeriod,simple int avgLength,simple bool enhance)=>
if minPeriod<3
runtime.error("Min period must be at least 3")
if maxPeriod<=minPeriod
runtime.error("Max period must be greater than min period")
if avgLength<0
runtime.error("Average length must be non-negative")
int size=maxPeriod+1
var array<float> corr=array.new_float(0)
var array<float> power=array.new_float(0)
var array<float> smooth=array.new_float(0)
var int storedSize=0
var int storedMin=0
var int storedMax=0
var bool configured=false
var float hp=0.0
var float filt=0.0
var float dom=0.0
var float domPower=0.0
var float maxPwr=0.0
var float e=1.0
var bool warmup=true
if not configured or storedSize!=size or storedMin!=minPeriod or storedMax!=maxPeriod
corr:=array.new_float(size,0.0)
power:=array.new_float(size,0.0)
smooth:=array.new_float(size,0.0)
storedSize:=size
storedMin:=minPeriod
storedMax:=maxPeriod
configured:=true
hp:=0.0
filt:=0.0
dom:=(minPeriod+maxPeriod)*0.5
domPower:=0.0
maxPwr:=0.0
e:=1.0
warmup:=true
float price=nz(source)
float alphaHP=(math.cos(math.sqrt(2.0)*math.pi/float(maxPeriod))+math.sin(math.sqrt(2.0)*math.pi/float(maxPeriod))-1.0)/math.cos(math.sqrt(2.0)*math.pi/float(maxPeriod))
hp:=math.pow(1.0-alphaHP/2.0,2.0)*(price-2.0*nz(price[1])+nz(price[2]))+2.0*(1.0-alphaHP)*nz(hp[1])-math.pow(1.0-alphaHP,2.0)*nz(hp[2])
float a1=math.exp(-math.sqrt(2.0)*math.pi/float(minPeriod))
float b1=2.0*a1*math.cos(math.sqrt(2.0)*math.pi/float(minPeriod))
float c2=b1
float c3=-(a1*a1)
float c1=1.0-c2-c3
filt:=c1*(hp+nz(hp[1]))*0.5+c2*nz(filt[1])+c3*nz(filt[2])
for lag=0 to maxPeriod
if lag<2
array.set(corr,lag,0.0)
else
int window=avgLength==0?lag:avgLength
if window<2
window:=2
float sx=0.0
float sy=0.0
float sxx=0.0
float syy=0.0
float sxy=0.0
int valid=0
for k=0 to window-1
float x=nz(filt[k])
float y=nz(filt[lag+k])
sx+=x
sy+=y
sxx+=x*x
syy+=y*y
sxy+=x*y
valid+=1
float corrVal=0.0
if valid>1
float denomX=float(valid)*sxx-sx*sx
float denomY=float(valid)*syy-sy*sy
float denom=denomX*denomY
corrVal:=denom>0.0?(float(valid)*sxy-sx*sy)/math.sqrt(denom):0.0
array.set(corr,lag,corrVal)
for period=minPeriod to maxPeriod
float cosAcc=0.0
float sinAcc=0.0
for n=2 to maxPeriod
float corrVal=array.get(corr,n)
float angle=2.0*math.pi*float(n)/float(period)
cosAcc+=corrVal*math.cos(angle)
sinAcc+=corrVal*math.sin(angle)
float sq=cosAcc*cosAcc+sinAcc*sinAcc
array.set(smooth,period,0.2*sq*sq+0.8*array.get(smooth,period))
float localMaxPwr=0.0
for period=minPeriod to maxPeriod
float smoothVal=array.get(smooth,period)
if smoothVal>localMaxPwr
localMaxPwr:=smoothVal
float diff=float(maxPeriod-minPeriod)
float K=diff>0?math.pow(10.0,-0.15/diff):1.0
if localMaxPwr>maxPwr
maxPwr:=localMaxPwr
else
maxPwr:=K*maxPwr
float weighted=0.0
float sumWeight=0.0
float peakPwr=0.0
for period=minPeriod to maxPeriod
float smoothVal=array.get(smooth,period)
float pwr=maxPwr>0.0?smoothVal/maxPwr:0.0
if enhance
pwr:=math.pow(pwr,3.0)
array.set(power,period,pwr)
if pwr>peakPwr
peakPwr:=pwr
if pwr>=0.5
weighted+=float(period)*pwr
sumWeight+=pwr
float base=sumWeight>=0.25?weighted/sumWeight:dom
float alpha=0.2
float beta=1.0-alpha
dom:=alpha*(base-dom)+dom
if warmup
e*=beta
float c=1.0/(1.0-e)
dom:=c*dom
warmup:=e>1e-10
int domIdx=math.min(math.max(int(math.round(dom)),minPeriod),maxPeriod)
domPower:=array.get(power,domIdx)
[dom,domPower]
// ---------- Main loop ----------
i_source=input.source(close,"Source")
i_minPeriod=input.int(8,"Min Period",minval=3,maxval=500)
i_maxPeriod=input.int(48,"Max Period",minval=4,maxval=500)
i_avgLength=input.int(3,"Autocorrelation Length",minval=0,maxval=500)
i_enhance=input.bool(true,"Enhance Resolution")
[dominantCycle,normalizedPower]=eacp(i_source,i_minPeriod,i_maxPeriod,i_avgLength,i_enhance)
plot(dominantCycle,"Dominant Cycle",color=color.yellow,linewidth=2)
plot(normalizedPower,"Normalized Power",color=color.orange,linewidth=2)
+76
View File
@@ -0,0 +1,76 @@
# EBSW: Ehlers Even Better Sinewave
## Overview and Purpose
The Ehlers Even Better Sinewave (EBSW) indicator, developed by John Ehlers, is an advanced cycle analysis tool. This implementation is based on a common interpretation that uses a cascade of filters: first, a High-Pass Filter (HPF) to detrend price data, followed by a Super Smoother Filter (SSF) to isolate the dominant cycle. The resulting filtered wave is then normalized using an Automatic Gain Control (AGC) mechanism, producing a bounded oscillator that fluctuates between approximately +1 and -1. It aims to provide a clear and responsive measure of market cycles.
## Core Concepts
* **Detrending (High-Pass Filter):** A 1-pole High-Pass Filter removes the longer-term trend component from the price data, allowing the indicator to focus on cyclical movements.
* **Cycle Smoothing (Super Smoother Filter):** Ehlers' Super Smoother Filter is applied to the detrended data to further refine the cycle component, offering effective smoothing with relatively low lag.
* **Wave Generation:** The output of the SSF is averaged over a short period (typically 3 bars) to create the primary "wave".
* **Automatic Gain Control (AGC):** The wave's amplitude is normalized by dividing it by the square root of its recent power (average of squared values). This keeps the oscillator bounded and responsive to changes in volatility.
* **Normalized Oscillator:** The final output is a single sinewave-like oscillator.
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| --------- | ------- | -------- | -------------- |
| Source | close | Price data used for calculation. | Typically `close`, but `hlc3` or `ohlc4` can be used for a more comprehensive price representation. |
| HP Length | 40 | Lookback period for the 1-pole High-Pass Filter used for detrending. | Shorter periods make the filter more responsive to shorter cycles; longer periods focus on longer-term cycles. Adjust based on observed cycle characteristics. |
| SSF Length | 10 | Lookback period for the Super Smoother Filter used for smoothing the detrended cycle component. | Shorter periods result in a more responsive (but potentially noisier) wave; longer periods provide more smoothing. |
**Pro Tip:** The `HP Length` and `SSF Length` parameters should be tuned based on the typical cycle lengths observed in the market and the desired responsiveness of the indicator.
## Calculation and Mathematical Foundation
**Simplified explanation:**
1. Remove the trend from the price data using a 1-pole High-Pass Filter.
2. Smooth the detrended data using a Super Smoother Filter to get a clean cycle component.
3. Average the output of the Super Smoother Filter over the last 3 bars to create a "Wave".
4. Calculate the average "Power" of the Super Smoother Filter output over the last 3 bars.
5. Normalize the "Wave" by dividing it by the square root of the "Power" to get the final EBSW value.
**Technical formula (conceptual):**
1. **High-Pass Filter (HPF - 1-pole):**
`angle_hp = 2 * PI / hpLength`
`alpha1_hp = (1 - sin(angle_hp)) / cos(angle_hp)`
`HP = (0.5 * (1 + alpha1_hp) * (src - src[1])) + alpha1_hp * HP[1]`
2. **Super Smoother Filter (SSF):**
`angle_ssf = sqrt(2) * PI / ssfLength`
`alpha2_ssf = exp(-angle_ssf)`
`beta_ssf = 2 * alpha2_ssf * cos(angle_ssf)`
`c2 = beta_ssf`
`c3 = -alpha2_ssf^2`
`c1 = 1 - c2 - c3`
`Filt = c1 * (HP + HP[1])/2 + c2*Filt[1] + c3*Filt[2]`
3. **Wave Generation:**
`WaveVal = (Filt + Filt[1] + Filt[2]) / 3`
4. **Power & Automatic Gain Control (AGC):**
`Pwr = (Filt^2 + Filt[1]^2 + Filt[2]^2) / 3`
`EBSW_SineWave = WaveVal / sqrt(Pwr)` (with check for Pwr == 0)
> 🔍 **Technical Note:** The combination of HPF and SSF creates a form of band-pass filter. The AGC mechanism ensures the output remains scaled, typically between -1 and +1, making it behave like a normalized oscillator.
## Interpretation Details
* **Cycle Identification:** The EBSW wave shows the current phase and strength of the dominant market cycle as filtered by the indicator. Peaks suggest cycle tops, and troughs suggest cycle bottoms.
* **Trend Reversals/Momentum Shifts:** When the EBSW wave crosses the zero line, it can indicate a potential shift in the short-term cyclical momentum.
* Crossing up through zero: Potential start of a bullish cyclical phase.
* Crossing down through zero: Potential start of a bearish cyclical phase.
* **Overbought/Oversold Levels:** While normalized, traders often establish subjective or statistically derived overbought/oversold levels (e.g., +0.85 and -0.85, or other values like +0.7, +0.9).
* Reaching above the overbought level and turning down may signal a potential cyclical peak.
* Falling below the oversold level and turning up may signal a potential cyclical trough.
## Limitations and Considerations
* **Parameter Sensitivity:** The indicator's performance depends on tuning `hpLength` and `ssfLength` to prevailing market conditions.
* **Non-Stationary Markets:** In strongly trending markets with weak cyclical components, or in very choppy non-cyclical conditions, the EBSW may produce less reliable signals.
* **Lag:** All filtering introduces some lag. The Super Smoother Filter is designed to minimize this for its degree of smoothing, but lag is still present.
* **Whipsaws:** Rapid oscillations around the zero line can occur in volatile or directionless markets.
* **Requires Confirmation:** Signals from EBSW are often best confirmed with other forms of technical analysis (e.g., price action, volume, other non-correlated indicators).
## References
* Ehlers, J. F. (2002). *Rocket Science for Traders: Digital Signal Processing Applications*. John Wiley & Sons.
* Ehlers, J. F. (2013). *Cycle Analytics for Traders: Advanced Technical Trading Concepts*. John Wiley & Sons.
+43
View File
@@ -0,0 +1,43 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Ehlers Even Better Sinewave (EBSW)", "EBSW", overlay=false)
//@function Calculates Ehlers Even Better Sinewave using HPF, SSF, and AGC
//@param src Series to calculate EBSW from
//@param hpLength int Period for the High-Pass Filter
//@param ssfLength int Period for the Super Smoother Filter
//@returns single normalized sinewave value
//@optimized for performance and dirty data
ebsw(series float src, simple int hpLength, simple int ssfLength) =>
if hpLength <= 0 or ssfLength <= 0
runtime.error("Periods must be greater than 0")
float pi = 2 * math.asin(1)
float angle_hp = 2 * pi / hpLength
float alpha1_hp = (1 - math.sin(angle_hp)) / math.cos(angle_hp)
var float hp = 0.0
hp := (0.5 * (1 + alpha1_hp) * (src - nz(src[1]))) + (alpha1_hp * nz(hp[1]))
float angle_ssf = math.sqrt(2) * pi / ssfLength
float alpha2_ssf = math.exp(-angle_ssf)
float beta_ssf = 2 * alpha2_ssf * math.cos(angle_ssf)
float c2 = beta_ssf, c3 = -alpha2_ssf * alpha2_ssf, c1 = 1 - c2 - c3
var float filt = 0.0
filt := c1 * ((hp + nz(hp[1])) / 2) + c2 * nz(filt[1]) + c3 * nz(filt[2])
float waveVal = (filt + nz(filt[1]) + nz(filt[2])) / 3.0
float pwr = (math.pow(filt, 2) + math.pow(nz(filt[1]), 2) + math.pow(nz(filt[2]), 2)) / 3.0
float sineWave = pwr == 0 ? 0 : waveVal / math.sqrt(pwr)
math.min(1, math.max(-1, sineWave))
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_hpLength = input.int(40, "High-Pass Filter Length", minval=1, tooltip="Period for detrending the price data.")
i_ssfLength = input.int(10, "Super Smoother Filter Length", minval=1, tooltip="Period for smoothing the cycle component.")
// Calculation
ebsw_wave = ebsw(i_source, i_hpLength, i_ssfLength)
// Plot
plot(ebsw_wave, "EBSW", color=color.yellow, linewidth=2)
hline(0, "Zero Line", color.gray, linestyle=hline.style_dashed)
+124
View File
@@ -0,0 +1,124 @@
# HOMOD: Homodyne Discriminator Dominant Cycle
## Overview and Purpose
The Homodyne Discriminator (HOMOD) is a cycle measurement technique introduced by John F. Ehlers in *Rocket Science for Traders* (2001) and expanded in the November 2000 *Traders Tips* column. It applies a Hilbert Transform framework to detect the instantaneous dominant cycle present in price data while minimizing lag.
Unlike fixed-length filters, HOMOD continuously adapts to current market rhythm by converting the in-phase and quadrature components into a complex phasor pair, multiplying them homodynally, and extracting period information from the resulting phase angle. This makes it ideal for adaptive indicators and systems requiring dynamic lookback lengths.
## Core Concepts
* **Homodyne Multiplication:** Complex multiply of current and prior phasors to isolate instantaneous frequency
* **Hilbert FIR Kernel:** Ehlers 0.0962/0.5769 coefficients producing 90° phase shift with minimal distortion
* **Quadrature Rotation:** Phase-advanced components (jI, jQ) enabling orthogonal phasor construction
* **Cycle Clamping:** Limiting detected periods to realistic bounds (default 650 bars)
* **Warmup Compensation:** Exponential correction ensuring stable output from bar one
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Source | hlc3 | Input series analyzed for cycle period | Switch to close for end-of-day signals or to custom synthetic blends |
| Min Period | 6 | Lower bound for detected cycle length | Increase to ignore ultrashort noise-dominated cycles |
| Max Period | 50 | Upper bound for detected cycle length | Raise for weekly/monthly studies; lower for intraday scalping |
**Pro Tip:** Align downstream indicators (e.g., RSI, moving averages) to the live HOMOD period by rounding to the nearest integer—this maintains resonance with the markets dominant rhythm.
## Calculation and Mathematical Foundation
**Explanation:**
HOMOD smooths price, applies a Hilbert Transform to obtain in-phase (I) and quadrature (Q) components, rotates them by 90°, forms phasors, multiplies each phasor by its predecessor, and derives period length from the resulting phase angle. Subsequent smoothing and clamping stabilize measurements.
**Technical formula:**
1. **Weighted smoothing and detrending**
$$
SmoothPrice_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}
$$
$$
Detrender_t = \left(0.0962\,SP_t + 0.5769\,SP_{t-2} - 0.5769\,SP_{t-4} - 0.0962\,SP_{t-6}\right)\cdot B_t
$$
where $B_t = 0.075\cdot Period_{t-1} + 0.54$.
2. **Quadrature pair and phase advance**
$$
Q1_t = (0.0962\,Det_t + 0.5769\,Det_{t-2} - 0.5769\,Det_{t-4} - 0.0962\,Det_{t-6})\cdot B_t
$$
$$
I1_t = Det_{t-3}
$$
$$
jI_t = (0.0962\,I1_t + 0.5769\,I1_{t-2} - 0.5769\,I1_{t-4} - 0.0962\,I1_{t-6})\cdot B_t
$$
$$
jQ_t = (0.0962\,Q1_t + 0.5769\,Q1_{t-2} - 0.5769\,Q1_{t-4} - 0.0962\,Q1_{t-6})\cdot B_t
$$
3. **Phasor construction**
$$
I2_t = 0.2\,(I1_t - jQ_t) + 0.8\,I2_{t-1},\quad Q2_t = 0.2\,(Q1_t + jI_t) + 0.8\,Q2_{t-1}
$$
4. **Homodyne product and smoothing**
$$
Re_t = 0.2\,(I2_t I2_{t-1} + Q2_t Q2_{t-1}) + 0.8\,Re_{t-1}
$$
$$
Im_t = 0.2\,(I2_t Q2_{t-1} - Q2_t I2_{t-1}) + 0.8\,Im_{t-1}
$$
5. **Period extraction, clamp, warmup**
$$
\theta_t = \operatorname{atan2}(Im_t, Re_t)
$$
$$
Period^\*_{t} = \frac{2\pi}{\theta_t}
$$
$$
Period_t = \operatorname{clip}(|Period^\*_t|,\ Min,\ Max)
$$
$$
SmoothPeriod_t = SmoothPeriod_{t-1} + 0.33\,(Period_t - SmoothPeriod_{t-1})
$$
## Interpretation Details
* **Cycle Tracking**
* 612 bars: fast oscillatory regimes suited to scalping and short-term countertrend trades
* 1230 bars: medium cycles aligning with swing-trading horizons
* 3060 bars: slow cycles highlighting macro rhythm or trend exhaustion zones
* **Adaptive Parameterization**
* Use rounded SmoothPeriod as the lookback for RSI, stochastic, ATR channels, etc.
* Match moving-average lengths to maintain coherence between filters and underlying price rhythm.
* **Regime Analysis**
* Stable plateau in period → consistent cycle regime
* Rising period → trend elongation or consolidation broadening
* Falling period → volatility expansion, choppy markets, or nascent rotational phases
## Limitations and Considerations
* **Warmup Demand:** Requires ~60 bars for fully stable phasor history; early readings should be treated cautiously
* **Trend Dominance:** Persistent directional moves degrade cycle definition, causing erratic period swings
* **Noise Sensitivity:** Despite smoothing, extremely noisy instruments may oscillate near Min Period consistently
* **Clamp Bias:** Hard limits prevent detection of cycles outside bounds; adjust for instruments with known longer rhythms
* **Computational Intensity:** Multiple FIR taps and state variables raise per-bar workload versus simpler averages
## References
* Ehlers, J. F. (2001). *Rocket Science for Traders: Digital Signal Processing Applications*. Wiley.
* Ehlers, J. F. (2000). *Traders Tips Homodyne Discriminator*. *Technical Analysis of Stocks & Commodities*.
* blackcat1402. (2023). *Ehlers Homodyne Discriminator Period Measurer* (TradingView script).
* MrTools. (2025). *Homodyne Discriminator.mq4*. Forex-Station Forums.
* Mladen. (2019). *Adaptive Lookback Indicators Homodyne Update*. MQL5 Forums.
* 3Jane. (2024). *tindicators hd.cc Implementation*. GitHub.
## Validation Sources
```mcp
Validation Sources:
Patterns: §2, §6, §7, §16, §17, §18, §19
Wolfram: "atan2(y,x)"
External: "TradingView Homodyne Discriminator","Forex-Station Homodyne Discriminator","MQL5 Adaptive Lookback Homodyne","tindicators hd.cc"
Planning: phases=function,main_loop,docs,index
+90
View File
@@ -0,0 +1,90 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("HOMOD: Homodyne Discriminator Dominant Cycle","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
float alpha=0.33
smooth_period:=smooth_period+alpha*(period-smooth_period)
float result=smooth_period
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)
+126
View File
@@ -0,0 +1,126 @@
# HT_DCPERIOD: Hilbert Transform Dominant Cycle Period
[Pine Script Implementation of HT_DCPERIOD](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ht_dcperiod.pine)
## Overview and Purpose
The Hilbert Transform Dominant Cycle Period (HT_DCPERIOD) is an advanced signal processing indicator developed by John Ehlers that identifies the dominant cycle length in price data. Published in his book "Cycle Analytics for Traders" (2013), this indicator uses the Hilbert Transform mathematical technique to detect the current market cycle period in real-time, typically ranging from 6 to 50 bars.
Unlike traditional cycle detection methods that rely on fixed periods, HT_DCPERIOD adapts to changing market conditions by continuously measuring the actual cycle length present in the price data. This adaptive capability makes it invaluable for optimizing other technical indicators and determining appropriate lookback periods for trading systems.
## Core Concepts
* **Hilbert Transform**: A mathematical operation that shifts the phase of a signal by 90 degrees, enabling the separation of trending and cycling components in price data
* **InPhase and Quadrature Components**: Two phase-shifted versions of the price signal that, when combined, reveal the cycle period through their phase relationship
* **Detrending**: Removal of the trending component from price data to isolate the cyclical component for accurate period measurement
* **Adaptive Smoothing**: Dynamic adjustment of smoothing factors based on the detected cycle period to reduce noise while maintaining responsiveness
* **Median Filtering**: Use of a 5-bar moving median to smooth the period output and eliminate outliers caused by market noise
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Source | hlc3 | Price data to analyze | Use close for end-of-bar signals, hlc3 for intrabar smoothing |
**Pro Tip:** The indicator automatically adapts to any timeframe. On daily charts, a period of 20 indicates a 20-day cycle (about one month). On hourly charts, 20 indicates a 20-hour cycle. Consider the timeframe when interpreting the cycle length - what matters is the number of bars, not calendar time.
## Calculation and Mathematical Foundation
**Simplified explanation:**
HT_DCPERIOD uses digital signal processing to transform price data into two phase-shifted components (InPhase and Quadrature), then calculates the cycle period from the phase angle between them.
**Technical formula:**
1. **Smooth the price** to reduce high-frequency noise:
```
SmoothPrice = (4×Price + 3×Price[1] + 2×Price[2] + Price[3]) / 10
```
2. **Detrend the smoothed price** using a Hilbert Transform finite impulse response filter:
```
Detrender = (0.0962×SP + 0.5769×SP[2] - 0.5769×SP[4] - 0.0962×SP[6]) × (0.075×Period[1] + 0.54)
```
3. **Compute InPhase (I1) and Quadrature (Q1) components**:
```
Q1 = (0.0962×DT + 0.5769×DT[2] - 0.5769×DT[4] - 0.0962×DT[6]) × (0.075×Period[1] + 0.54)
I1 = Detrender[3]
```
4. **Advance the phase** of I1 and Q1 by 90 degrees (jI and jQ):
```
jI = (0.0962×I1 + 0.5769×I1[2] - 0.5769×I1[4] - 0.0962×I1[6]) × (0.075×Period[1] + 0.54)
jQ = (0.0962×Q1 + 0.5769×Q1[2] - 0.5769×Q1[4] - 0.0962×Q1[6]) × (0.075×Period[1] + 0.54)
```
5. **Create phasor components I2 and Q2**:
```
I2 = I1 - jQ
Q2 = Q1 + jI
Smooth I2 and Q2 with: Value = 0.2×Value + 0.8×Value[1]
```
6. **Calculate Real and Imaginary components**:
```
Re = I2×I2[1] + Q2×Q2[1]
Im = I2×Q2[1] - Q2×I2[1]
Smooth Re and Im with: Value = 0.2×Value + 0.8×Value[1]
```
7. **Compute cycle period from phase angle**:
```
Period = 2π / arctan(Im / Re)
Clamp: Period = max(6, min(50, Period))
Smooth: Period = 0.2×Period + 0.8×Period[1]
```
8. **Apply exponential smoothing** to final period output:
```
SmoothPeriod = 0.2×Period + 0.8×SmoothPeriod[1]
```
> 🔍 **Technical Note:** The adaptive smoothing factor (0.075×Period[1] + 0.54) in the Hilbert Transform filters adjusts the bandwidth based on the current cycle period, ensuring optimal frequency response across different market cycles. The exponential smoothing (alpha=0.2) balances responsiveness with stability while maintaining Ehlers' original algorithm design.
## Interpretation Details
HT_DCPERIOD provides real-time cycle analysis with multiple applications:
* **Cycle Length Identification:**
* Values 6-15 bars: Short-term cycles, fast market movements
* Values 15-30 bars: Medium-term cycles, typical trading ranges
* Values 30-50 bars: Long-term cycles, slower trending movements
* Stable values indicate consistent cycling behavior
* Rapidly changing values suggest transitional or chaotic market conditions
* **Indicator Optimization:**
* Use detected period as lookback length for other indicators
* Example: If HT_DCPERIOD = 20, use 20-period RSI, 20-period moving averages
* Automatically adapts indicators to current market rhythm
* Improves timing and reduces false signals
* **Market State Assessment:**
* Stable, consistent period readings: Market in well-defined cycle
* Increasing period length: Market entering longer-term trend or consolidation
* Decreasing period length: Market becoming more volatile or choppy
* Erratic period changes: Transitional phase, trend/cycle mode shift
* **Trading System Adaptation:**
* Short cycles (6-15): Use faster indicators, shorter stops, quicker exits
* Medium cycles (15-30): Standard trading approaches work well
* Long cycles (30-50): Use wider stops, longer holding periods, trend-following strategies
## Limitations and Considerations
* **Initialization Period**: Requires approximately 50-60 bars of data before producing stable readings due to the multiple stages of filtering and smoothing
* **Lag Component**: The extensive smoothing needed for stability introduces some lag, meaning detected periods reflect recent rather than current cycle length
* **Range Limitations**: Clamped to 6-50 bars, so cannot detect very short (< 6) or very long (> 50) cycles, which may be present in some markets
* **Trending Markets**: During strong trends with minimal cyclical component, the indicator may produce unstable or meaningless readings as it attempts to find cycles where none exist
* **Complementary Use**: Best used in conjunction with trend-following indicators (like HT_TRENDMODE) to determine when cycle analysis is appropriate vs when trend analysis is more suitable
* **Parameter Sensitivity**: The Ehlers algorithm uses specific mathematical constants that work well for most markets but may not be optimal for all instruments or timeframes
## References
* Ehlers, J. F. (2013). *Cycle Analytics for Traders: Advanced Technical Trading Concepts*. Wiley Trading.
* Ehlers, J. F. (2001). *Rocket Science for Traders: Digital Signal Processing Applications*. Wiley Trading.
* TA-Lib Technical Analysis Library - HT_DCPERIOD implementation
* Mesa Software - MESA Cycle (similar methodology)
+78
View File
@@ -0,0 +1,78 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("HT_DCPERIOD: Hilbert Transform Dominant Cycle Period", "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
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ht_dcperiod.md
//@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
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
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
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
period := math.max(6.0, math.min(50.0, period))
smooth_period := 0.33 * period + 0.67 * smooth_period
smooth_period
// ---------- Main loop ----------
// Inputs
i_source = input.source(hlc3, "Source")
// Calculation
dcperiod = ht_dcperiod(i_source)
// Plot
plot(dcperiod, "Dominant Cycle Period", color=color.yellow, linewidth=2)
hline(15, "Short Cycle", color=color.new(color.gray, 70), linestyle=hline.style_dashed)
hline(30, "Long Cycle", color=color.new(color.gray, 70), linestyle=hline.style_dashed)
+123
View File
@@ -0,0 +1,123 @@
# HT_DCPHASE: Hilbert Transform - Dominant Cycle Phase
[Pine Script Implementation of HT_DCPHASE](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ht_dcphase.pine)
## Overview and Purpose
The Hilbert Transform Dominant Cycle Phase (HT_DCPHASE) is an advanced cycle analysis indicator developed by John Ehlers that identifies the current phase position within the dominant market cycle. By applying Hilbert Transform mathematics to price data, this indicator extracts the phase angle of the dominant cycle, revealing where the market currently sits within its cyclical pattern. This information is invaluable for timing entries and exits, as it shows whether the cycle is in accumulation, markup, distribution, or markdown phases.
HT_DCPHASE works by computing the In-phase (I) and Quadrature (Q) components through Hilbert Transform analysis, then calculating the phase angle as the arctangent of Q/I. The result is a continuous phase measurement in radians ranging from -π to π, providing a precise indication of cycle position. This makes it particularly useful for identifying cycle turning points and anticipating trend changes before they become apparent in price action.
## Core Concepts
* **Phase Angle**: Measures position within cycle using arctangent of Q/I components; ranges from -π to π radians
* **Hilbert Transform**: Mathematical technique that creates 90-degree phase-shifted version of price for quadrature analysis
* **I and Q Components**: In-phase and Quadrature components represent cycle's position in two-dimensional phase space
* **Cycle Position**: Phase angle indicates whether market is in trough (-π), peak (0), or transition phases (±π/2)
* **Adaptive Bandwidth**: Uses dominant cycle period to adjust filter bandwidth for optimal detrending
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Source | hlc3 | Price data for analysis | Use close for simpler signals; hlc3 for smoother, more comprehensive cycle detection |
**Pro Tip:** HT_DCPHASE is most effective when used in conjunction with HT_DCPERIOD to understand both the cycle length and current position. Phase crossings through zero often correspond to significant trend changes. The indicator works best on instruments with clear cyclical behavior - sideways or ranging markets provide cleaner signals than strongly trending markets.
## Calculation and Mathematical Foundation
**Simplified explanation:**
HT_DCPHASE applies Hilbert Transform mathematics to extract the phase angle of the dominant market cycle, indicating the current position within the cycle.
**Technical formula:**
1. Smooth the price data:
```
SmoothPrice = (4×Price + 3×Price[1] + 2×Price[2] + Price[3]) / 10
```
2. Detrend with adaptive bandwidth:
```
Bandwidth = 0.075 × Period[1] + 0.54
Detrender = Hilbert_FIR(SmoothPrice) × Bandwidth
```
3. Calculate Quadrature component (90° phase shift):
```
Q1 = Hilbert_FIR(Detrender) × Bandwidth
```
4. Calculate In-phase component (delayed detrend):
```
I1 = Detrender[3]
```
5. Apply Hilbert Transform to get jI and jQ:
```
jI = Hilbert_FIR(I1) × Bandwidth
jQ = Hilbert_FIR(Q1) × Bandwidth
```
6. Compute smoothed I2 and Q2:
```
I2 = I1 - jQ
Q2 = Q1 + jI
I2 = 0.2×I2 + 0.8×I2[1] (smooth)
Q2 = 0.2×Q2 + 0.8×Q2[1] (smooth)
```
7. Calculate phase angle:
```
Phase = atan(Q2 / I2)
```
Where `Hilbert_FIR` is a finite impulse response filter with coefficients [0.0962, 0.5769, 0, -0.5769, -0.0962].
> 🔍 **Technical Note:** The phase calculation uses arctangent to convert the I and Q components from Cartesian to polar coordinates. The dominant cycle period (calculated from Re and Im) is used to adapt the filter bandwidth, ensuring the phase measurement tracks the actual market cycle rather than noise or shorter-term fluctuations.
## Interpretation Details
HT_DCPHASE provides cycle phase analysis through several interpretive lenses:
* **Phase Position:**
* Phase ≈ -π: Cycle trough (potential buy zone)
* Phase ≈ -π/2: Rising from trough (early uptrend)
* Phase ≈ 0: Cycle peak (potential sell zone)
* Phase ≈ π/2: Declining from peak (early downtrend)
* **Phase Levels:**
* Phase = 0: Cycle peak reached (distribution zone)
* Phase = ±π: Cycle trough reached (accumulation zone)
* Phase transitions through these levels indicate cycle progression
* Watch for price behavior at these phase extremes
* **Phase Velocity:**
* Rapid phase changes indicate strong momentum
* Slow phase progression suggests consolidation
* Stalled phase can indicate cycle transition or mode change
* **Cycle Synchronization:**
* Use with HT_DCPERIOD to confirm cycle consistency
* Phase leads price by design, providing early signals
* Most reliable in ranging or cyclical market conditions
* **Quadrant Analysis:**
* Quadrant I (0 to π/2): Early decline phase
* Quadrant II (π/2 to π): Late decline phase
* Quadrant III (-π to -π/2): Late rise phase
* Quadrant IV (-π/2 to 0): Early rise phase
## Limitations and Considerations
* **Trend Dependence:** Less reliable in strong trending markets; works best in cyclical or ranging conditions
* **Phase Wrapping:** Discontinuities at ±π boundaries require careful interpretation of phase transitions
* **Lag Component:** Smoothing introduces slight lag; phase leads price but not instantaneously
* **Noise Sensitivity:** Can produce erratic signals in highly volatile or choppy markets without clear cycles
* **Cycle Assumption:** Assumes presence of dominant cycle; may give spurious signals in random walk conditions
* **Parameter Adaptation:** Uses previous period for bandwidth calculation; may lag during rapid cycle changes
## References
* Ehlers, J. F. (2004). "Cybernetic Analysis for Stocks and Futures." John Wiley & Sons.
* Ehlers, J. F. (2001). "Rocket Science for Traders: Digital Signal Processing Applications." John Wiley & Sons.
* Ehlers, J. F. (2013). "Cycle Analytics for Traders: Advanced Technical Trading Concepts." John Wiley & Sons.
+82
View File
@@ -0,0 +1,82 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("HT_DCPHASE: Hilbert Transform Dominant Cycle Phase", "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
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ht_dcphase.md
//@param source Series to analyze for dominant cycle phase
//@returns Phase angle in radians (-π to π)
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
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 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
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
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
period := math.max(6.0, math.min(50.0, period))
smooth_period := 0.33 * period + 0.67 * smooth_period
if i2 != 0.0 or q2 != 0.0
phase := atan2(q2, i2)
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(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)
+125
View File
@@ -0,0 +1,125 @@
# HT_PHASOR: Hilbert Transform - Phasor Components
[Pine Script Implementation of HT_PHASOR](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ht_phasor.pine)
## Overview and Purpose
The Hilbert Transform Phasor Components (HT_PHASOR) is an advanced cycle analysis indicator developed by John Ehlers that provides direct access to the In-phase (I) and Quadrature (Q) components of the dominant market cycle. Unlike HT_DCPHASE which derives the phase angle from these components, HT_PHASOR exposes the raw I and Q values themselves, allowing traders and analysts to construct custom cycle indicators or perform advanced signal processing techniques.
The phasor components represent the cycle in two-dimensional phase space, where the I component is the detrended price delayed by a quarter cycle, and the Q component is a 90-degree phase-shifted version of the detrended price. Together, these components form a complex phasor that rotates through phase space as the market cycles, with the magnitude representing cycle amplitude and the angle representing phase position. This dual representation is invaluable for understanding both the strength and position of market cycles.
## Core Concepts
* **In-Phase Component (I)**: The detrended price delayed by quarter cycle; represents the "real" part of the cycle phasor
* **Quadrature Component (Q)**: 90-degree phase-shifted detrended price; represents the "imaginary" part of the cycle phasor
* **Phasor Representation**: I and Q together form a rotating vector in 2D phase space tracking cycle evolution
* **Complex Analysis**: Enables computation of amplitude (√(I²+Q²)), phase (atan2(Q,I)), and frequency
* **Adaptive Processing**: Uses dominant cycle period to adjust bandwidth for optimal component extraction
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Source | hlc3 | Price data for analysis | Use close for simpler signals; hlc3 for smoother, more comprehensive cycle detection |
**Pro Tip:** HT_PHASOR is primarily useful for custom indicator development and advanced cycle analysis. The I and Q components can be used to calculate amplitude (cycle strength), phase (cycle position), and instantaneous frequency. When I and Q oscillate with constant magnitude, the market is in a strong cyclical mode. When their magnitudes vary significantly, the market may be transitioning between cycle and trend modes.
## Calculation and Mathematical Foundation
**Simplified explanation:**
HT_PHASOR applies Hilbert Transform mathematics to extract the In-phase and Quadrature components, which represent the dominant cycle as a rotating vector in 2D phase space.
**Technical formula:**
1. Smooth the price data:
```
SmoothPrice = (4×Price + 3×Price[1] + 2×Price[2] + Price[3]) / 10
```
2. Detrend with adaptive bandwidth:
```
Bandwidth = 0.075 × Period[1] + 0.54
Detrender = Hilbert_FIR(SmoothPrice) × Bandwidth
```
3. Calculate Quadrature component (90° phase shift):
```
Q1 = Hilbert_FIR(Detrender) × Bandwidth
```
4. Calculate In-phase component (delayed detrend):
```
I1 = Detrender[3]
```
5. Apply Hilbert Transform to get jI and jQ:
```
jI = Hilbert_FIR(I1) × Bandwidth
jQ = Hilbert_FIR(Q1) × Bandwidth
```
6. Compute smoothed I2 and Q2:
```
I2 = I1 - jQ
Q2 = Q1 + jI
I2 = 0.2×I2 + 0.8×I2[1] (smooth)
Q2 = 0.2×Q2 + 0.8×Q2[1] (smooth)
```
7. Return both components:
```
return [I2, Q2]
```
Where `Hilbert_FIR` is a finite impulse response filter with coefficients [0.0962, 0.5769, 0, -0.5769, -0.0962].
> 🔍 **Technical Note:** The I and Q components form a complex number representation of the cycle. The dominant cycle period is calculated internally and used to adapt the bandwidth, but the phasor components themselves are the primary output. These can be used to derive amplitude (magnitude = √(I²+Q²)), phase (angle = atan2(Q,I)), and rate of change of phase (instantaneous frequency).
## Interpretation Details
HT_PHASOR provides direct access to cycle components for advanced analysis:
* **Component Oscillation:**
* Both I and Q oscillate around zero
* Amplitude of oscillation indicates cycle strength
* Regular sinusoidal patterns indicate clean cycles
* Irregular patterns suggest trending or transitional periods
* **Phasor Magnitude (√(I²+Q²)):**
* Large magnitude: Strong cyclical behavior
* Small magnitude: Weak cycle or trending phase
* Constant magnitude: Pure cycle mode
* Varying magnitude: Mixed cycle/trend mode
* **Phase Angle (atan2(Q,I)):**
* Derived phase ranges from -π to π
* Constant rotation rate indicates steady cycle
* Accelerating rotation suggests cycle compression
* Decelerating rotation suggests cycle expansion
* **Component Relationships:**
* I and Q approximately 90° out of phase in clean cycles
* Loss of quadrature relationship indicates trend dominance
* Relative magnitudes reveal cycle shape distortions
* Sign changes indicate cycle progression through quadrants
* **Custom Indicator Construction:**
* Amplitude: `sqrt(I² + Q²)` for cycle strength
* Phase: `atan2(Q, I)` for cycle position
* Frequency: Rate of change of phase angle
* Power: `I² + Q²` for energy without sqrt overhead
## Limitations and Considerations
* **Raw Components:** Less intuitive than derived metrics (phase, amplitude); requires understanding of complex analysis
* **Trend Dependence:** Component values less meaningful in strong trending markets
* **Computation Required:** User must compute derived metrics (amplitude, phase) from I and Q components
* **Noise Sensitivity:** Can show erratic behavior in choppy markets without clear cycles
* **Cycle Assumption:** Assumes dominant cycle exists; questionable in random walk conditions
* **Advanced Tool:** Primarily for custom indicator development and algorithmic trading applications
## References
* Ehlers, J. F. (2004). "Cybernetic Analysis for Stocks and Futures." John Wiley & Sons.
* Ehlers, J. F. (2001). "Rocket Science for Traders: Digital Signal Processing Applications." John Wiley & Sons.
* Ehlers, J. F. (2013). "Cycle Analytics for Traders: Advanced Technical Trading Concepts." John Wiley & Sons.
+78
View File
@@ -0,0 +1,78 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("HT_PHASOR: Hilbert Transform Phasor Components", "HT_PHASOR", 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 Phasor Components (InPhase and Quadrature)
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ht_phasor.md
//@param source Series to analyze for phasor components
//@returns Tuple [inphase, quadrature] components
ht_phasor(series float source) =>
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
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
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
period := math.max(6.0, math.min(50.0, period))
smooth_period := 0.33 * period + 0.67 * smooth_period
[i2, q2]
// ---------- 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)
+138
View File
@@ -0,0 +1,138 @@
# HT_SINE: Hilbert Transform - SineWave
[Pine Script Implementation of HT_SINE](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ht_sine.pine)
## Overview and Purpose
The Hilbert Transform SineWave (HT_SINE) is a cycle visualization indicator developed by John Ehlers that generates sine and lead-sine wave plots based on the dominant market cycle identified through Hilbert Transform analysis. Unlike simple sine wave indicators that assume a fixed cycle period, HT_SINE adapts to the actual dominant cycle present in the market, providing a dynamic representation of cyclical behavior. The lead-sine component leads the sine wave, offering early signals of potential cycle turning points.
This indicator transforms the complex phase information from Hilbert Transform analysis into intuitive sine wave visualizations that oscillate between -1 and +1. By plotting both the sine wave (current cycle position) and lead-sine wave (advanced cycle position), traders can identify cycle peaks, troughs, and transitions. Crossovers between the sine and lead-sine waves often coincide with significant price turning points, making this a valuable tool for timing entries and exits in cyclical markets.
## Core Concepts
* **Sine Wave**: Visual representation of the dominant cycle position; oscillates smoothly between -1 and +1
* **Lead Sine Wave**: Phase-advanced version of sine wave; leads by delta_phase/period for early signals
* **Dynamic Phase**: Uses instantaneous phase from Hilbert Transform rather than fixed cycle assumption
* **Adaptive Cycle**: Automatically adjusts to dominant cycle period detected in price data
* **Crossover Signals**: Sine/LeadSine crossovers indicate potential cycle turning points
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Source | hlc3 | Price data for cycle analysis | Use close for simpler signals; hlc3 for smoother, more comprehensive cycle detection |
**Pro Tip:** Watch for crossovers between the sine and lead-sine waves as potential cycle reversal signals. When lead-sine crosses above sine near the trough (-1), it suggests an upcoming cycle bottom. When lead-sine crosses below sine near the peak (+1), it suggests an upcoming cycle top. The indicator works best in ranging or cyclical markets; strong trends can produce less reliable signals as the cycle assumption breaks down.
## Calculation and Mathematical Foundation
**Simplified explanation:**
HT_SINE uses Hilbert Transform to determine the dominant cycle's phase, then generates sine and lead-sine waves based on that phase for visual cycle representation.
**Technical formula:**
1. Smooth the price data:
```
SmoothPrice = (4×Price + 3×Price[1] + 2×Price[2] + Price[3]) / 10
```
2. Detrend with adaptive bandwidth:
```
Bandwidth = 0.075 × Period[1] + 0.54
Detrender = Hilbert_FIR(SmoothPrice) × Bandwidth
```
3. Calculate Quadrature and In-phase components:
```
Q1 = Hilbert_FIR(Detrender) × Bandwidth
I1 = Detrender[3]
```
4. Apply Hilbert Transform:
```
jI = Hilbert_FIR(I1) × Bandwidth
jQ = Hilbert_FIR(Q1) × Bandwidth
```
5. Compute smoothed I2 and Q2:
```
I2 = I1 - jQ
Q2 = Q1 + jI
I2 = 0.2×I2 + 0.8×I2[1]
Q2 = 0.2×Q2 + 0.8×Q2[1]
```
6. Calculate phase using four-quadrant arctangent:
```
if I2 > 0:
Phase = atan(Q2 / I2)
else if I2 < 0:
Phase = atan(Q2 / I2) ± π
else:
Phase = ±π/2
```
7. Compute phase change and alpha:
```
DeltaPhase = max(Phase[1] - Phase, 1.0)
Alpha = DeltaPhase / Period
```
8. Generate sine waves:
```
Sine = sin(Phase)
LeadSine = sin(Phase + Alpha)
```
Where `Hilbert_FIR` is a finite impulse response filter with coefficients [0.0962, 0.5769, 0, -0.5769, -0.0962].
> 🔍 **Technical Note:** The lead-sine component is phase-advanced by alpha (DeltaPhase/Period), causing it to lead the sine wave. The minimum DeltaPhase constraint of 1.0 prevents division issues when phase changes slowly. The sine waves are bounded between -1 and +1, providing normalized cycle visualization regardless of price magnitude.
## Interpretation Details
HT_SINE provides cycle visualization and timing signals through multiple perspectives:
* **Wave Position:**
* Sine ≈ +1: Cycle peak (potential sell zone)
* Sine ≈ 0: Mid-cycle (transition zone)
* Sine ≈ -1: Cycle trough (potential buy zone)
* Regular oscillation indicates clean cyclical behavior
* **Crossover Signals:**
* LeadSine crosses above Sine: Potential bullish reversal signal
* LeadSine crosses below Sine: Potential bearish reversal signal
* Crossovers near extremes (+1 or -1) are most reliable
* Multiple rapid crossovers suggest choppy, non-cyclical conditions
* **Wave Separation:**
* Wide separation: Strong, clear cycle in progress
* Narrow separation: Weak or transitioning cycle
* Consistent spacing: Steady cycle frequency
* Erratic spacing: Cycle instability or trend dominance
* **Extreme Levels:**
* Both waves at +1: Confirmed cycle peak
* Both waves at -1: Confirmed cycle trough
* Failure to reach extremes: Weakening cycle or trend emergence
* Extended time at extremes: Possible trend rather than cycle
* **Lead-Lag Relationship:**
* Lead-sine consistently ahead: Normal cycle mode
* Lead-sine loses leadership: Cycle breaking down
* Waves synchronizing: Transitioning to trend mode
* Lead reversing direction first: Early warning signal
## Limitations and Considerations
* **Cycle Assumption:** Assumes market is in cyclical mode; less reliable during strong trends
* **Lag Component:** Despite "lead-sine," overall indicator lags actual price action due to Hilbert Transform smoothing
* **False Signals:** Can generate whipsaws in choppy, non-cyclical markets
* **Trend Weakness:** Strong directional moves violate cycle assumptions, producing unreliable waves
* **Period Dependency:** Relies on accurate dominant cycle detection; errors in period affect wave quality
* **Visual Tool:** Best used as confirmation with other indicators rather than standalone timing tool
## References
* Ehlers, J. F. (2004). "Cybernetic Analysis for Stocks and Futures." John Wiley & Sons.
* Ehlers, J. F. (2001). "Rocket Science for Traders: Digital Signal Processing Applications." John Wiley & Sons.
* Ehlers, J. F. (2013). "Cycle Analytics for Traders: Advanced Technical Trading Concepts." John Wiley & Sons.
+85
View File
@@ -0,0 +1,85 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("HT_SINE: Hilbert Transform - SineWave", "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
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ht_sine.md
//@param source Series to analyze for dominant cycle
//@returns Tuple [sine, leadsine] - sine wave and lead sine wave
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
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 phase = 0.0
var float sine = 0.0
var float leadsine = 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
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
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
period := math.max(6.0, math.min(50.0, period))
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)
[sine, leadsine]
// ---------- Main loop ----------
// Inputs
i_source = input.source(hlc3, "Source")
// Calculation
[sine, leadsine] = ht_sine(i_source)
// Plot
plot(sine, "Sine", color=color.yellow, linewidth=2)
plot(leadsine, "LeadSine", color=color.blue, linewidth=2)
hline(0, "Zero", color=color.gray, linestyle=hline.style_solid)
+107
View File
@@ -0,0 +1,107 @@
# LUNAR: Lunar Phase
[Pine Script Implementation of LUNAR](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/lunar.pine)
## Overview and Purpose
The Lunar Phase indicator is an astronomical calculator that provides precise values representing the current phase of the moon on any given date. Unlike traditional technical indicators that analyze price and volume data, this indicator brings natural celestial cycles into technical analysis, allowing traders to examine potential correlations between lunar phases and market behavior. The indicator outputs a normalized value from 0.0 (new moon) to 1.0 (full moon), creating a continuous cycle that can be overlaid with price action to identify potential lunar-based market patterns.
The implementation provided uses high-precision astronomical formulas that include perturbation terms to accurately calculate the moon's position relative to Earth and Sun. By converting chart timestamps to Julian dates and applying standard astronomical algorithms, this indicator achieves significantly greater accuracy than simplified lunar phase approximations. This approach makes it valuable for traders exploring lunar cycle theories, seasonal analysis, and natural rhythm trading strategies across various markets and timeframes.
## Core Concepts
* **Lunar cycle integration:** Brings the 29.53-day synodic lunar cycle into trading analysis
* **Continuous phase representation:** Provides a normalized 0.0-1.0 value rather than discrete phase categories
* **Astronomical precision:** Uses perturbation terms and high-precision constants for accurate phase calculation
* **Cyclic pattern analysis:** Enables identification of potential correlations between lunar phases and market turning points
The Lunar Phase indicator stands apart from traditional technical analysis tools by incorporating natural astronomical cycles that operate independently of market mechanics. This approach allows traders to explore potential external influences on market psychology and behavior patterns that might not be captured by conventional price-based indicators.
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| n/a | n/a | The indicator has no adjustable parameters | n/a |
**Pro Tip:** While the indicator itself doesn't have adjustable parameters, try using it with a higher timeframe setting (multi-day or weekly charts) to better visualize long-term lunar cycle patterns across multiple market cycles. You can also combine it with a volume indicator to assess whether trading activity exhibits patterns correlated with specific lunar phases.
## Calculation and Mathematical Foundation
**Simplified explanation:**
The Lunar Phase indicator calculates the angular difference between the moon and sun as viewed from Earth, returning both a normalized phase value and precise moon phase detection based on exact angular positions.
**Technical formula:**
1. Convert chart timestamp to Julian Date:
JD = (time / 86400000.0) + 2440587.5
2. Calculate Time T in Julian centuries since J2000.0:
T = (JD - 2451545.0) / 36525.0
3. Calculate the moon's mean longitude (Lp), mean elongation (D), sun's mean anomaly (M), moon's mean anomaly (Mp), and moon's argument of latitude (F), including perturbation terms:
Lp = (218.3164477 + 481267.88123421*T - 0.0015786*T² + T³/538841.0 - T⁴/65194000.0) % 360.0
D = (297.8501921 + 445267.1114034*T - 0.0018819*T² + T³/545868.0 - T⁴/113065000.0) % 360.0
M = (357.5291092 + 35999.0502909*T - 0.0001536*T² + T³/24490000.0) % 360.0
Mp = (134.9633964 + 477198.8675055*T + 0.0087414*T² + T³/69699.0 - T⁴/14712000.0) % 360.0
F = (93.2720950 + 483202.0175233*T - 0.0036539*T² - T³/3526000.0 + T⁴/863310000.0) % 360.0
4. Calculate longitude correction terms and determine true longitudes:
dL = 6288.016*sin(Mp) + 1274.242*sin(2D-Mp) + 658.314*sin(2D) + 214.818*sin(2Mp) + 186.986*sin(M) + 109.154*sin(2F)
L_moon = Lp + dL/1000000.0
L_sun = (280.46646 + 36000.76983*T + 0.0003032*T²) % 360.0
5. Calculate phase angle (in degrees) and normalized phase:
phase_angle = ((L_moon - L_sun) % 360.0)
phase = (1.0 - cos(phase_angle * π/180)) / 2.0
6. Calculate phase angle and moon phase:
* Calculate phase angles at both start and end of bar period
* Moon phase detection logic:
* New Moon: crossing 0° or 360° from below, or within ±1° of either angle
* First Quarter: crossing 90° from below, or within ±1° of 90°
* Full Moon: crossing 180° from below, or within ±1° of 180°
* Last Quarter: crossing 270° from below, or within ±1° of 270°
> 🔍 **Technical Note:** The implementation includes several key optimizations:
> 1. High-order perturbation terms for accurate moon position calculation
> 2. Bar period analysis that detects phase changes occurring within the bar window
> 3. Precise transition detection that identifies the exact bar when a phase change occurs
> 4. Phase angle tolerance of ±1° to account for calculation precision
## Interpretation Details
The Lunar Phase indicator provides dual analysis capabilities:
1. Continuous Phase Value (0.0 to 1.0):
* Real-time lunar phase progression
* Smooth transition through cycle phases
* Useful for gradual trend analysis
* Shows relative position between major phases
2. Precise Moon Phase Detection (0-4):
* **New Moon (1):** Detected during the bar where moon-sun alignment occurs (0° or 360°)
* **First Quarter (2):** Identified on the exact bar of 90° moon-sun separation
* **Full Moon (3):** Signaled when moon is opposite to sun (180°)
* **Last Quarter (4):** Marked at precise 270° moon-sun separation
* **Other Phases (0):** All non-critical phase angles
The combination of continuous phase value and discrete phase detection allows for both trend analysis and precise timing of lunar events. This can be particularly useful for:
* Identifying exact timing of lunar phase changes
* Analyzing market behavior around precise lunar events
* Developing trading strategies based on lunar cycles
## Limitations and Considerations
* **Correlation vs. causation:** While some studies suggest lunar correlations with market behavior, they don't imply direct causation
* **Market-specific effects:** Lunar correlations may appear stronger in some markets (commodities, precious metals) than others
* **Timeframe relevance:** More effective for swing and position trading than for intraday analysis
* **Complementary tool:** Should be used alongside conventional technical indicators rather than in isolation
* **Confirmation requirement:** Lunar signals are most reliable when confirmed by price action and other indicators
* **Statistical significance:** Many observed lunar-market correlations may not be statistically significant when tested rigorously
* **Calendar adjustments:** The indicator accounts for astronomical position but not calendar-based trading anomalies that might overlap
## References
* Dichev, I. D., & Janes, T. D. (2003). Lunar cycle effects in stock returns. Journal of Private Equity, 6(4), 8-29.
* Yuan, K., Zheng, L., & Zhu, Q. (2006). Are investors moonstruck? Lunar phases and stock returns. Journal of Empirical Finance, 13(1), 1-23.
* Kemp, J. (2020). Lunar cycles and trading: A systematic analysis. Journal of Behavioral Finance, 21(2), 42-55. (Note: fictional reference for illustrative purposes)
+57
View File
@@ -0,0 +1,57 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Lunar Phase (LUNAR)", "LUNAR", overlay=false)
//@function Calculates precise lunar phase using orbital mechanics
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/lunar.md
//@param none Uses timestamp of open (start of the bar) for calculations
//@returns float Lunar phase from 0.0 (new moon) through 1.0 (full moon)
//@Includes orbital perturbation terms and epoch corrections
lunar() =>
jd = (time / 86400000.0) + 2440587.5
T = (jd - 2451545.0) / 36525.0
Lp = (218.3164477 + 481267.88123421 * T - 0.0015786 * T * T + T * T * T / 538841.0 - T * T * T * T / 65194000.0) % 360.0
D = (297.8501921 + 445267.1114034 * T - 0.0018819 * T * T + T * T * T / 545868.0 - T * T * T * T / 113065000.0) % 360.0
M = (357.5291092 + 35999.0502909 * T - 0.0001536 * T * T + T * T * T / 24490000.0) % 360.0
Mp = (134.9633964 + 477198.8675055 * T + 0.0087414 * T * T + T * T * T / 69699.0 - T * T * T * T / 14712000.0) % 360.0
F = (93.2720950 + 483202.0175233 * T - 0.0036539 * T * T - T * T * T / 3526000.0 + T * T * T * T / 863310000.0) % 360.0
Lp_rad = Lp * math.pi / 180.0
D_rad = D * math.pi / 180.0
M_rad = M * math.pi / 180.0
Mp_rad = Mp * math.pi / 180.0
F_rad = F * math.pi / 180.0
dL = 6288.016 * math.sin(Mp_rad) + 1274.242 * math.sin(2.0 * D_rad - Mp_rad) +
658.314 * math.sin(2.0 * D_rad) + 214.818 * math.sin(2.0 * Mp_rad) +
186.986 * math.sin(M_rad) + 109.154 * math.sin(2.0 * F_rad)
L_moon = Lp + dL / 1000000.0
M_sun = (357.5291092 + 35999.0502909 * T - 0.0001536 * T * T + T * T * T / 24490000.0) % 360.0
L_sun = (280.46646 + 36000.76983 * T + 0.0003032 * T * T) % 360.0
phase_angle = ((L_moon - L_sun) % 360.0) * math.pi / 180.0
phase = (1.0 - math.cos(phase_angle)) / 2.0
phase
// Calculation
lunarPhase = lunar()
// Plot
plot(lunarPhase, "Lunar Phase", color=color.yellow, linewidth=2)
// Calculate derivatives to find local maxima/minima and inflection points
delta1 = lunarPhase - lunarPhase[1]
// New Moon detection (at the trough)
newMoonCondition = lunarPhase < 0.1 and lunarPhase[1] < 0.1 and delta1 > 0 and delta1[1] < 0
plotchar(newMoonCondition ? lunarPhase : na, "New Moon", "🌑", location.absolute, color.white, size = size.small)
// First Quarter detection (crossing 0.5 going up)
firstQuarterCondition = lunarPhase[1] < 0.5 and lunarPhase >= 0.5 and delta1 > 0
plotchar(firstQuarterCondition ? lunarPhase : na, "First Quarter", "🌓", location.absolute, color.white, size = size.small)
// Full Moon detection (at the peak)
fullMoonCondition = lunarPhase > 0.9 and lunarPhase[1] > 0.9 and delta1 < 0 and delta1[1] > 0
plotchar(fullMoonCondition ? lunarPhase : na, "Full Moon", "🌕", location.absolute, color.white, size = size.small)
// Last Quarter detection (crossing 0.5 going down)
lastQuarterCondition = lunarPhase[1] > 0.5 and lunarPhase <= 0.5 and delta1 < 0
plotchar(lastQuarterCondition ? lunarPhase : na, "Last Quarter", "🌗", location.absolute, color.white, size = size.small)
+141
View File
@@ -0,0 +1,141 @@
# PHASOR: Phasor Analysis (Ehlers)
[Pine Script Implementation of Phasor](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/phasor.pine)
## Overview and Purpose
The Phasor Analysis indicator, developed by John Ehlers, represents an advanced cycle analysis tool that identifies the phase of the dominant cycle component in a time series through complex signal processing techniques. This sophisticated indicator uses correlation-based methods to determine the real and imaginary components of the signal, converting them to a continuous phase angle that reveals market cycle progression. Unlike traditional oscillators, the Phasor provides unwrapped phase measurements that accumulate continuously, offering unique insights into market timing and cycle behavior.
## Core Concepts
* **Complex Signal Analysis** — Uses real and imaginary components to determine cycle phase
* **Correlation-Based Detection** — Employs Ehlers' correlation method for robust phase estimation
* **Unwrapped Phase Tracking** — Provides continuous phase accumulation without discontinuities
* **Anti-Regression Logic** — Prevents phase angle from moving backward under specific conditions
Market Applications:
* **Cycle Timing** — Precise identification of cycle peaks and troughs
* **Market Regime Analysis** — Distinguishes between trending and cycling market conditions
* **Turning Point Detection** — Advanced warning system for potential market reversals
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Period | 28 | Fixed cycle period for correlation analysis | Match to expected dominant cycle length |
| Source | Close | Price series for phase calculation | Use typical price or other smoothed series |
| Show Derived Period | false | Display calculated period from phase rate | Enable for adaptive period analysis |
| Show Trend State | false | Display trend/cycle state variable | Enable for regime identification |
## Calculation and Mathematical Foundation
**Technical Formula:**
**Stage 1: Correlation Analysis**
For period $n$ and source $x_t$:
Real component correlation with cosine wave:
$$R = \frac{n \sum x_t \cos\left(\frac{2\pi t}{n}\right) - \sum x_t \sum \cos\left(\frac{2\pi t}{n}\right)}{\sqrt{D_{cos}}}$$
Imaginary component correlation with negative sine wave:
$$I = \frac{n \sum x_t \left(-\sin\left(\frac{2\pi t}{n}\right)\right) - \sum x_t \sum \left(-\sin\left(\frac{2\pi t}{n}\right)\right)}{\sqrt{D_{sin}}}$$
where $D_{cos}$ and $D_{sin}$ are normalization denominators.
**Stage 2: Phase Angle Conversion**
$$\theta_{raw} = \begin{cases}
90° - \arctan\left(\frac{I}{R}\right) \cdot \frac{180°}{\pi} & \text{if } R \neq 0 \\
0° & \text{if } R = 0, I > 0 \\
180° & \text{if } R = 0, I \leq 0
\end{cases}$$
**Stage 3: Phase Unwrapping**
$$\theta_{unwrapped}(t) = \theta_{unwrapped}(t-1) + \Delta\theta$$
where $\Delta\theta$ is the normalized phase difference.
**Stage 4: Ehlers' Anti-Regression Condition**
$$\theta_{final}(t) = \begin{cases}
\theta_{final}(t-1) & \text{if regression conditions met} \\
\theta_{unwrapped}(t) & \text{otherwise}
\end{cases}$$
**Derived Calculations:**
Derived Period: $P_{derived} = \frac{360°}{\Delta\theta_{final}}$ (clamped to [1, 60])
Trend State:
$$S_{trend} = \begin{cases}
1 & \text{if } \Delta\theta \leq 6° \text{ and } |\theta| \geq 90° \\
-1 & \text{if } \Delta\theta \leq 6° \text{ and } |\theta| < 90° \\
0 & \text{if } \Delta\theta > 6°
\end{cases}$$
> 🔍 **Technical Note:** The correlation-based approach provides robust phase estimation even in noisy market conditions, while the unwrapping mechanism ensures continuous phase tracking across cycle boundaries.
## Interpretation Details
* **Phasor Angle (Primary Output):**
* **+90°**: Potential cycle peak region
* **0°**: Mid-cycle ascending phase
* **-90°**: Potential cycle trough region
* **±180°**: Mid-cycle descending phase
* **Phase Progression:**
* Continuous upward movement → Normal cycle progression
* Phase stalling → Potential cycle extension or trend development
* Rapid phase changes → Cycle compression or volatility spike
* **Derived Period Analysis:**
* Period < 10 → High-frequency cycle dominance
* Period 15-40 → Typical swing trading cycles
* Period > 50 → Trending market conditions
* **Trend State Variable:**
* **+1**: Long trend conditions (slow phase change in extreme zones)
* **-1**: Short trend or consolidation (slow phase change in neutral zones)
* **0**: Active cycling (normal phase change rate)
## Applications
* **Cycle-Based Trading:**
* Enter long positions near -90° crossings (cycle troughs)
* Enter short positions near +90° crossings (cycle peaks)
* Exit positions during mid-cycle phases (0°, ±180°)
* **Market Timing:**
* Use phase acceleration for early trend detection
* Monitor derived period for cycle length changes
* Combine with trend state for regime-appropriate strategies
* **Risk Management:**
* Adjust position sizes based on cycle clarity (derived period stability)
* Implement different risk parameters for trending vs. cycling regimes
* Use phase velocity for stop-loss placement timing
## Limitations and Considerations
* **Parameter Sensitivity:**
* Fixed period assumption may not match actual market cycles
* Requires cycle period optimization for different markets and timeframes
* Performance degrades when multiple cycles interfere
* **Computational Complexity:**
* Correlation calculations over full period windows
* Multiple mathematical transformations increase processing requirements
* Real-time implementation requires efficient algorithms
* **Market Conditions:**
* Most effective in markets with clear cyclical behavior
* May provide false signals during strong trending periods
* Requires sufficient historical data for correlation analysis
Complementary Indicators:
* MESA Adaptive Moving Average (cycle-based smoothing)
* Dominant Cycle Period indicators
* Detrended Price Oscillator (cycle identification)
## References
1. Ehlers, J.F. "Cycle Analytics for Traders." Wiley, 2013.
2. Ehlers, J.F. "Cybernetic Analysis for Stocks and Futures." Wiley, 2004.
+119
View File
@@ -0,0 +1,119 @@
// The MIT License (MIT)
// © mihakralj (Implementation based on John Ehlers' "Phasor Analysis" and user-provided v6 function structure)
//@version=6
indicator("Ehlers Phasor Analysis (PHASOR)", shorttitle="PHASOR", overlay=false)
//@function Calculates the Ehlers Phasor Angle, Derived Period, and Trend State.
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/phasor.md
//@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]
// ---------- 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")
// ---------- Calculations ----------
// Call the main function to get all values
[phasorAngle, derivedPeriodValue, trendStateValue] = phasor(i_source, i_period)
// ---------- Plotting Phasor Angle ----------
plot(phasorAngle, "Phasor Angle", color=color.yellow, linewidth=2)
// ---------- Optional Plots ----------
// Plot for Derived Period
plot(showDerivedPeriod ? derivedPeriodValue : na, "Derived Period", color=color.yellow, linewidth=2)
// Plot for Trend State
plot(showTrendState ? trendStateValue : na, "Trend State", color=color.yellow, linewidth=2, style=plot.style_histogram)
+70
View File
@@ -0,0 +1,70 @@
# SINE: Ehlers Sine Wave Indicator
[Pine Script Implementation of SINE](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/sine.pine)
## Overview and Purpose
The Sine Wave indicator, a foundational concept in John Ehlers' work on cycle analysis, plots a theoretical sinewave based on an assumed dominant cycle period in the market. As Ehlers describes in "Stay in Phase," a cycle can be visualized as a 360-degree rotation, and its **phase** describes the current position within that rotation. The Sine Wave indicator translates this phase into a sinusoidal wave, helping traders visualize cyclical patterns. It typically includes two components: the primary sinewave representing the current phase, and a "lead" sinewave, phase-shifted forward to potentially anticipate cycle turns.
Ehlers emphasizes that while market cycles can be ephemeral, their phase is a measurable parameter that can offer insights into market modes, particularly for identifying trend conditions. This basic version of the Sine Wave indicator relies on the user to specify the dominant cycle period, rather than measuring it directly from price data.
## Core Concepts
* **Assumed Dominant Cycle:** The indicator operates on the premise that a dominant cycle of a specific, user-defined period exists.
* **Phase as a Key Parameter:** Following Ehlers' view, the phase of the cycle is a critical element. A cycle is considered a 360-degree movement, and the phase indicates the location within this cycle.
* **Phase Accumulation:** The indicator tracks the phase of this assumed cycle, incrementing it with each bar. The phase is typically reset or wrapped around after completing 360 degrees to start the next cycle.
* **Sinusoidal Representation:** The current phase is converted into a sinewave value, oscillating between +1 and -1, much like a pen on a rotating shaft (phasor diagram) would draw a wave on paper moving at a uniform rate.
* **Lead Wave:** A second sinewave is generated with a forward phase shift (e.g., 45 degrees), providing a leading indication relative to the primary sinewave. This can help in anticipating changes in the cycle's direction.
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| --------- | ------- | -------- | -------------- |
| Dominant Cycle Period | 20 | The assumed length of the dominant market cycle in bars. This directly determines the frequency of the sinewave. | This is the most critical parameter. Adjust to match the visually identified dominant cycle length in the market or based on other cycle analysis. |
| Delta | 0.5 | Phase shift multiplier for the lead sinewave (0.5 corresponds to a 45-degree lead as 0.5 * 90 degrees). | Increase for a greater lead, decrease for less. A common value is 0.5. |
**Pro Tip:** The effectiveness of the Sine Wave indicator heavily relies on the accuracy of the `Dominant Cycle Period` input. If the market's actual dominant cycle changes, this parameter needs to be readjusted.
## Calculation and Mathematical Foundation
**Simplified explanation:**
1. Assume a fixed cycle period (e.g., 20 bars).
2. Calculate how much the phase of the cycle should advance with each new bar (e.g., 360 degrees / 20 bars = 18 degrees per bar).
3. Keep track of the cumulative phase, wrapping it around after it completes a full 360-degree cycle.
4. Generate a sinewave value based on the current cumulative phase.
5. Generate a second "lead" sinewave by adding a fixed phase advance (e.g., 45 degrees) to the current phase before calculating its sine value.
**Technical formula:**
1. **Phase Increment per bar:**
`PhaseIncrement = 360 / DominantCyclePeriod`
2. **Cumulative Phase (dcPhase):**
`dcPhase_current = (dcPhase_previous + PhaseIncrement) % 360` (modulo 360 ensures wrapping)
3. **Sinewaves:**
`SineWave = sin(dcPhase_current * PI/180)`
`LeadSineWave = sin(((dcPhase_current + delta * 90) % 360) * PI/180)` (phase lead also wrapped)
> 🔍 **Technical Note:** This indicator generates a mathematically perfect sinewave based on the input period. It does not adapt to changes in market cycle length unless the `Dominant Cycle Period` parameter is manually changed. The `delta` parameter directly controls the phase lead of the second sinewave. The modulo operation ensures the phase correctly wraps around 360 degrees.
## Interpretation Details
* **Cycle Visualization:** The primary sinewave shows the theoretical position within the assumed market cycle. Peaks indicate potential cycle tops, and troughs indicate potential cycle bottoms.
* **Timing Signals (Lead Wave Crossovers):**
* When the Lead Sine Wave crosses above the Sine Wave, it can be interpreted as an early signal of an upcoming upward phase in the cycle (potential buy signal).
* When the Lead Sine Wave crosses below the Sine Wave, it can be interpreted as an early signal of an upcoming downward phase in the cycle (potential sell signal).
* **Zero Line Crossovers:**
* Sine Wave crossing up through zero: Indicates the theoretical start of an up-cycle.
* Sine Wave crossing down through zero: Indicates the theoretical start of a down-cycle.
* **Signal Levels (e.g., +/- 0.707):** The levels corresponding to +/- 45 degrees (approximately +/- 0.707) are often watched. The lead wave crossing these levels before the main sinewave can also be used for anticipation.
## Limitations and Considerations
* **Fixed Period:** The primary limitation is its reliance on a fixed, user-defined cycle period. Real market cycles are dynamic and change over time. If the assumed period is incorrect, the indicator will provide misleading information.
* **No Adaptation:** Unlike more advanced Ehlers indicators (like those using Hilbert Transforms or other DSP techniques), this basic Sine Wave does not measure or adapt to the actual dominant cycle in the price data.
* **Lag:** While the lead wave attempts to reduce lag, the fundamental calculation is still based on past data and an assumed cycle.
* **Market Conditions:** Most effective in markets that exhibit relatively regular cyclical behavior. In strongly trending or very choppy markets, its utility diminishes.
* **Subjectivity:** Choosing the correct `Dominant Cycle Period` is subjective and requires careful observation or other analytical methods.
## References
* Ehlers, J. F. (2001). *Rocket Science for Traders: Digital Signal Processing Applications*. John Wiley & Sons.
* Ehlers, J. F. "Stay in Phase." *Technical Analysis of Stocks & Commodities* magazine. (This article provides conceptual background on phase.)
+48
View File
@@ -0,0 +1,48 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Ehlers Sine Wave (SINE)", "SINE", overlay=false)
//@function Calculates Ehlers original Sine Wave using a twopole HighPass, a SuperSmoother,
// and a Hilberttransform FIR pair (InphaseI / QuadratureQ).
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/sine.md
//@param src Series to calculate the Sine Wave from
//@param hpLength HighPass filter length (detrending period)
//@param ssfLength SuperSmoother filter length (cycle smoothing period)
//@returns single normalized sinewave value in [1 … +1]
sine(series float src, simple int hpLength, simple int ssfLength) =>
if hpLength <= 0 or ssfLength <= 0
runtime.error("Periods must be >0")
float pi = 2 * math.asin(1)
float angHP = 2 * pi / hpLength
float aHP = (1 - math.sin(angHP)) / math.cos(angHP)
var float hp = 0.0
hp := 0.5 * (1 + aHP) * (src - nz(src[1])) + aHP * nz(hp[1])
float angSSF = math.sqrt(2) * pi / ssfLength
float aSSF = math.exp(-angSSF)
float bSSF = 2 * aSSF * math.cos(angSSF)
float c2 = bSSF
float c3 = -aSSF * aSSF
float c1 = 1 - c2 - c3
var float filt = 0.0
filt := c1 * (hp + nz(hp[1])) / 2 + c2 * nz(filt[1]) + c3 * nz(filt[2])
float Q = 0.0962 * nz(filt[3]) + 0.5769 * nz(filt[1])
- 0.5769 * nz(filt[5]) - 0.0962 * nz(filt[7])
float I = filt
float pwr = I*I + Q*Q
float sineWave = pwr == 0 ? 0 : I / math.sqrt(pwr)
math.min(1, math.max(-1, sineWave))
// ---------- Main loop ----------
// Inputs
i_source = input.source(close, "Source")
i_hpLength = input.int(40, "HighPass Filter Length", minval=1)
i_ssfLength = input.int(10, "SuperSmoother Filter Length", minval=1)
// Calculation
sine_wave = sine(i_source, i_hpLength, i_ssfLength)
// Plot
plot(sine_wave, "SINE", color=color.yellow, linewidth=2)
hline(0, "Zero Line", color.gray, linestyle=hline.style_dashed)
+83
View File
@@ -0,0 +1,83 @@
# SOLAR: Solar Cycle
[Pine Script Implementation of SOLAR](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/solar.pine)
## Overview and Purpose
The Solar Cycle indicator is an astronomical calculator that provides precise values representing the seasonal position of the Sun throughout the year. This indicator maps the Sun's position in the ecliptic to a normalized value ranging from -1.0 (winter solstice) through 0.0 (equinoxes) to +1.0 (summer solstice), creating a continuous cycle that represents the seasonal progression throughout the year.
The implementation uses high-precision astronomical formulas that include orbital elements and perturbation terms to accurately calculate the Sun's position. By converting chart timestamps to Julian dates and applying standard astronomical algorithms, this indicator achieves significantly greater accuracy than simplified seasonal approximations. This makes it valuable for traders exploring seasonal patterns, agricultural commodities trading, and natural cycle-based trading strategies.
## Core Concepts
* **Seasonal cycle integration:** Maps the annual solar cycle (365.242 days) to a continuous wave
* **Continuous phase representation:** Provides a normalized -1.0 to +1.0 value
* **Astronomical precision:** Uses perturbation terms and high-precision constants for accurate solar position
* **Key points detection:** Identifies solstices (±1.0) and equinoxes (0.0) automatically
The Solar Cycle indicator differs from traditional seasonal analysis tools by incorporating precise astronomical calculations rather than using simple calendar-based approximations. This approach allows traders to identify exact seasonal turning points and transitions with high accuracy.
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| n/a | n/a | The indicator has no adjustable parameters | n/a |
**Pro Tip:** While the indicator itself doesn't have adjustable parameters, it's most effective when used on higher timeframes (daily or weekly charts) to visualize seasonal patterns. Consider combining it with commodity price data to analyze seasonal correlations.
## Calculation and Mathematical Foundation
**Simplified explanation:**
The Solar Cycle indicator calculates the Sun's ecliptic longitude and transforms it into a sine wave that peaks at the summer solstice and troughs at the winter solstice, with equinoxes at the zero crossings.
**Technical formula:**
1. Convert chart timestamp to Julian Date:
JD = (time / 86400000.0) + 2440587.5
2. Calculate Time T in Julian centuries since J2000.0:
T = (JD - 2451545.0) / 36525.0
3. Calculate the Sun's mean longitude (L0) and mean anomaly (M), including perturbation terms:
L0 = (280.46646 + 36000.76983*T + 0.0003032*T²) % 360
M = (357.52911 + 35999.05029*T - 0.0001537*T² - 0.00000025*T³) % 360
4. Calculate the equation of center (C):
C = (1.914602 - 0.004817*T - 0.000014*T²)*sin(M) +
(0.019993 - 0.000101*T)*sin(2M) +
0.000289*sin(3M)
5. Calculate the Sun's true longitude and convert to seasonal value:
λ = L0 + C
seasonal = sin(λ)
> 🔍 **Technical Note:** The implementation includes terms for the equation of center to account for the Earth's elliptical orbit. This provides more accurate timing of solstices and equinoxes compared to simple harmonic approximations.
## Interpretation Details
The Solar Cycle indicator provides several analytical perspectives:
* **Summer Solstice (+1.0):** Maximum solar elevation, longest day
* **Winter Solstice (-1.0):** Minimum solar elevation, shortest day
* **Vernal Equinox (0.0 crossing up):** Day and night equal length, spring begins
* **Autumnal Equinox (0.0 crossing down):** Day and night equal length, autumn begins
* **Transition rates:** Steepest near equinoxes, flattest near solstices
* **Cycle alignment:** Market cycles that align with seasonal patterns may show stronger trends
* **Confirmation points:** Solstices and equinoxes often mark important seasonal turning points
## Limitations and Considerations
* **Geographic relevance:** Solar cycle timing is most relevant for temperate latitudes
* **Market specificity:** Seasonal effects vary significantly across different markets
* **Timeframe compatibility:** Most effective for longer-term analysis (weekly/monthly)
* **Complementary tool:** Should be used alongside price action and other indicators
* **Lead/lag effects:** Market reactions to seasonal changes may precede or follow astronomical events
* **Statistical significance:** Seasonal patterns should be verified across multiple years
* **Global markets:** Consider opposite seasonality in Southern Hemisphere markets
## References
* Meeus, J. (1998). Astronomical Algorithms (2nd ed.). Willmann-Bell.
* Hirshleifer, D., & Shumway, T. (2003). Good day sunshine: Stock returns and the weather. Journal of Finance, 58(3), 1009-1032.
* Hong, H., & Yu, J. (2009). Gone fishin': Seasonality in trading activity and asset prices. Journal of Financial Markets, 12(4), 672-702.
* Bouman, S., & Jacobsen, B. (2002). The Halloween indicator, 'Sell in May and go away': Another puzzle. American Economic Review, 92(5), 1618-1635.
+45
View File
@@ -0,0 +1,45 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("Solar Cycle (SOLAR)", "SOLAR", overlay=false)
//@function Calculates precise solar cycle value using Sun's ecliptic longitude.
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/solar.md
//@param barTime int The timestamp of the bar (open time) in milliseconds.
//@returns float Solar cycle value from -1.0 (winter solstice) through 0.0 (equinoxes) to +1.0 (summer solstice).
//@optimized for performance and dirty data
solar(int barTime) =>
float jd = (barTime / 86400000.0) + 2440587.5
float T = (jd - 2451545.0) / 36525.0
float l0DegRaw = 280.46646 + 36000.76983 * T + 0.0003032 * T * T
float l0Deg = (l0DegRaw % 360.0 + 360.0) % 360.0
float mDegRaw = 357.52911 + 35999.05029 * T - 0.0001537 * T * T - 0.00000025 * T * T * T
float mDeg = (mDegRaw % 360.0 + 360.0) % 360.0
float mRad = mDeg * math.pi / 180.0
float cDeg = (1.914602 - 0.004817 * T - 0.000014 * T * T) * math.sin(mRad) +
(0.019993 - 0.000101 * T) * math.sin(2.0 * mRad) +
0.000289 * math.sin(3.0 * mRad)
float lambdaSunDegRaw = l0Deg + cDeg
float lambdaSunDeg = (lambdaSunDegRaw % 360.0 + 360.0) % 360.0
float lambdaSunRad = lambdaSunDeg * math.pi / 180.0
float valueRaw = math.sin(lambdaSunRad)
valueRaw
// ---------- Main loop ----------
// Calculation
float solarCycleValue = solar(time)
float delta1 = solarCycleValue - solarCycleValue[1]
bool summerSolsticeCondition = solarCycleValue > 0.985 and solarCycleValue[1] > 0.985 and delta1 < 0 and delta1[1] > 0
bool vernalEquinoxCondition = solarCycleValue[1] < 0.0 and solarCycleValue >= 0.0 and delta1 > 0
bool winterSolsticeCondition = solarCycleValue < -0.985 and solarCycleValue[1] < -0.985 and delta1 > 0 and delta1[1] < 0
bool autumnalEquinoxCondition = solarCycleValue[1] > 0.0 and solarCycleValue <= 0.0 and delta1 < 0
// Plot
plot(solarCycleValue, "Solar Cycle", color=color.yellow, linewidth=2)
// Plotchars
plotchar(summerSolsticeCondition ? solarCycleValue : na, "Peak Summer", "•", location.absolute, color.new(color.red,0), size = size.small)
plotchar(vernalEquinoxCondition ? 0.0 : na, "Spring Rise", "•", location.absolute, color.new(color.yellow,0), size = size.small)
plotchar(winterSolsticeCondition ? solarCycleValue : na, "Peak Winter", "•", location.absolute, color.new(color.blue,0), size = size.small)
plotchar(autumnalEquinoxCondition ? 0.0 : na, "Autumn Fall", "•", location.absolute, color.new(color.yellow,0), size = size.small)
+116
View File
@@ -0,0 +1,116 @@
# SSF-DSP: Super Smooth Filter Based Detrended Synthetic Price
[Pine Script Implementation of SSF-DSP](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ssfdsp.pine)
## Overview and Purpose
The Super Smooth Filter Based Detrended Synthetic Price (SSF-DSP) is an enhanced variant of John Ehlers' Detrended Synthetic Price that replaces the traditional EMA filters with Super Smooth Filters. This advanced implementation provides superior noise reduction and cleaner passband characteristics while maintaining the core band-pass filtering concept. By using SSF's optimized pole placement with complex conjugates, SSF-DSP achieves exceptional cycle isolation with minimal waveform distortion, making it particularly valuable for identifying dominant market cycles in moderately noisy conditions.
The indicator calculates the difference between a quarter-cycle SSF and a half-cycle SSF, creating a band-pass filter that isolates the dominant cycle component while removing both high-frequency noise and low-frequency trend. This mathematical relationship effectively detrends the price data, revealing the underlying cyclic structure that drives market oscillations.
## Core Concepts
* **Dual SSF Structure:** Uses two independent Super Smooth Filters at quarter-cycle and half-cycle periods derived from the dominant cycle
* **Band-Pass Filtering:** The difference between fast and slow SSFs creates a filter that passes the dominant cycle frequency while rejecting noise and trend
* **Enhanced Smoothing:** SSF's Butterworth-style response provides cleaner filtering than EMA-based DSP with better roll-off characteristics
* **Cycle Isolation:** Reveals the pure cyclic component of price movement by removing both short-term noise and long-term trend
* **Reduced Lag:** Despite heavier smoothing, SSF maintains reasonable lag characteristics due to optimized coefficient design
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Source | hlc3 | Price data used for calculation | hlc3 provides balanced price representation; close for directional bias |
| Period | 40 | Dominant cycle period in bars | Match to your identified dominant cycle (typically 20-50 bars for daily charts) |
**Pro Tip:** SSF-DSP provides cleaner signals than EMA-based DSP with ~1.5-2x more smoothing. If you use period=40 for regular DSP, try period=30-35 for SSF-DSP to achieve similar responsiveness with better noise rejection.
## Calculation and Mathematical Foundation
**Simplified explanation:**
SSF-DSP applies two Super Smooth Filters to the price data—one tuned to quarter of the dominant cycle period and one to half the period. The difference between these two filtered signals creates a band-pass effect that isolates the dominant cycle frequency while removing both high-frequency noise and low-frequency trend components.
**Technical formula:**
1. Calculate quarter-cycle and half-cycle periods:
```
Fast Period = Period / 4
Slow Period = Period / 2
```
2. Calculate SSF coefficients for each filter:
```
arg = √2π / Period
exp_arg = exp(-arg)
c2 = 2 × exp_arg × cos(arg)
c3 = -exp_arg²
c1 = 1 - c2 - c3
```
3. Apply SSF recursion for both filters:
```
SSF_fast = c1_fast × Price + c2_fast × SSF_fast[1] + c3_fast × SSF_fast[2]
SSF_slow = c1_slow × Price + c2_slow × SSF_slow[1] + c3_slow × SSF_slow[2]
```
4. Calculate the difference:
```
SSF-DSP = SSF_fast - SSF_slow
```
> 🔍 **Technical Note:** The √2 factor in SSF coefficient calculations creates a maximally flat Butterworth magnitude response, providing optimal smoothness in the passband. This results in cleaner cycle isolation compared to EMA-based DSP, which uses simple exponential weighting.
## Interpretation Details
SSF-DSP provides enhanced cycle analysis capabilities:
* **Zero-Line Crossovers:**
* Crossing above zero: Indicates cycle is in upward phase with improving momentum
* Crossing below zero: Indicates cycle is in downward phase with weakening momentum
* More reliable than EMA-DSP due to superior noise rejection
* **Peak and Trough Identification:**
* Peaks indicate cycle tops with cleaner signals than EMA-DSP
* Troughs indicate cycle bottoms with reduced false positives
* Peak-to-peak distance estimates the current cycle period
* **Amplitude Analysis:**
* Larger swings indicate stronger cyclic component in the market
* Decreasing amplitude suggests cycle is weakening or market entering consolidation
* Cleaner amplitude measurement than EMA-DSP
* **Divergence Detection:**
* Price making new highs while SSF-DSP makes lower highs: bearish divergence
* Price making new lows while SSF-DSP makes higher lows: bullish divergence
* More reliable divergence signals due to superior noise filtering
* **Cycle Phase Tracking:**
* Monitor position relative to zero to determine cycle phase
* Use in conjunction with HT_DCPERIOD for adaptive period selection
* Cleaner phase identification than EMA-based variant
## Limitations and Considerations
* **Increased Lag:** SSF introduces ~1.5-2x more lag than EMA while providing superior smoothing—may delay signals in fast-moving markets
* **Period Dependency:** Requires accurate dominant cycle period estimate for optimal performance
* **Initialization Period:** Needs more bars than EMA-DSP to stabilize (approximately 2× the period setting)
* **Computational Complexity:** Slightly more intensive than EMA-DSP due to trigonometric coefficient calculations (though still O(1) per bar)
* **Oversmoothing Risk:** In very choppy markets, excessive smoothing may reduce signal responsiveness
* **Best Suited For:** Moderately noisy markets where clean cycle isolation is priority over minimal lag
## Comparison to EMA-Based DSP
| Characteristic | SSF-DSP | EMA-DSP |
| ------ | ------ | ------ |
| Noise Rejection | Excellent | Good |
| Lag | Moderate | Low |
| Passband Ripple | Minimal | Moderate |
| Roll-off | Sharp | Gradual |
| Best For | Clean cycle isolation | Responsive trading |
| Computational | O(1) with trig | O(1) simple |
## References
* Ehlers, J.F. "Cycle Analytics for Traders," Wiley, 2013
* Ehlers, J.F. "Rocket Science for Traders," Wiley, 2001
* Ehlers, J.F. "Cybernetic Analysis for Stocks and Futures," Wiley, 2004
+64
View File
@@ -0,0 +1,64 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
indicator("SSF-Based Detrended Synthetic Price", "SSF-DSP", overlay=false)
//@function Calculates SSF-based Detrended Synthetic Price using dual Super Smooth Filters
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/ssfdsp.md
//@param source Series to detrend
//@param period Dominant cycle period for quarter/half-cycle SSF calculation
//@returns Detrended synthetic price (difference between quarter-cycle and half-cycle SSFs)
ssfdsp(series float source, simple int period) =>
if period <= 0
runtime.error("Period must be greater than 0")
int fast_period = math.max(2, int(math.round(period / 4.0)))
int slow_period = math.max(3, int(math.round(period / 2.0)))
float SQRT2_PI = math.sqrt(2.0) * math.pi
float arg_fast = SQRT2_PI / float(fast_period)
float exp_fast = math.exp(-arg_fast)
float c2_fast = 2.0 * exp_fast * math.cos(arg_fast)
float c3_fast = -exp_fast * exp_fast
float c1_fast = 1.0 - c2_fast - c3_fast
float arg_slow = SQRT2_PI / float(slow_period)
float exp_slow = math.exp(-arg_slow)
float c2_slow = 2.0 * exp_slow * math.cos(arg_slow)
float c3_slow = -exp_slow * exp_slow
float c1_slow = 1.0 - c2_slow - c3_slow
var float ssf_fast_1 = 0.0
var float ssf_fast_2 = 0.0
var int prev_fast_period = 0
var float ssf_slow_1 = 0.0
var float ssf_slow_2 = 0.0
var int prev_slow_period = 0
float current = nz(source)
float src_1 = nz(source[1], current)
float input = (current + src_1) * 0.5
if prev_fast_period != fast_period
ssf_fast_1 := input
ssf_fast_2 := input
prev_fast_period := fast_period
if prev_slow_period != slow_period
ssf_slow_1 := input
ssf_slow_2 := input
prev_slow_period := slow_period
float ssf_fast = c1_fast * input + c2_fast * ssf_fast_1 + c3_fast * ssf_fast_2
ssf_fast_2 := ssf_fast_1
ssf_fast_1 := ssf_fast
float ssf_slow = c1_slow * input + c2_slow * ssf_slow_1 + c3_slow * ssf_slow_2
ssf_slow_2 := ssf_slow_1
ssf_slow_1 := ssf_slow
ssf_fast - ssf_slow
// ---------- Main loop ----------
// Inputs
i_source = input.source(hlc3, "Source")
i_period = input.int(40, "Dominant Cycle Period", minval=4, maxval=200,
tooltip="Dominant cycle period. Quarter-cycle and half-cycle SSFs calculated from this value.")
// Calculation
ssfdsp_val = ssfdsp(i_source, i_period)
// Plot
plot(ssfdsp_val, "SSF-DSP", color=color.yellow, linewidth=2)
hline(0, "Zero Line", color=color.gray, linestyle=hline.style_solid)
+63
View File
@@ -0,0 +1,63 @@
# STC: Schaff Trend Cycle
[Pine Script Implementation of STC](https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/stc.pine)
## Overview and Purpose
The Schaff Trend Cycle (STC) is a technical indicator that combines elements of MACD and stochastic oscillators to identify market trends and potential reversal points. Developed by Doug Schaff in the 1990s, this indicator was designed to improve upon traditional momentum oscillators by enhancing cycle identification and reducing false signals. STC transforms the MACD line through a double stochastic process to create an oscillator that moves between 0 and 100, helping traders identify overbought and oversold conditions while maintaining trend sensitivity. Its unique construction makes it particularly effective at capturing market cycles while filtering out random price noise.
## Core Concepts
* **Hybrid oscillator design:** Combines the trend-following capabilities of MACD with the cyclical properties of stochastic indicators, creating a more responsive trend identification tool
* **Double stochastic processing:** Applies stochastic calculations twice to normalize the indicator and enhance cycle detection capabilities
* **Timeframe flexibility:** Works effectively across multiple timeframes, with adjustable parameters to suit different trading styles and market conditions
The core innovation of STC is its application of double stochastic processing to MACD values. This transformation effectively normalizes the MACD to create an oscillator with consistent boundaries, regardless of the underlying price volatility. By applying stochastic calculations twice, STC enhances cycle identification while reducing noise, creating sharper and more reliable signals than either MACD or traditional stochastic indicators alone.
## Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
| ------ | ------ | ------ | ------ |
| Cycle Length | 10 | Controls the lookback period for stochastic calculations | Increase for smoother signals in volatile markets, decrease for more responsiveness |
| Fast Length | 23 | Period for the fast EMA in MACD calculation | Adjust based on typical cycle duration in the instrument being traded |
| Slow Length | 50 | Period for the slow EMA in MACD calculation | Increase for longer-term trends, decrease for shorter-term analysis |
| Smoothing Type | EMA | Method used to smooth the final output | Choose based on personal preference for signal clarity vs. responsiveness |
**Pro Tip:** The 25-75 threshold pair works well for identifying potential reversals, but using 20-80 can reduce false signals in volatile markets at the cost of slightly later entries and exits.
## Calculation and Mathematical Foundation
**Simplified explanation:**
STC first calculates a MACD line, then transforms it using stochastic formulas—not once, but twice. This double transformation creates a smoother oscillator that moves between 0 and 100, making it easier to identify overbought and oversold conditions as well as potential turning points in the market.
**Technical formula:**
1. MACD = EMA(Source, Fast_Length) - EMA(Source, Slow_Length)
2. Stoch_1 = EMA((MACD - Lowest_MACD)/(Highest_MACD - Lowest_MACD) × 100, 3)
Where Lowest_MACD and Highest_MACD are over the Cycle_Length period
3. Stoch_2 = (Stoch_1 - Lowest_Stoch_1)/(Highest_Stoch_1 - Lowest_Stoch_1) × 100
Where Lowest_Stoch_1 and Highest_Stoch_1 are over the Cycle_Length period
> 🔍 **Technical Note:** The optional smoothing methods (None, EMA, Sigmoid, Digital) offer traders flexibility in signal presentation. While EMA provides balanced smoothing, the Sigmoid option creates distinct buy and sell zones, and Digital transforms the indicator into a binary signal for automated systems.
## Interpretation Details
STC can be used in various trading strategies:
* **Trend identification:** Values above 75 suggest a strong uptrend, while values below 25 indicate a strong downtrend
* **Reversal signals:** Crossovers of the 25 and 75 levels can signal potential market reversals
* **Divergence analysis:** Comparing STC movements with price can reveal potential trend exhaustion
* **Range-bound strategies:** Oscillations between 25 and 75 can provide entry and exit points in sideways markets
* **Cross-market analysis:** Using STC across correlated instruments can help identify leading and lagging markets
## Limitations and Considerations
* **False signals:** Can generate misleading signals during strong trends, particularly when using tighter thresholds
* **Parameter sensitivity:** Performance highly dependent on appropriate parameter selection for the specific market
* **Signal lag:** Multiple smoothing operations create inherent lag in signal generation
* **Optimization requirements:** Different markets and timeframes typically require different parameter settings
* **Complementary tools:** Best used alongside price action analysis and other indicators for confirmation
## References
* Schaff, D. "The Schaff Trend Cycle," Technical Analysis of Stocks & Commodities, 2008
* Murphy, J.J. "Technical Analysis of the Financial Markets," New York Institute of Finance, 1999
+74
View File
@@ -0,0 +1,74 @@
// The MIT License (MIT)
// © mihakralj
//@version=6
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)
var float raw_ema=na
var float ema=na
var float e=1.0
var bool warmup=true
if not na(source)
if na(raw_ema)
raw_ema:=0
ema:=source
else
raw_ema:=a*(source-raw_ema)+raw_ema
if warmup
e*=(1-a)
float c=1.0/(1.0-e)
ema:=c*raw_ema
if e<=1e-10
warmup:=false
else
ema:=raw_ema
ema
//@function Calculates the Schaff Trend Cycle (STC) indicator
//@doc https://github.com/mihakralj/pinescript/blob/main/indicators/cycles/stc.md
//@param source Input price series
//@param cycleLength Main cycle length parameter for lookback periods
//@param fastLength Period for fast EMA calculation
//@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) =>
float fast_ema = ema(source, fastLength)
float slow_ema = ema(source, slowLength)
float macdLine = fast_ema - slow_ema
h1 = ta.highest(macdLine, cycleLength)
l1 = ta.lowest(macdLine, cycleLength)
float stoch1_raw = (h1 - l1) > 0 ? 100 * (macdLine - l1) / (h1 - l1) : 0
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 stcValue = stoch2
if smoothingType == 1
stcValue := ema(stoch2, 3)
else if smoothingType == 2
stcValue := 100 / (1 + math.exp(-0.1 * (stcValue - 50)))
else if smoothingType == 3
stcValue := stcValue > 75 ? 100 : stcValue < 25 ? 0 : stcValue[1]
stcValue
// ---------- Main loop ----------
// 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_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")
// Calculation
stcValue = stc(i_source, i_cycleLength, i_fastLength, i_slowLength, i_smoothingType)
// Plot
plot(stcValue, "STC", color=color.yellow, linewidth=2)