Complete thin Dx-composition wrapper indicators with full test coverage: - PlusDi/MinusDi: Directional Indicator wrappers (DiPlus/DiMinus from Dx) - PlusDm/MinusDm: Directional Movement wrappers (DmPlus/DmMinus from Dx) - Individual validation tests per indicator directory (TALib, Skender, bounds) - Combined unit tests (DiDm.Tests.cs) and validation tests (DiDm.Validation.Tests.cs) - Quantower wrappers + tests for all 4 indicators - PineScript v6 implementations with compensated RMA - Normalized .md documentation for all indicators and categories - 182 tests passing, 0 failures
9.3 KiB
DPO: Detrended Price Oscillator
Strip the trend and what remains is the cycle.
| Property | Value |
|---|---|
| Category | Oscillator |
| Inputs | Single series (close) |
| Parameters | period (default 20) |
| Outputs | Single series (DPO line) |
| Output range | Unbounded (centered on zero) |
| Warmup | period + floor(period / 2) + 1 bars |
| PineScript | dpo.pine |
Key takeaways
- Removes the long-term trend by subtracting a displaced SMA, isolating short-term cycles.
- The displacement is
floor(period / 2) + 1bars into the past, making DPO intentionally non-current. - Zero-line crossovers indicate whether price sits above or below its detrended average.
- Useful for estimating dominant cycle length by measuring peak-to-peak distance.
- Not a timing indicator. DPO reveals rhythm, not direction.
Historical Context
William Blau formalized the Detrended Price Oscillator in Momentum, Direction, and Divergence (1995), though the concept of removing trend via displaced moving averages predates his work by decades. The core insight is straightforward: if you shift an SMA backward by half its period, the average sits at the center of the window rather than its trailing edge. Subtracting that centered value from price strips the trend component and exposes the underlying cycle.
Most oscillators try to be current. DPO deliberately looks backward. That design choice makes it useless for real-time trend-following but surprisingly effective for cycle analysis. Traders who need to estimate the dominant period of a market's oscillation find DPO more honest than indicators that blend trend and cycle information into a single, ambiguous line.
The PineScript (non-centered) variant used here computes close - SMA[displacement], referencing the SMA from displacement bars ago rather than centering the SMA around the current bar. This differs from the Tulip/TA-Lib centered variant (close[displacement] - SMA), which anchors the price to the past instead. Both isolate cycles, but they produce different numeric results and are not cross-comparable.
What It Measures and Why It Matters
DPO measures the deviation of current price from a displaced simple moving average. By pushing the SMA backward in time, the trend component cancels out, leaving only the cyclical oscillation around zero.
The result is an unbounded oscillator that crosses zero when price equals its detrended average. Peaks and troughs in the DPO correspond to short-term cycle extremes. The distance between consecutive peaks (or troughs) estimates the dominant cycle period. This makes DPO a cycle-measurement tool rather than a trading signal generator.
Because DPO is not aligned to the latest bar, it should not be used for real-time entry/exit decisions. Its value lies in structural analysis: identifying how frequently a market oscillates and whether the current cycle amplitude is expanding or contracting.
Mathematical Foundation
Core Formula
\text{displacement} = \left\lfloor \frac{P}{2} \right\rfloor + 1
\text{SMA}_t = \frac{1}{P} \sum_{i=0}^{P-1} x_{t-i}
\text{DPO}_t = x_t - \text{SMA}_{t - \text{displacement}}
where P is the lookback period and x_t is the input value at bar t.
Parameter Mapping
| Parameter | Code | Default | Constraints |
|---|---|---|---|
| Period | period |
20 | > 0 |
| Displacement | computed | (period / 2) + 1 |
derived |
Warmup Period
W = P + \left\lfloor \frac{P}{2} \right\rfloor + 1
The SMA requires P bars to fill. The displacement buffer then requires \lfloor P/2 \rfloor + 1 additional bars before the oldest SMA value is available.
Architecture & Physics
1. Dual RingBuffer Design
Two RingBuffer instances partition the computation:
_smaBuffer(period): maintains a running sum for O(1) SMA calculation viaSum / period._smaHistory(displacement + 1): stores computed SMA values;.Oldestretrieves the displaced SMA.
2. Source Subscription
The chaining constructor subscribes to src.Pub += Handle, forwarding TValueEventArgs directly into Update without storing the source reference.
3. Batch Path
Batch(ReadOnlySpan<double>, Span<double>, int) mirrors the streaming logic with local RingBuffer instances. No SIMD acceleration is applied because the displacement dependency creates a sequential data-flow constraint.
4. Edge Cases
| Condition | Behavior |
|---|---|
period <= 0 |
ArgumentException with nameof(period) |
NaN / Infinity input |
Substitutes last valid value |
| Before warmup complete | Returns 0.0 |
isNew = false |
Restore() on both RingBuffers; recomputes with corrected value |
Interpretation and Signals
Signal Zones
| Zone | Condition | Interpretation |
|---|---|---|
| Above zero | DPO > 0 |
Price above detrended average |
| Below zero | DPO < 0 |
Price below detrended average |
| Peak | Local maximum | Short-term cycle top |
| Trough | Local minimum | Short-term cycle bottom |
Signal Patterns
- Zero-line crossover: DPO crossing above zero suggests price has moved above its displaced average; crossing below suggests the opposite.
- Peak-to-peak measurement: Distance between consecutive DPO peaks estimates the dominant cycle length.
- Amplitude expansion: Growing peak/trough magnitudes indicate increasing cyclical volatility.
- Divergence: Price making new highs while DPO peaks decline warns of potential cycle exhaustion.
Practical Notes
- DPO is backward-looking by design. Do not use it for real-time trade timing.
- Choose
periodto approximate the expected cycle length. A 20-bar DPO isolates roughly 20-bar cycles. - Works best in range-bound or cyclical markets. Strong trends produce a persistent DPO bias that obscures the cycle signal.
Related Indicators
- SMA: The underlying moving average that DPO detrends against.
- CFO: Another detrending oscillator using linear regression instead of displaced SMA.
- APO: Measures momentum via EMA difference rather than cycle isolation.
Validation
| Library | Status | Notes |
|---|---|---|
| Manual SMA | ✅ | Exact match against independent displaced-SMA computation (< 1e-9) |
| Tulip | ❌ | Not comparable. Tulip uses centered formula (close[back] - SMA); QuanTAlib uses PineScript non-centered formula (close - SMA[back]) |
| Skender | ❌ | No DPO implementation available |
| TA-Lib | ❌ | Uses centered variant; different algorithm |
Performance Profile
Key Optimizations
- O(1) streaming:
RingBuffer.Sumprovides constant-time SMA;.Oldestprovides constant-time displacement lookback. - Zero allocation: Both
Updatepaths use pre-allocatedRingBufferinstances and arecord struct State. - Snapshot/Restore: Bar correction via
RingBuffer.Snapshot()/Restore()avoids buffer cloning.
Operation Count (Streaming Mode)
| Operation | Count per bar |
|---|---|
| Additions | 1 (running sum maintained by RingBuffer) |
| Divisions | 1 (Sum / period) |
| Subtractions | 1 (value - displacedSma) |
| Comparisons | 2 (IsFinite check, IsFull checks) |
| Total | ~5 ops |
SIMD Analysis (Batch Mode)
| Property | Value |
|---|---|
| Vectorizable | No |
| Reason | Displacement creates sequential dependency between SMA output and DPO computation |
| Fallback | Scalar loop with local RingBuffer instances |
Common Pitfalls
- Confusing DPO variants: The PineScript (non-centered) and Tulip/TA-Lib (centered) formulas produce different results. Cross-library validation requires matching the same variant.
- Using DPO for real-time signals: DPO is displaced by design. It does not reflect the latest bar's relationship to the current trend.
- Wrong period selection: If the DPO period does not approximate the dominant cycle, the oscillator produces noise rather than signal. Measure peak-to-peak distance to calibrate.
- Ignoring warmup: The first
period + displacementbars output0.0. Trading on these values guarantees false signals. - Trend-dominated markets: In strong trends, DPO stays persistently positive or negative, defeating its cycle-isolation purpose.
FAQ
Q: Why does QuanTAlib's DPO not match Tulip or TA-Lib?
A: Different formula variant. QuanTAlib uses the PineScript non-centered formula (close - SMA[displacement]), while Tulip and TA-Lib use the centered variant (close[displacement] - SMA). Both are valid but not numerically equivalent.
Q: How do I pick the right period? A: Start with a period that matches your expected cycle length (e.g., 20 for daily data targeting a roughly monthly cycle). Then measure peak-to-peak distance in the DPO output. If the measured distance consistently differs from your period, adjust accordingly.
Q: Can DPO be used as a standalone trading signal? A: Not recommended. DPO measures cycle structure, not timing. Pair it with a trend-following indicator (SMA, EMA) for entries and use DPO to confirm whether the market is in a cyclical phase worth trading.
References
- Blau, William. Momentum, Direction, and Divergence. Wiley, 1995.
- Dorsey, Thomas. Point and Figure Charting. Wiley, 2007.
- PineScript reference:
dpo.pine