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.
7.9 KiB
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 (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, TRENDFLEX | 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:
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:
- Super Smoother is a 2-pole IIR filter with recursive state
- RMS depends on running sum of squared values
- 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
-
Warmup Period: DSO requires
Periodbars to fill the RMS buffer. Before warmup, output will be unstable. UseIsHotto detect readiness. -
Close[2] Dependency: The whitening step
Close - Close[2]requires tracking two-bar-ago close. State stores bothSrc1andSrc2for this purpose. On the first two bars, the filter output is zero. -
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). -
RMS Floor: During perfectly flat markets (zero volatility), RMS approaches zero. The
MinRms = 1e-10floor prevents division by zero but may produce large scaled values. The ±0.99 Fisher clamp provides a second safety net. -
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.
-
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.
-
Bar Correction: Like all QuanTAlib indicators, DSO supports bar correction via the
isNewparameter. The RingBufferSnapshot()/Restore()mechanism handles this atomically.