- 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.
3.9 KiB
DSP: Detrended Synthetic Price
"Remove the trend, reveal the cycles."
The Detrended Synthetic Price (DSP) indicator creates a zero-centered oscillator by subtracting a half-cycle EMA from a quarter-cycle EMA. Developed by John Ehlers, this "synthetic" price highlights underlying cyclical movement, identifying momentum shifts when the faster EMA crosses the slower one.
Historical Context
John Ehlers introduced the DSP as part of his research into cycle analytics for traders. While many indicators (like MACD) use arbitrary periods (12/26), DSP is grounded in cycle theory. Ehlers posits that to effectively isolate a cycle, one should filter data based on the dominant cycle period.
The use of period/4 and period/2 roughly corresponds to extracting the cycle's momentum while cancelling out longer-term trends. This makes DSP particularly effective for cycle-based trading strategies.
Architecture & Physics
DSP utilizes a dual EMA architecture, calibrated to specific fractions of the cycle period.
1. Component Periods
P_{fast} = \max(2, \text{round}(P / 4))
P_{slow} = \max(3, \text{round}(P / 2))
2. Alpha Coefficients
\alpha_{fast} = \frac{2}{P_{fast} + 1}
\alpha_{slow} = \frac{2}{P_{slow} + 1}
3. EMA Updates (with Bias Correction)
EMA_{raw} = \alpha \cdot Price + (1 - \alpha) \cdot EMA_{raw\_prev}
EMA_{corrected} = \frac{EMA_{raw}}{1 - (1-\alpha)^n}
4. DSP Calculation
DSP = EMA_{fast} - EMA_{slow}
Performance Profile
Operation Count (Streaming Mode, per Bar)
| Operation | Count | Cost (cycles) | Subtotal |
|---|---|---|---|
| FMA (EMA updates) | 2 | 4 | 8 |
| MUL (decay factors) | 2 | 3 | 6 |
| DIV (bias correction) | 2 | 15 | 30 |
| SUB (DSP = fast - slow) | 1 | 1 | 1 |
| Total | 7 | — | ~45 cycles |
Complexity Analysis
- Streaming: O(1) per bar—fixed calculation depth
- Memory: O(1)—only EMA state variables
- Warmup: ~2 × slow period for convergence
Validation
| Library | Status | Notes |
|---|---|---|
| TA-Lib | N/A | Not standard |
| Skender | N/A | Not standard |
| PineScript | ✅ | Matches Ehlers' reference logic |
Usage & Pitfalls
- Zero crossing indicates cycle phase change—above zero is bullish, below zero is bearish
- Period should match market cycle—if market cycle is 20 bars, use period 20 not 40
- Not normalized—amplitude reflects absolute price difference, varies by asset
- Whipsaws occur in ranging markets with cycles shorter than the setting
- Divergence (higher price highs with lower DSP highs) suggests cycle energy loss
- Use FusedMultiplyAdd for optimal precision in EMA recursion
API
classDiagram
class Dsp {
+int Period
+double Value
+bool IsHot
+Dsp(int period)
+Dsp(ITValuePublisher source, int period)
+TValue Update(TValue input, bool isNew)
+void Reset()
}
Class: Dsp
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
period |
int |
40 |
≥4 |
Dominant cycle period |
Properties
Value(double): The current DSP value (oscillates around 0)IsHot(bool): Returnstruewhen warmup is complete
Methods
Update(TValue input, bool isNew): Updates the indicator with a new data point
C# Example
using QuanTAlib;
// Create DSP for a 40-bar cycle
var dsp = new Dsp(period: 40);
// Update with streaming data
foreach (var bar in quotes)
{
var result = dsp.Update(new TValue(bar.Date, bar.Close));
if (dsp.IsHot)
{
Console.WriteLine($"{bar.Date}: DSP = {result.Value:F4}");
// Cycle phase detection
if (result.Value > 0)
Console.WriteLine(" → Bullish cycle phase");
else
Console.WriteLine(" → Bearish cycle phase");
}
}
// Batch calculation
var output = Dsp.Calculate(sourceSeries, period: 40);