Detrended Price Oscillator (DPO)

Haiyue
10min

I. What is the DPO Indicator

The Detrended Price Oscillator (DPO) is an oscillator that removes the long-term trend component from prices to reveal short-term cycles. It calculates the deviation between the closing price and a backward-displaced moving average, stripping away the trend influence so that traders can more clearly see the cyclical fluctuation patterns in price.

Historical Background

DPO does not have a single clearly identified inventor. It was developed by the technical analysis community during research on Market Cycles. Its theoretical foundation comes from Cycle Analysis, which assumes that price movements can be decomposed into a trend component and a cyclical component. By removing the trend, DPO helps traders identify underlying cyclical patterns.

Indicator Classification

  • Type: Oscillator, displayed in a separate panel
  • Category: Momentum / Cycle indicator
  • Default Parameter: Period n=20n = 20
  • Value Range: Unbounded, oscillates around the zero line
Core Idea of DPO

DPO is not concerned with “whether price is rising or falling” (that’s the job of trend indicators). Instead, it asks: “Where is price within its cycle?” — is it at a cycle high or low?


II. Mathematical Principles and Calculation

Core Formula

DPOt=CtSMA(C,n)tn/2+1DPO_t = C_t - SMA(C, n)_{t - \lfloor n/2 + 1 \rfloor}

Where:

  • CtC_t = current closing price
  • SMA(C,n)SMA(C, n) = nn-period Simple Moving Average
  • Displacement = n/2+1\lfloor n/2 + 1 \rfloor (shifted into the past)

For the default n=20n = 20: Displacement = 20/2+1=11\lfloor 20/2 + 1 \rfloor = 11

Therefore:

DPOt=CtSMA(C,20)t11DPO_t = C_t - SMA(C, 20)_{t-11}

Equivalent Understanding

The DPO calculation can be understood as follows:

  1. Calculate the 20-period SMA
  2. Shift the SMA right by 11 periods (i.e., use the SMA value from 11 periods ago)
  3. Subtract this displaced SMA from the current price

Step-by-Step Calculation Logic

  1. Calculate SMA(Close, 20)
  2. Determine displacement: 20/2+1=11\lfloor 20/2 + 1 \rfloor = 11
  3. Displace the SMA: Shift the SMA series right by 11 periods
  4. DPO = Close - displaced SMA

Why the Displacement?

The center point of SMA(20) falls at the middle of its window (approximately 10 periods ago). By shifting the SMA right by n/2+1n/2+1 periods, we realign it to the center of the window, making DPO more accurately reflect the price’s deviation around the moving average.

About “Detrending”

The “detrended” in DPO means removing the trend component represented by the SMA. When DPO > 0, the price is above the detrended baseline; when DPO < 0, the price is below it. Due to the displacement, DPO does not contain the most recent trend information, so it is not suitable for real-time trend tracking.


III. Python Implementation

import numpy as np
import pandas as pd


def dpo(close: pd.Series, period: int = 20) -> pd.Series:
    """
    Calculate Detrended Price Oscillator (DPO)

    Parameters
    ----------
    close : pd.Series
        Close price series
    period : int
        Calculation period, default 20

    Returns
    -------
    pd.Series
        DPO value series
    """
    shift_amount = period // 2 + 1
    sma = close.rolling(window=period, min_periods=period).mean()

    # Displace SMA to the right (use SMA from shift_amount periods ago)
    dpo_values = close - sma.shift(shift_amount)

    return dpo_values


# ========== Usage Example ==========
if __name__ == "__main__":
    np.random.seed(42)
    dates = pd.date_range("2024-01-01", periods=150, freq="D")
    # Construct trend + cyclical price
    trend = np.linspace(0, 15, 150)
    cycle = 5 * np.sin(2 * np.pi * np.arange(150) / 25)  # 25-day cycle
    noise = np.random.randn(150) * 0.5
    price = 100 + trend + cycle + noise

    df = pd.DataFrame({
        "date": dates,
        "open":  price + np.random.randn(150) * 0.3,
        "high":  price + np.abs(np.random.randn(150) * 0.6),
        "low":   price - np.abs(np.random.randn(150) * 0.6),
        "close": price,
        "volume": np.random.randint(1000, 10000, size=150),
    })
    df.set_index("date", inplace=True)

    # Calculate DPO
    df["DPO_20"] = dpo(df["close"], period=20)

    print("=== DPO Results (Last 20 Rows) ===")
    print(df[["close", "DPO_20"]].tail(20).to_string())

    # Zero-line crossover signals
    df["cross_up"] = (df["DPO_20"] > 0) & (df["DPO_20"].shift(1) <= 0)
    df["cross_down"] = (df["DPO_20"] < 0) & (df["DPO_20"].shift(1) >= 0)

    print("\n=== DPO Crosses Above Zero ===")
    print(df[df["cross_up"]][["close", "DPO_20"]].to_string())
    print("\n=== DPO Crosses Below Zero ===")
    print(df[df["cross_down"]][["close", "DPO_20"]].to_string())

    # Cycle highs and lows
    from scipy.signal import argrelextrema  # Optional dependency
    try:
        dpo_clean = df["DPO_20"].dropna()
        highs = argrelextrema(dpo_clean.values, np.greater, order=5)[0]
        lows = argrelextrema(dpo_clean.values, np.less, order=5)[0]

        print("\n=== Cycle Highs ===")
        print(dpo_clean.iloc[highs].to_string())
        print("\n=== Cycle Lows ===")
        print(dpo_clean.iloc[lows].to_string())

        if len(highs) >= 2:
            avg_cycle = np.mean(np.diff(highs))
            print(f"\nEstimated cycle length: approx. {avg_cycle:.1f} days")
    except ImportError:
        print("\n(Install scipy to auto-detect cycle highs and lows)")

IV. Problems the Indicator Solves

1. Cycle Identification

DPO’s primary use is identifying cyclical patterns in price:

  • The distance between DPO peaks = the dominant cycle length in price
  • By measuring the spacing between multiple peaks or troughs, you can estimate the market’s underlying cycle

2. Overbought / Oversold Within Cycles

Once a cycle is identified, DPO can be used to determine where the current price sits within the cycle:

  • DPO near cycle high -> cyclical overbought, likely to pull back
  • DPO near cycle low -> cyclical oversold, likely to bounce

Even during an uptrend, prices experience short-term pullbacks. DPO helps identify the bottom of these pullbacks:

  • In an uptrend, DPO falling to negative then bouncing back -> buy opportunity
  • In a downtrend, DPO rising to positive then falling back -> sell opportunity

4. Pairing with Cycle Analysis Tools

DPO is often used alongside Fourier analysis, spectral analysis, and other mathematical tools to extract the market’s dominant cycles more precisely.

Note

Due to the SMA displacement, the last few DPO values (approximately n/2n/2 periods) are missing or unreliable. This means DPO is not suitable as the sole basis for real-time trading signals — it is better suited for analyzing cycle patterns and providing supplementary judgment.


V. Advantages, Disadvantages, and Use Cases

Advantages

AdvantageDescription
Removes trendEliminates trend interference, clearly revealing cyclical fluctuations
Cycle measurementCan be used to estimate the market’s dominant cycle length
IntuitiveZero-line crossovers and peak/trough patterns are easy to understand
Complementary perspectiveProvides a cycle dimension that other trend/momentum indicators lack

Disadvantages

DisadvantageDescription
Missing dataDisplacement causes the most recent periods to be uncalculable
Not real-timeNot suitable for real-time trading decisions; signals have inherent delay
Cycle assumptionAssumes stable cycles exist in price, but actual market cycles may be unstable
Parameter selection difficultyNeed to approximately know the cycle length before choosing appropriate parameters

Use Cases

  • Commodity futures: Many commodities (e.g., agricultural products) have seasonal cycles that DPO captures well
  • Stock indices: Used in the context of known cycles such as election cycles or earnings seasons
  • Paired with cycle analysis: As part of a cycle analysis toolkit
ComparisonDPOCCIRSI
DetrendedYesNoNo
Real-timePoorGoodGood
Cycle analysisSpecialtyGeneralNot suited
Value rangeUnboundedUnbounded0–100
Practical Advice
  1. Determine the cycle first: Before using DPO, estimate the market’s dominant cycle length through visual observation or mathematical methods (such as autocorrelation functions).
  2. Match parameters: DPO’s period parameter should be set to half the estimated cycle length or equal to it.
  3. Combine with trend direction: In an uptrend, only focus on DPO lows for buying; in a downtrend, only focus on DPO highs for selling.
  4. Don’t rely on recent values: Due to displacement, the last n/2n/2 periods of DPO values are incomplete — use other indicators to compensate.