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

4.4 KiB
Raw Blame History

HOMOD: Homodyne Discriminator

"The homodyne discriminator reveals instantaneous frequency by multiplying a signal with its delayed self — the phase rotation between samples directly encodes the cycle period."

The Homodyne Discriminator (HOMOD) estimates the dominant cycle period of a market using homodyne mixing—multiplying the signal by a delayed version of itself. This technique exposes the angular phase change between bars, allowing calculation of the instantaneous period at every time step.

Historical Context

In Rocket Science for Traders and Cybernetic Analysis for Stocks and Futures, John Ehlers introduced signal processing concepts novel to technical analysis. The Homodyne Discriminator was presented as a superior alternative to the Hilbert Transform Discriminator for cycle measurement.

It offers better noise rejection and stability while maintaining reasonable responsiveness, making it practical for real-time trading applications.

Architecture & Physics

The algorithm is a complex pipeline of filters and transformations designed to isolate the analytic signal.

1. Pre-Processing (4-Bar WMA)


Smooth = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}

2. Analytic Signal Generation

In-Phase (I) and Quadrature (Q) components via Hilbert Transform:


I_2 = I_1 - JQ

Q_2 = Q_1 + JI

Smoothed with EMA (α = 0.2).

3. Homodyne Mixing

Multiplying complex signal z_t by its conjugate delayed by one bar:


Real = (I_2 \cdot I_{2,prev}) + (Q_2 \cdot Q_{2,prev})

Imag = (I_2 \cdot Q_{2,prev}) - (Q_2 \cdot I_{2,prev})

4. Period Extraction


\theta = \operatorname{atan2}(Imag, Real)

Period = \frac{2\pi}{\theta}

Clamped to [MinPeriod, MaxPeriod] and smoothed.

Performance Profile

Operation Count (Streaming Mode, per Bar)

Operation Count Cost (cycles) Subtotal
MUL (Hilbert taps) 14 3 42
MUL (homodyne mix) 4 3 12
ADD/SUB 20 1 20
ATAN2 1 25 25
DIV 2 15 30
Total 41 ~129 cycles

Complexity Analysis

  • Streaming: O(1) per bar—fixed filter depth
  • Memory: O(1)—state struct with history variables
  • Warmup: ~2 × MaxPeriod bars for convergence

Validation

Library Status Notes
TA-Lib N/A Not implemented
Skender N/A Not implemented
PineScript Matches Ehlers' reference code

Usage & Pitfalls

  • Output is period in bars—not an oscillator like RSI, but a measurement like ATR
  • Long settling time (~2 × MaxPeriod)—early values unreliable
  • Trending markets make "cycle" ill-defined—period drifts to MaxPeriod
  • Check for cycling (ADX or trend filter) before trusting period values
  • High noise causes jitter—pre-smooth extremely noisy data
  • Use for adaptive tuning: Stochastic(length: homod.DominantCycle)

API

classDiagram
    class Homod {
        +double MinPeriod
        +double MaxPeriod
        +double DominantCycle
        +bool IsHot
        +Homod(double minPeriod, double maxPeriod)
        +Homod(ITValuePublisher source, double minPeriod, double maxPeriod)
        +TValue Update(TValue input, bool isNew)
        +void Reset()
    }

Class: Homod

Parameter Type Default Range Description
minPeriod double 6.0 >0 Minimum period to detect
maxPeriod double 50.0 >minPeriod Maximum period to detect

Properties

  • DominantCycle (double): Current dominant cycle period in bars
  • IsHot (bool): Returns true when warmup is complete

Methods

  • Update(TValue input, bool isNew): Updates the indicator with a new data point

C# Example

using QuanTAlib;

// Configure for cycles between 6 and 50 bars
var homod = new Homod(minPeriod: 6, maxPeriod: 50);

// Update with streaming data
foreach (var bar in quotes)
{
    var result = homod.Update(new TValue(bar.Date, bar.Close));
    
    if (homod.IsHot)
    {
        double period = homod.DominantCycle;
        Console.WriteLine($"{bar.Date}: Dominant Cycle = {period:F1} bars");
        
        // Use cycle to tune Stochastic
        int adaptiveLength = (int)Math.Round(period);
        var adaptiveStoch = new Stochastic(adaptiveLength);
    }
}

// Batch calculation
var output = Homod.Calculate(sourceSeries, minPeriod: 6, maxPeriod: 50);