Files
QuanTAlib/lib/cycles/sine/Sine.md
T
Miha Kralj 26280ce80b Add Choppiness Index (CHOP) implementation and tests
- Implemented ChopIndicator for Quantower with configurable period and cold value display.
- Created Chop class for calculating the Choppiness Index with detailed documentation.
- Added comprehensive unit tests for Chop functionality, covering various market conditions and edge cases.
- Developed markdown documentation for CHOP, detailing its historical context, mathematical foundation, and usage examples.
- Established a remediation plan for channel indicators documentation, identifying gaps and prioritizing updates.
2026-02-05 19:42:49 -08:00

5.8 KiB

SINE: Ehlers Sine Wave

"The sine wave extraction reveals what moving averages obscure—the pure rhythmic heartbeat of price action."

The Ehlers Sine Wave extracts the dominant cycle from price data using cascaded signal processing: high-pass detrending, super-smoother noise reduction, and Hilbert Transform quadrature decomposition. Output oscillates between -1 and +1, representing the normalized position within the current cycle.

Historical Context

John Ehlers introduced the Sine Wave indicator in Cybernetic Analysis for Stocks and Futures (2004) as a refined approach to cycle extraction. Unlike the HT_SINE which derives phase from raw Hilbert Transform output, this implementation adds explicit detrending and smoothing stages for cleaner cycle isolation.

The design philosophy separates three signal processing concerns: (1) trend removal via high-pass filtering, (2) aliasing prevention via super-smoothing, and (3) cycle extraction via Hilbert Transform. This staged approach produces cleaner output than attempting all three simultaneously.

The Sine Wave is particularly valuable in mean-reverting strategies. When the cycle position reaches extremes (-1 or +1), it suggests the cyclical component is stretched and likely to revert. Zero crossings indicate phase transitions—potential entry/exit points in the cycle.

Architecture & Physics

The algorithm cascades three distinct filter stages with carefully tuned frequency responses.

Step 1: High-Pass Filter (Detrending)

A single-pole high-pass filter removes low-frequency trends below the cutoff period:

\alpha_{HP} = \frac{1 - \sin(2\pi/P_{HP})}{\cos(2\pi/P_{HP})} HP_t = \frac{1 + \alpha_{HP}}{2}(P_t - P_{t-1}) + \alpha_{HP} \cdot HP_{t-1}

Step 2: Super-Smoother Filter

A 2-pole Butterworth low-pass filter removes high-frequency noise:

a = e^{-\sqrt{2}\pi/P_{SSF}} b = 2a\cos(\sqrt{2}\pi/P_{SSF}) c_1 = 1 - b + a^2, \quad c_2 = b, \quad c_3 = -a^2 \text{Filt}_t = c_1 \cdot \frac{HP_t + HP_{t-1}}{2} + c_2 \cdot \text{Filt}_{t-1} + c_3 \cdot \text{Filt}_{t-2}

Step 3: Hilbert Transform FIR

Discrete Hilbert approximation extracts quadrature component:

Q_t = 0.0962 \cdot \text{Filt}_{t-3} + 0.5769 \cdot \text{Filt}_{t-1} - 0.5769 \cdot \text{Filt}_{t-5} - 0.0962 \cdot \text{Filt}_{t-7} I_t = \text{Filt}_t

Step 4: Power Normalization

\text{Power}_t = I_t^2 + Q_t^2 \text{Sine}_t = \frac{I_t}{\sqrt{\text{Power}_t}}

Performance Profile

Operation Count (Streaming Mode, per Bar)

Operation Count Cost (cycles) Subtotal
FMA 6 5 30
MUL 8 4 32
ADD/SUB 12 1 12
SQRT 1 15 15
Buffer access 10 3 30
Total ~120

Complexity Analysis

  • Time: O(1) per bar — fixed filter stages
  • Space: O(1) — ring buffers: 2 (src) + 2 (hp) + 8 (filt) = 12 elements
  • Latency: max(hpPeriod, ssfPeriod) + 8 bars warmup

Validation

Library Status Notes
Ehlers Reference Match Cybernetic Analysis algorithm verified
Synthetic Chirp Pass Locks onto dominant frequency in passband
Quantower Match Sine.Quantower.Tests.cs adapter tests

Usage & Pitfalls

  • Trending Markets: Strong trends cause erratic output or extremum pegging
  • Period Tuning: hpPeriod defines trend/cycle boundary; ssfPeriod removes aliasing noise
  • Ratio Rule: Typically ssfPeriod = hpPeriod / 4 to hpPeriod / 2
  • Reversal Signals: Extremes near ±1 often precede reversals in ranging markets
  • Zero Crossing: Phase transition point—potential entry/exit signal
  • Single Output: Unlike HT_SINE, provides only Sine (no LeadSine)

API

classDiagram
    class AbstractBase {
        <<abstract>>
        +Name string
        +WarmupPeriod int
        +IsHot bool
        +Last TValue
        +Update(TValue input, bool isNew) TValue
        +Reset() void
    }
    class Sine {
        +HpPeriod int
        +SsfPeriod int
        +Sine(int hpPeriod, int ssfPeriod)
        +Sine(ITValuePublisher source, int hpPeriod, int ssfPeriod)
        +Update(TValue input, bool isNew) TValue
        +Update(TSeries source) TSeries
        +Prime(ReadOnlySpan~double~ source, TimeSpan? step) void
        +Reset() void
        +Calculate(TSeries source, int hpPeriod, int ssfPeriod)$ TSeries
    }
    AbstractBase <|-- Sine

Class: Sine

Ehlers Sine Wave indicator with configurable filter periods.

Properties

Name Type Description
HpPeriod int High-pass filter cutoff period
SsfPeriod int Super-smoother filter period
IsHot bool True after warmup complete
Last TValue Most recent Sine output (-1 to +1)

Methods

Name Returns Description
Update(TValue, bool) TValue Updates state with new price value
Calculate(TSeries, hp, ssf) TSeries Static factory with custom periods
Reset() void Clears all filter state

C# Example

using QuanTAlib;

// Create Sine indicator with default periods (40, 10)
var sine = new Sine(hpPeriod: 40, ssfPeriod: 10);

// Process price data
foreach (var bar in bars)
{
    var result = sine.Update(new TValue(bar.Time, bar.Close));
    
    if (sine.IsHot)
    {
        double sineValue = result.Value;
        
        // Cycle position interpretation
        // +1.0 = cycle peak (potential short)
        // -1.0 = cycle trough (potential long)
        //  0.0 = mid-cycle transition
        
        if (sineValue > 0.9)
            Console.WriteLine("Near cycle peak");
        else if (sineValue < -0.9)
            Console.WriteLine("Near cycle trough");
    }
}

// Static calculation
var sineResults = Sine.Calculate(prices, hpPeriod: 48, ssfPeriod: 12);