- 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.
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);