Skip to content
Open RaamTrade

DPO — Detrended Price Oscillator

The close minus the simple average from n/2 + 1 bars ago — the common, non-centred form. Subtracting a lagged average removes the trend and leaves the cycle; the reading belongs to the current bar.

Signature

DPO(period, on=<source>)
Argument Type Required Notes
period int Param or literal Yes Cycle length to remove. Default 20, bounds 2–400.
on= symbol, OHLCV field, or indicator No Source. Defaults to the trade symbol’s close.

Formula

$$ \text{DPO}t = C_t - \text{SMA}(C, n){t-(n/2+1)} $$

Warmup

period + int(period/2) + 1 bars — the lagged window has to be whole; 0 until then.

Reading the value

dpo.value
dpo > 0
dpo.crossed_above(0)

Example

class CycleTimer(Strategy):
"""Enter when the detrended cycle turns up from a trough."""
period = Param(20, min=2, max=400)
trade = Symbol()
sizing = FixedCapital()
def execute(self):
dpo = DPO(self.period, on=self.trade)
if dpo.crossed_above(0) and self.trade.is_flat:
self.buy(self.trade, reason="Cycle up")
if dpo.crossed_below(0) and self.trade.is_long:
self.sell(self.trade, self.trade.position, reason="Cycle down")

Notes

  • The non-centred construction most charting platforms ship by default (the lag is int(n/2) + 1). The centred textbook form — a past close minus the average centred on it — is not aligned with the current bar; this one is, at the cost of a lagged average.
  • On a straight-line price the reading is the constant lag of the average, int(n/2) + 1 + (n − 1)/2 (20.5 for n = 20) — the trend the detrending removes.