2026-02-04 11:43:59 -08:00
# HOMOD: Homodyne Discriminator
2026-01-18 19:02:03 -08:00
2026-02-04 11:43:59 -08:00
> "The homodyne discriminator reveals instantaneous frequency by multiplying a signal with its delayed self — the phase rotation between samples directly encodes the cycle period."
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
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.
2026-01-18 19:02:03 -08:00
2026-02-04 11:43:59 -08:00
## Historical Context
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
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.
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
It offers better noise rejection and stability while maintaining reasonable responsiveness, making it practical for real-time trading applications.
2026-01-18 19:02:03 -08:00
2026-02-04 11:43:59 -08:00
## Architecture & Physics
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
The algorithm is a complex pipeline of filters and transformations designed to isolate the analytic signal.
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
### 1. Pre-Processing (4-Bar WMA)
2026-01-18 19:02:03 -08:00
2026-02-04 11:43:59 -08:00
$$
2026-02-05 19:42:49 -08:00
Smooth = \frac{4P_t + 3P_{t-1} + 2P_{t-2} + P_{t-3}}{10}
2026-02-04 11:43:59 -08:00
$$
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
### 2. Analytic Signal Generation
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
In-Phase (I) and Quadrature (Q) components via Hilbert Transform:
2026-01-18 19:02:03 -08:00
2026-02-04 11:43:59 -08:00
$$
2026-02-05 19:42:49 -08:00
I_2 = I_1 - JQ
2026-02-04 11:43:59 -08:00
$$
$$
2026-02-05 19:42:49 -08:00
Q_2 = Q_1 + JI
2026-02-04 11:43:59 -08:00
$$
2026-02-05 19:42:49 -08:00
Smoothed with EMA (α = 0.2).
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
### 3. Homodyne Mixing
Multiplying complex signal $z_t$ by its conjugate delayed by one bar:
2026-02-04 11:43:59 -08:00
$$
2026-02-05 19:42:49 -08:00
Real = (I_2 \cdot I_{2,prev}) + (Q_2 \cdot Q_{2,prev})
2026-02-04 11:43:59 -08:00
$$
$$
2026-02-05 19:42:49 -08:00
Imag = (I_2 \cdot Q_{2,prev}) - (Q_2 \cdot I_{2,prev})
2026-02-04 11:43:59 -08:00
$$
2026-02-05 19:42:49 -08:00
### 4. Period Extraction
2026-02-04 11:43:59 -08:00
$$
2026-02-05 19:42:49 -08:00
\theta = \operatorname{atan2}(Imag, Real)
2026-02-04 11:43:59 -08:00
$$
$$
2026-02-05 19:42:49 -08:00
Period = \frac{2\pi}{\theta}
2026-02-04 11:43:59 -08:00
$$
2026-02-05 19:42:49 -08:00
Clamped to [MinPeriod, MaxPeriod] and smoothed.
2026-01-18 19:02:03 -08:00
## Performance Profile
2026-02-05 19:42:49 -08:00
### Operation Count (Streaming Mode, per Bar)
2026-01-18 19:02:03 -08:00
| Operation | Count | Cost (cycles) | Subtotal |
| :--- | :---: | :---: | :---: |
2026-02-05 19:42:49 -08:00
| 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** |
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
### Complexity Analysis
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
- **Streaming:** O(1) per bar—fixed filter depth
- **Memory:** O(1)—state struct with history variables
- **Warmup:** ~2 × MaxPeriod bars for convergence
2026-02-04 11:43:59 -08:00
## Validation
| Library | Status | Notes |
| :--- | :---: | :--- |
2026-02-05 19:42:49 -08:00
| TA-Lib | N/A | Not implemented |
| Skender | N/A | Not implemented |
| PineScript | ✅ | Matches Ehlers' reference code |
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
## Usage & Pitfalls
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
- **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)`
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
## API
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
```mermaid
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()
}
```
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
### Class: `Homod`
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
| Parameter | Type | Default | Range | Description |
| :--- | :--- | :--- | :--- | :--- |
| `minPeriod` | `double` | `6.0` | `>0` | Minimum period to detect |
| `maxPeriod` | `double` | `50.0` | `>minPeriod` | Maximum period to detect |
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
### Properties
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
- `DominantCycle` (`double` ): Current dominant cycle period in bars
- `IsHot` (`bool` ): Returns `true` when warmup is complete
2026-02-04 11:43:59 -08:00
2026-02-05 19:42:49 -08:00
### Methods
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
- `Update(TValue input, bool isNew)` : Updates the indicator with a new data point
2026-01-18 19:02:03 -08:00
2026-02-05 19:42:49 -08:00
## C# Example
```csharp
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 );
```