Files
QuanTAlib/lib/cycles/homod/homod.md
T

156 lines
4.4 KiB
Markdown
Raw Normal View 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
```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()
}
```
### 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
```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);
```