mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-03 11:47:44 +00:00
7db48e2418
Implement DSO (TASC Oct 2018) with SSF 2-pole filter, RMS normalization, and Fisher Transform (±0.99 clamp). Sealed class, O(1) streaming RMS via RingBuffer, precomputed SSF coefficients. New files: Dso.cs, Dso.Quantower.cs, Dso.md, dso.pine, Dso.Tests.cs (27), Dso.Validation.Tests.cs (7), Dso.Quantower.Tests.cs (11) Updated: Exports.cs, _bridge.py, oscillators.py, SPEC.md, _sidebar.md, lib/_index.md, oscillators/_index.md, docs/indicators.md, docs/pinescript.md All 19,565 tests pass, 0 warnings.
159 lines
7.9 KiB
Markdown
159 lines
7.9 KiB
Markdown
# DSO: Ehlers Deviation-Scaled Oscillator
|
||
|
||
> *When price deviates from its smoothed norm, DSO amplifies the signal through Fisher transformation—producing sharp, decisive oscillator readings that compress during noise and expand during trends.*
|
||
|
||
| Property | Value |
|
||
| ---------------- | -------------------------------- |
|
||
| **Category** | Oscillator |
|
||
| **Inputs** | Source (close) |
|
||
| **Parameters** | `period` (default 40) |
|
||
| **Outputs** | Single series (Dso) |
|
||
| **Output range** | Unbounded (typically ±3) |
|
||
| **Warmup** | `period` bars |
|
||
| **PineScript** | [dso.pine](dso.pine) |
|
||
|
||
- DSO (Deviation-Scaled Oscillator) is a Fisher-transformed, RMS-normalized Super Smoother oscillator that measures price deviation from its filtered trend, amplified through a nonlinear Fisher Transform.
|
||
- **Similar:** [REFLEX](../reflex/Reflex.md), [TRENDFLEX](../trendflex/Trendflex.md) | **Complementary:** ADX for trend confirmation | **Trading note:** Values beyond ±2 indicate extreme deviation; zero crossings signal direction changes.
|
||
- No external validation libraries implement DSO. Validated through self-consistency and behavioral testing.
|
||
|
||
DSO applies three stages of signal processing: (1) input whitening to remove DC bias and Nyquist aliasing, (2) a 2-pole Super Smoother filter for trend extraction, and (3) RMS normalization followed by a Fisher Transform that amplifies readings near the center and compresses extremes, producing sharp turning-point signals.
|
||
|
||
## Historical Context
|
||
|
||
The Deviation-Scaled Oscillator was published by John F. Ehlers in the October 2018 issue of *Technical Analysis of Stocks & Commodities* magazine. Ehlers described it as a "Fisherized" version of his deviation-scaled approach, combining the Super Smoother filter (his signature contribution to technical analysis) with RMS normalization and the Fisher Transform (inverse hyperbolic tangent) to produce an oscillator with Gaussian-distributed output—ideal for statistical threshold-based trading.
|
||
|
||
## Architecture & Physics
|
||
|
||
DSO operates in four stages:
|
||
|
||
### Stage 1: Input Whitening
|
||
|
||
The raw price is whitened by computing a 2-bar difference:
|
||
|
||
$$ \text{Zeros}_t = \text{Close}_t - \text{Close}_{t-2} $$
|
||
|
||
This removes the DC (constant) component and rejects Nyquist frequency aliasing, ensuring only meaningful mid-frequency cycles pass through to the filter.
|
||
|
||
### Stage 2: Super Smoother Filter (2-pole Butterworth)
|
||
|
||
The whitened input is smoothed using Ehlers' 2-pole Super Smoother at half-period cutoff:
|
||
|
||
$$ \text{Filt}_t = \frac{c_1}{2}(\text{Zeros}_t + \text{Zeros}_{t-1}) + c_2 \cdot \text{Filt}_{t-1} + c_3 \cdot \text{Filt}_{t-2} $$
|
||
|
||
Coefficients are precomputed from the period:
|
||
|
||
$$ a_1 = e^{-\sqrt{2} \cdot \pi / (\text{period}/2)} $$
|
||
|
||
$$ c_2 = 2 a_1 \cos\left(\sqrt{2} \cdot \pi / (\text{period}/2)\right), \quad c_3 = -a_1^2, \quad c_1 = 1 - c_2 - c_3 $$
|
||
|
||
### Stage 3: RMS Normalization
|
||
|
||
Root Mean Square over the period window normalizes the filtered signal by its recent volatility:
|
||
|
||
$$ \text{RMS}_t = \sqrt{\frac{1}{N} \sum_{i=0}^{N-1} \text{Filt}_{t-i}^2} $$
|
||
|
||
$$ \text{ScaledFilt}_t = \frac{\text{Filt}_t}{\text{RMS}_t} $$
|
||
|
||
Implemented using a `RingBuffer` for O(1) running sum updates. RMS is floored at `1e-10` to prevent division by zero.
|
||
|
||
### Stage 4: Fisher Transform
|
||
|
||
The scaled filter output is clamped to ±0.99 and passed through the Fisher (inverse hyperbolic tangent) Transform:
|
||
|
||
$$ \text{DSO}_t = \frac{1}{2} \ln\left(\frac{1 + \text{clamp}(\text{ScaledFilt}_t)}{1 - \text{clamp}(\text{ScaledFilt}_t)}\right) $$
|
||
|
||
The Fisher Transform converts the bounded [-1, 1] input into an unbounded Gaussian-like output, amplifying readings near zero (where reversals often originate) and compressing extreme values.
|
||
|
||
Implemented with FMA for the SSF filter:
|
||
|
||
```csharp
|
||
filt = Math.FusedMultiplyAdd(_c1Half, zeros + _s.Zeros1,
|
||
Math.FusedMultiplyAdd(_c2, _s.Filt, _c3 * _s.Filt1));
|
||
```
|
||
|
||
## Performance Profile
|
||
|
||
DSO combines a 2-pole IIR filter, O(1) RMS via ring buffer, and the Fisher Transform logarithm.
|
||
|
||
### Operation Count (Streaming Mode, Scalar)
|
||
|
||
| Operation | Count | Cost (cycles) | Subtotal |
|
||
| :--- | :---: | :---: | :---: |
|
||
| **Stage 1: Input Whitening** | | | |
|
||
| SUB (Close - Close[2]) | 1 | 1 | 1 |
|
||
| **Stage 2: Super Smoother (2-pole Butterworth)** | | | |
|
||
| ADD (zeros + zeros1) | 1 | 1 | 1 |
|
||
| FMA (c1Half × sum + c2×filt + c3×filt1) | 2 | 4 | 8 |
|
||
| MUL (c3 × filt1) | 1 | 3 | 3 |
|
||
| **Stage 3: RMS Buffer Update** | | | |
|
||
| MUL (filt × filt) | 1 | 3 | 3 |
|
||
| ADD/SUB (sumSquared update) | 2 | 1 | 2 |
|
||
| FMA (running sum) | 1 | 4 | 4 |
|
||
| MUL (sumSquared × periodRecip) | 1 | 3 | 3 |
|
||
| SQRT | 1 | 15 | 15 |
|
||
| **Stage 4: Fisher Transform** | | | |
|
||
| DIV (filt / rms) | 1 | 15 | 15 |
|
||
| CLAMP (max/min) | 2 | 1 | 2 |
|
||
| ADD/SUB (1±clamped) | 2 | 1 | 2 |
|
||
| DIV (ratio) | 1 | 15 | 15 |
|
||
| LOG | 1 | 20 | 20 |
|
||
| MUL (0.5 × log) | 1 | 3 | 3 |
|
||
| **Total** | | | **~97 cycles** |
|
||
|
||
**Dominant costs:**
|
||
- LOG (20 cycles, 21%) — Fisher Transform
|
||
- SQRT (15 cycles, 15%) — RMS calculation
|
||
- DIV (2×15 cycles, 31%) — RMS normalization + Fisher ratio
|
||
|
||
### Batch Mode (SIMD Analysis)
|
||
|
||
DSO is **not SIMD-parallelizable** across bars due to:
|
||
1. Super Smoother is a 2-pole IIR filter with recursive state
|
||
2. RMS depends on running sum of squared values
|
||
3. Fisher Transform LOG is inherently scalar
|
||
|
||
### Quality Metrics
|
||
|
||
| Metric | Score | Notes |
|
||
| :--- | :---: | :--- |
|
||
| **Accuracy** | 8/10 | Fisher Transform amplifies clean signals near zero |
|
||
| **Timeliness** | 8/10 | Input whitening + SSF = low lag for oscillator class |
|
||
| **Overshoot** | 7/10 | Clamping at ±0.99 prevents infinity, but Fisher amplifies |
|
||
| **Smoothness** | 7/10 | Super Smoother provides good noise rejection |
|
||
|
||
## Validation
|
||
|
||
DSO is not implemented in mainstream libraries. Validation relies on behavioral testing.
|
||
|
||
| Library | Status | Notes |
|
||
| :--- | :--- | :--- |
|
||
| **TA-Lib** | N/A | Not implemented |
|
||
| **Skender** | N/A | Not implemented |
|
||
| **Tulip** | N/A | Not implemented |
|
||
| **Ooples** | N/A | Not implemented |
|
||
| **Behavioral** | ✅ | Validated: constant→zero, symmetry, mode consistency |
|
||
|
||
### Behavioral Test Summary
|
||
|
||
- **Constant Input → Zero**: Constant close → zeros=0 → filt=0 → scaledFilt=0 → Fisher(0)=0
|
||
- **Fisher Symmetry**: DSO(-x) = -DSO(x) — output is antisymmetric
|
||
- **Trending Input**: Strong trend produces non-zero DSO values
|
||
- **Mode Consistency**: Streaming, batch, span, and event-driven modes produce identical results
|
||
- **Bar Correction**: Snapshot/Restore via RingBuffer produces exact rollback
|
||
|
||
## Common Pitfalls
|
||
|
||
1. **Warmup Period**: DSO requires `Period` bars to fill the RMS buffer. Before warmup, output will be unstable. Use `IsHot` to detect readiness.
|
||
|
||
2. **Close[2] Dependency**: The whitening step `Close - Close[2]` requires tracking two-bar-ago close. State stores both `Src1` and `Src2` for this purpose. On the first two bars, the filter output is zero.
|
||
|
||
3. **Fisher Transform Singularity**: The Fisher Transform has a singularity at ±1 (ln(0)). Clamping at ±0.99 prevents this. The maximum possible DSO value is ±2.646 (`0.5 * ln(199) ≈ 2.646`).
|
||
|
||
4. **RMS Floor**: During perfectly flat markets (zero volatility), RMS approaches zero. The `MinRms = 1e-10` floor prevents division by zero but may produce large scaled values. The ±0.99 Fisher clamp provides a second safety net.
|
||
|
||
5. **Not a Bounded Oscillator**: Unlike RSI or Stochastics, DSO is unbounded. Values beyond ±2 indicate extreme deviation—roughly equivalent to a 2-sigma event in the Fisher-transformed space.
|
||
|
||
6. **Period Selection**: Ehlers recommends period=40 (approximately one market month of bars on daily charts). Shorter periods increase sensitivity but also noise; longer periods add lag.
|
||
|
||
7. **Bar Correction**: Like all QuanTAlib indicators, DSO supports bar correction via the `isNew` parameter. The RingBuffer `Snapshot()`/`Restore()` mechanism handles this atomically.
|