Files
QuanTAlib/lib/cycles/ht_sine/HtSine.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

6.0 KiB

HT_SINE: Hilbert Transform SineWave

"The Hilbert Transform gives us the phase of the dominant cycle—knowing when to buy and sell becomes a matter of trigonometry."

The Hilbert Transform SineWave extracts the dominant market cycle phase and outputs both sine and lead sine (45° phase advance) for cycle timing. The crossover of these two waves identifies turning points in ranging markets up to 1/8th of a cycle early.

Historical Context

John Ehlers introduced the Hilbert Transform SineWave in Rocket Science for Traders (2001) as part of his comprehensive signal processing framework for financial markets. The indicator addresses a fundamental limitation of traditional oscillators—they respond to price amplitude rather than cycle phase.

The HT_SINE builds upon David Hilbert's 1905 mathematical transform, which creates a 90° phase-shifted (quadrature) version of a signal. In signal processing, this enables instantaneous frequency and phase extraction. Ehlers recognized that market cycles, though noisy and variable, could be analyzed using these same techniques.

Unlike momentum oscillators that lag price action, the HT_SINE theoretically provides zero-lag cycle detection by measuring phase directly. This makes it particularly valuable in ranging markets where cycles are well-defined. The dual output (Sine and LeadSine) creates a built-in early warning system for cycle reversals.

Architecture & Physics

The algorithm implements a discrete approximation of the Hilbert Transform optimized for financial time series with adaptive period estimation.

Step 1: WMA Smoothing

A 4-bar weighted moving average removes Nyquist-frequency noise:

\bar{P}_t = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}

Step 2: Hilbert Transform FIR

The discrete Hilbert approximation generates quadrature components:

\text{Detrender}_t = 0.0962\bar{P}_t + 0.5769\bar{P}_{t-2} - 0.5769\bar{P}_{t-4} - 0.0962\bar{P}_{t-6}

Step 3: I/Q Component Smoothing

In-phase and quadrature components undergo exponential smoothing:

Q_t = 0.2(Q1_t + JI_t) + 0.8 Q_{t-1} I_t = 0.2(I1_t - JQ_t) + 0.8 I_{t-1}

Step 4: Homodyne Discriminator

Period estimation uses phase rate of change:

Re_t = 0.2(I_t \cdot I_{t-1} + Q_t \cdot Q_{t-1}) + 0.8 Re_{t-1} Im_t = 0.2(I_t \cdot Q_{t-1} - Q_t \cdot I_{t-1}) + 0.8 Im_{t-1} \text{Period}_t = \frac{2\pi}{\arctan(Im_t / Re_t)}

Step 5: DC Phase Calculation

The dominant cycle phase sums weighted contributions:

\phi_t = \arctan\left(\frac{\sum_{i=0}^{P-1} \sin(2\pi i/P) \cdot \bar{P}_{t-i}}{\sum_{i=0}^{P-1} \cos(2\pi i/P) \cdot \bar{P}_{t-i}}\right)

Step 6: Output Generation

\text{Sine}_t = \sin(\phi_t) \text{LeadSine}_t = \sin(\phi_t + 45°)

Performance Profile

Operation Count (Streaming Mode, per Bar)

Operation Count Cost (cycles) Subtotal
FMA 12 5 60
MUL 18 4 72
ADD/SUB 25 1 25
DIV 2 15 30
sin/cos 2P 40 ~80P
atan 2 50 100
Buffer access 15 3 45
Total ~370

Complexity Analysis

  • Time: O(P) per bar where P is smoothed period (typically 6-50)
  • Space: O(1) — fixed-size circular buffers (50 + 44 + 64 elements)
  • Latency: 63 bars warmup (31 + 32 for TA-Lib compatibility)

Validation

Library Status Notes
TA-Lib Match TA_HT_SINE() reference implementation
PineScript Match Custom ht_sine.pine validation script
Quantower Match HtSine.Quantower.Tests.cs adapter tests

Usage & Pitfalls

  • Trend Failure: Crossover signals whipsaw in strong trends; parallel "snake" pattern indicates trending mode
  • Warmup Period: Requires 63 bars before outputs stabilize
  • Phase Lag: Despite "zero-lag" theory, smoothing introduces 4-6 bars practical lag
  • Range-Only: Most effective in sideways/ranging markets with clear cyclical behavior
  • LeadSine First: LeadSine turns before Sine at reversals—watch for divergence

API

classDiagram
    class AbstractBase {
        <<abstract>>
        +Name string
        +WarmupPeriod int
        +IsHot bool
        +Last TValue
        +Update(TValue input, bool isNew) TValue
        +Reset() void
    }
    class HtSine {
        +LeadSine double
        +HtSine()
        +HtSine(ITValuePublisher source)
        +Update(TValue input, bool isNew) TValue
        +Update(TSeries source) TSeries
        +Prime(ReadOnlySpan~double~ source, TimeSpan? step) void
        +Reset() void
        +Calculate(TSeries source)$ TSeries
        +Batch(ReadOnlySpan~double~ source, Span~double~ sine, Span~double~ leadSine)$ void
    }
    AbstractBase <|-- HtSine

Class: HtSine

Hilbert Transform SineWave indicator with dual output.

Properties

Name Type Description
LeadSine double Current LeadSine value (45° phase lead)
IsHot bool True after 63 bars warmup
Last TValue Most recent Sine output

Methods

Name Returns Description
Update(TValue, bool) TValue Updates state with new price value
Batch(source, sine, leadSine) void Processes span with dual output spans
Calculate(TSeries) TSeries Static factory returning Sine series

C# Example

using QuanTAlib;

// Create HT_SINE indicator
var htSine = new HtSine();

// Process price data
foreach (var bar in bars)
{
    var result = htSine.Update(new TValue(bar.Time, bar.Close));
    
    if (htSine.IsHot)
    {
        double sine = result.Value;
        double leadSine = htSine.LeadSine;
        
        // Crossover detection
        // Buy: Sine crosses above LeadSine
        // Sell: Sine crosses below LeadSine
        Console.WriteLine($"Sine: {sine:F4}, LeadSine: {leadSine:F4}");
    }
}

// Batch processing with dual outputs
Span<double> sineOut = stackalloc double[prices.Length];
Span<double> leadOut = stackalloc double[prices.Length];
HtSine.Batch(prices, sineOut, leadOut);