mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-22 12:38:06 +00:00
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.
This commit is contained in:
+80
-133
@@ -1,202 +1,149 @@
|
||||
# SSFDSP: Super Smooth Filter Detrended Synthetic Price
|
||||
# SSFDSP: SSF-Based Detrended Synthetic Price
|
||||
|
||||
> "The Super Smoother does what its name implies—it smooths without adding the lag penalty that haunts lesser filters."
|
||||
> "The Super-Smoother filter provides Butterworth-quality noise rejection—combine two of them and you isolate cycles with surgical precision."
|
||||
|
||||
SSF-DSP applies John Ehlers' Super Smooth Filter (SSF) as a detrending mechanism, subtracting a slow SSF from a fast SSF to isolate cyclical components. Where the original DSP uses dual EMAs, SSF-DSP substitutes 2-pole Butterworth-derived filters that reject high-frequency noise more aggressively while maintaining phase fidelity. The result oscillates around zero with reduced whipsaw in choppy conditions.
|
||||
The SSF-Based Detrended Synthetic Price (SSFDSP) is an advanced oscillator by John Ehlers. It creates a synthetic, detrended price series by subtracting a half-cycle Super-Smoother from a quarter-cycle Super-Smoother, providing superior noise rejection and reduced lag compared to EMA-based DSP.
|
||||
|
||||
## Historical Context
|
||||
|
||||
John Ehlers introduced the Super Smoother Filter in his 2013 book *Cycle Analytics for Traders*. The SSF represents Ehlers' effort to create a filter with the smoothness of higher-order IIR filters without excessive lag. By using a 2-pole Butterworth-style design with coefficients derived from the cutoff period, SSF achieves superior noise rejection compared to EMAs of equivalent lag.
|
||||
Ehlers introduced the concept of "Synthetic Price" to remove the DC (trend) component from market data, isolating cyclic energy. While earlier versions used EMAs, the SSF variant exploits the 2-pole Butterworth characteristics of the Super-Smoother Filter to achieve cleaner separation between trend and cycle.
|
||||
|
||||
The Detrended Synthetic Price concept—subtracting a slower smoothed series from a faster one—predates SSF. The innovation here combines the detrending approach with SSF's superior frequency response. Where EMA-based DSP suffers from high-frequency bleed-through, SSF-DSP provides cleaner cycle extraction.
|
||||
The SSF provides zero phase lag at the cutoff frequency, making it ideal for cycle isolation in noisy market data.
|
||||
|
||||
## Architecture & Physics
|
||||
|
||||
### 1. Period Decomposition
|
||||
The indicator computes the difference between two Super-Smoother filters tuned to fractions of the dominant cycle period.
|
||||
|
||||
The single `period` parameter decomposes into two cutoff frequencies:
|
||||
### 1. Filter Periods
|
||||
|
||||
$$
|
||||
\text{fastPeriod} = \max\left(2, \left\lfloor \frac{P}{4} \right\rfloor\right)
|
||||
P_{fast} = \max(2, \text{round}(P / 4))
|
||||
$$
|
||||
|
||||
$$
|
||||
\text{slowPeriod} = \max\left(3, \left\lfloor \frac{P}{2} \right\rfloor\right)
|
||||
P_{slow} = \max(3, \text{round}(P / 2))
|
||||
$$
|
||||
|
||||
The floor operation and minimum bounds ensure valid filter coefficients even for small periods. Fast period captures quarter-cycle oscillations; slow period captures half-cycle trends.
|
||||
|
||||
### 2. SSF Coefficient Derivation
|
||||
|
||||
Each SSF uses identical coefficient formulas with different periods:
|
||||
### 2. Super-Smoother Coefficients
|
||||
|
||||
$$
|
||||
\omega = \frac{\sqrt{2} \cdot \pi}{P_{cutoff}}
|
||||
\alpha = \frac{\pi\sqrt{2}}{period}
|
||||
$$
|
||||
|
||||
$$
|
||||
c_2 = 2 \cdot e^{-\omega} \cdot \cos(\omega)
|
||||
c_2 = 2e^{-\alpha}\cos(\alpha)
|
||||
$$
|
||||
|
||||
$$
|
||||
c_3 = -e^{-2\omega}
|
||||
c_3 = -e^{-2\alpha}
|
||||
$$
|
||||
|
||||
$$
|
||||
c_1 = 1 - c_2 - c_3
|
||||
$$
|
||||
|
||||
The $\sqrt{2}$ factor originates from Butterworth filter design, ensuring maximally flat passband response. The exponential-cosine product creates the characteristic 2-pole rolloff.
|
||||
|
||||
### 3. IIR Recursion
|
||||
|
||||
Each SSF applies the standard 2-pole recursion:
|
||||
### 3. SSF Recursion
|
||||
|
||||
$$
|
||||
\text{SSF}_t = c_1 \cdot x_t + c_2 \cdot \text{SSF}_{t-1} + c_3 \cdot \text{SSF}_{t-2}
|
||||
SSF_t = c_1 \cdot \frac{P_t + P_{t-1}}{2} + c_2 \cdot SSF_{t-1} + c_3 \cdot SSF_{t-2}
|
||||
$$
|
||||
|
||||
where $x_t$ is the current input price. The recursion maintains two bars of history for each filter.
|
||||
|
||||
### 4. Detrending Operation
|
||||
|
||||
The final output removes trend by differencing:
|
||||
### 4. SSFDSP Output
|
||||
|
||||
$$
|
||||
\text{SSFDSP}_t = \text{SSF}_{fast,t} - \text{SSF}_{slow,t}
|
||||
SSFDSP = SSF_{fast} - SSF_{slow}
|
||||
$$
|
||||
|
||||
This produces a zero-centered oscillator. When price rises faster than the slow filter can track, SSFDSP goes positive. When price momentum fades, SSFDSP returns toward zero.
|
||||
|
||||
## Mathematical Foundation
|
||||
|
||||
### Transfer Function
|
||||
|
||||
Each SSF has the z-domain transfer function:
|
||||
|
||||
$$
|
||||
H(z) = \frac{c_1}{1 - c_2 z^{-1} - c_3 z^{-2}}
|
||||
$$
|
||||
|
||||
The combined system (fast minus slow) creates a bandpass-like response, attenuating both very high frequencies (rejected by both filters) and very low frequencies (canceled by the differencing operation).
|
||||
|
||||
### Frequency Response
|
||||
|
||||
The -3dB cutoff frequency for each SSF:
|
||||
|
||||
$$
|
||||
f_{cutoff} = \frac{1}{P_{cutoff}}
|
||||
$$
|
||||
|
||||
The bandpass center frequency falls approximately between the fast and slow cutoffs:
|
||||
|
||||
$$
|
||||
f_{center} \approx \frac{1}{2} \left( \frac{1}{P_{fast}} + \frac{1}{P_{slow}} \right)
|
||||
$$
|
||||
|
||||
### Warmup Period
|
||||
|
||||
The filter requires warmup before producing stable output. Given the 2-pole recursive structure:
|
||||
|
||||
$$
|
||||
\text{WarmupPeriod} = P_{slow}
|
||||
$$
|
||||
|
||||
During warmup, the filter uses available history to bootstrap state, but outputs should be considered unreliable until `IsHot = true`.
|
||||
|
||||
## Performance Profile
|
||||
|
||||
### Operation Count (Streaming Mode, Scalar)
|
||||
### Operation Count (Streaming Mode, per Bar)
|
||||
|
||||
| Operation | Count | Cost (cycles) | Subtotal |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| MUL | 6 | 3 | 18 |
|
||||
| ADD/SUB | 5 | 1 | 5 |
|
||||
| State load/store | 8 | 1 | 8 |
|
||||
| FMA candidates | 4 | 4→3 | 12→9 |
|
||||
| **Total** | — | — | **~28 cycles** |
|
||||
| FMA (SSF updates) | 4 | 4 | 16 |
|
||||
| MUL (coefficients) | 2 | 3 | 6 |
|
||||
| ADD/SUB (input avg, output) | 3 | 1 | 3 |
|
||||
| **Total** | **9** | — | **~25 cycles** |
|
||||
|
||||
Dominant cost: coefficient multiplications. FMA optimization reduces 2 MUL+ADD pairs per SSF to single FMA operations.
|
||||
### Complexity Analysis
|
||||
|
||||
### State Memory
|
||||
|
||||
| Component | Size |
|
||||
| :--- | :---: |
|
||||
| Fast SSF state (2 doubles) | 16 bytes |
|
||||
| Slow SSF state (2 doubles) | 16 bytes |
|
||||
| Tick counter | 4 bytes |
|
||||
| Last valid input | 8 bytes |
|
||||
| **Total per instance** | **~48 bytes** |
|
||||
|
||||
### Quality Metrics
|
||||
|
||||
| Metric | Score | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| **Accuracy** | 9/10 | Exact SSF formula; matches PineScript reference |
|
||||
| **Timeliness** | 8/10 | Lower lag than EMA-based DSP for equivalent smoothing |
|
||||
| **Overshoot** | 7/10 | 2-pole design has mild overshoot on step inputs |
|
||||
| **Smoothness** | 9/10 | Superior noise rejection vs EMA |
|
||||
| **Cycle Fidelity** | 8/10 | Good phase preservation; minor amplitude distortion at extremes |
|
||||
- **Streaming:** O(1) per bar—fixed 2-pole IIR filters
|
||||
- **Memory:** O(1)—only filter state variables
|
||||
- **Warmup:** ~2 × slow period for convergence
|
||||
- **Note:** Recursive dependencies prevent SIMD vectorization
|
||||
|
||||
## Validation
|
||||
|
||||
| Library | Status | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| **TA-Lib** | N/A | No SSF-DSP implementation |
|
||||
| **Skender** | N/A | No SSF-DSP implementation |
|
||||
| **Tulip** | N/A | No SSF-DSP implementation |
|
||||
| **Ooples** | N/A | No SSF-DSP implementation |
|
||||
| **PineScript** | ✅ | Matches `ssfdsp.pine` reference within floating-point tolerance |
|
||||
| TA-Lib | N/A | Not standard |
|
||||
| Skender | N/A | Not standard |
|
||||
| PineScript | ✅ | Matches Ehlers' reference logic |
|
||||
|
||||
Validation relies on mathematical property verification:
|
||||
1. Zero-crossing behavior matches detrending theory
|
||||
2. Coefficient formulas match Ehlers' published SSF design
|
||||
3. Output bounds are symmetric around zero
|
||||
4. Filter stability verified (poles inside unit circle)
|
||||
## Usage & Pitfalls
|
||||
|
||||
## Common Pitfalls
|
||||
- **Oscillates around zero**—positive values indicate bullish cycle phase
|
||||
- **Zero crossings** signal cycle phase changes—entry points in direction of cross
|
||||
- **Period mismatch** degrades amplitude and phase accuracy
|
||||
- **Smoother than EMA-DSP** with sharper turning points
|
||||
- **Divergence** (price highs vs DSP highs) indicates trend exhaustion
|
||||
- **Pre-smooth input** for extremely noisy data
|
||||
|
||||
1. **Period Too Small**: Periods below 8 produce fast/slow periods that are too close, resulting in minimal oscillator amplitude. Recommended minimum: `period >= 8`.
|
||||
## API
|
||||
|
||||
2. **Warmup Interpretation**: The filter produces output immediately but is unreliable until `IsHot = true`. Trading signals during warmup phase are statistically noise.
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Ssfdsp {
|
||||
+int Period
|
||||
+double Value
|
||||
+bool IsHot
|
||||
+Ssfdsp(int period)
|
||||
+Ssfdsp(ITValuePublisher source, int period)
|
||||
+TValue Update(TValue input, bool isNew)
|
||||
+void Reset()
|
||||
}
|
||||
```
|
||||
|
||||
3. **Amplitude Variability**: Unlike bounded oscillators (RSI, Stochastic), SSF-DSP amplitude varies with price volatility. Normalize if consistent threshold signals are needed.
|
||||
### Class: `Ssfdsp`
|
||||
|
||||
4. **Lag vs Smoothness Tradeoff**: Increasing period improves smoothness but increases lag. The fast/slow period ratio (4:2 or 1:2) is fixed by design. Adjust base period, not ratio.
|
||||
| Parameter | Type | Default | Range | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `period` | `int` | `40` | `≥4` | Expected dominant cycle period |
|
||||
|
||||
5. **Bar Correction**: When updating the same bar (`isNew = false`), state rolls back to prevent cumulative drift. Failing to use `isNew` correctly corrupts filter memory.
|
||||
### Properties
|
||||
|
||||
6. **Memory Requirements**: Each SSF maintains 2 bars of state. For multi-period analysis, memory scales linearly with instance count.
|
||||
- `Value` (`double`): The current SSFDSP value (oscillates around 0)
|
||||
- `IsHot` (`bool`): Returns `true` when warmup is complete
|
||||
|
||||
## API Usage
|
||||
### Methods
|
||||
|
||||
- `Update(TValue input, bool isNew)`: Updates the indicator with a new data point
|
||||
|
||||
## C# Example
|
||||
|
||||
```csharp
|
||||
// Streaming mode
|
||||
var ssfdsp = new Ssfdsp(period: 20);
|
||||
foreach (var bar in bars)
|
||||
using QuanTAlib;
|
||||
|
||||
// Initialize with a 40-bar dominant cycle assumption
|
||||
var ssfdsp = new Ssfdsp(period: 40);
|
||||
|
||||
// Update with streaming data
|
||||
foreach (var bar in quotes)
|
||||
{
|
||||
TValue result = ssfdsp.Update(new TValue(bar.Time, bar.Close), isNew: true);
|
||||
var result = ssfdsp.Update(new TValue(bar.Date, bar.Close));
|
||||
|
||||
if (ssfdsp.IsHot)
|
||||
{
|
||||
// Use result.Value for signal generation
|
||||
Console.WriteLine($"{bar.Date}: SSF-DSP = {result.Value:F4}");
|
||||
|
||||
// Zero crossing detection
|
||||
if (result.Value > 0 && ssfdsp.Previous.Value <= 0)
|
||||
Console.WriteLine(" → Bullish cycle phase");
|
||||
else if (result.Value < 0 && ssfdsp.Previous.Value >= 0)
|
||||
Console.WriteLine(" → Bearish cycle phase");
|
||||
}
|
||||
}
|
||||
|
||||
// Bar correction (same bar, updated price)
|
||||
TValue corrected = ssfdsp.Update(new TValue(bar.Time, newClose), isNew: false);
|
||||
|
||||
// Batch mode
|
||||
TSeries output = Ssfdsp.Calculate(closePrices, period: 20);
|
||||
|
||||
// Chaining
|
||||
var source = new Ema(10);
|
||||
var ssfdsp = new Ssfdsp(source, period: 20);
|
||||
// ssfdsp automatically subscribes to source.Pub events
|
||||
// Batch calculation
|
||||
var output = Ssfdsp.Calculate(sourceSeries, period: 40);
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- Ehlers, J. (2013). *Cycle Analytics for Traders*. Wiley.
|
||||
- Ehlers, J. (2001). *Rocket Science for Traders*. Wiley.
|
||||
- Ehlers, J. (2004). *Cybernetic Analysis for Stocks and Futures*. Wiley.
|
||||
- PineScript reference: `lib/cycles/ssfdsp/ssfdsp.pine`
|
||||
Reference in New Issue
Block a user