Rate of Change (ROC)

Haiyue
9min

I. What is ROC

The Rate of Change (ROC) is a momentum oscillator that measures the percentage change of the current price relative to the price nn periods ago. It is the normalized version of the Momentum indicator, converting absolute differences into percentage form so that assets with different price levels can be compared horizontally.

Historical Background

Like Momentum, ROC is one of the most original concepts in technical analysis with no single identifiable inventor. The concept of percentage price change (Price Rate of Change) runs throughout the entire history of financial analysis, from the earliest stock market analysis to modern quantitative trading. ROC remains a fundamental tool for measuring the speed of price movement. In academic finance, ROC is essentially the same concept as “simple return.”

Indicator Classification

  • Type: Oscillator, displayed in a separate panel
  • Category: Momentum
  • Default Parameters: Period n=10n = 10, data source is Close price
  • Value Range: Unbounded, oscillates around zero (unit: %)
ROC and Returns

ROC(1) is essentially the daily return. ROC(5) is the weekly return, ROC(20) is the monthly return. In quantitative finance, ROC is the most fundamental measure of return.


II. Mathematical Principles and Calculation

Core Formula

ROCt=CtCtnCtn×100ROC_t = \frac{C_t - C_{t-n}}{C_{t-n}} \times 100

Where:

  • CtC_t = current closing price
  • CtnC_{t-n} = closing price nn periods ago
  • nn = lookback period (default 10)

Equivalent form:

ROCt=(CtCtn1)×100ROC_t = \left(\frac{C_t}{C_{t-n}} - 1\right) \times 100

Relationship to Momentum

ROCt=MOMtCtn×100ROC_t = \frac{MOM_t}{C_{t-n}} \times 100

That is, ROC = Momentum / base period price x 100.

Step-by-Step Calculation Logic

  1. Determine lookback period nn (e.g., 10)
  2. Get current closing price CtC_t and closing price nn periods ago CtnC_{t-n}
  3. Calculate difference: CtCtnC_t - C_{t-n}
  4. Divide by base period price: (CtCtn)/Ctn(C_t - C_{t-n}) / C_{t-n}
  5. Multiply by 100 to get percentage

Value Interpretation

ROC ValueMeaning
ROC = +5%Current price is 5% higher than nn periods ago
ROC = 0Current price is unchanged from nn periods ago
ROC = -5%Current price is 5% lower than nn periods ago
Logarithmic ROC

In some academic applications and high-frequency strategies, logarithmic returns are used: ROClog=ln(Ct/Ctn)×100ROC_{log} = \ln(C_t / C_{t-n}) \times 100. Logarithmic returns have the additivity property (multi-period returns can be summed directly), which is more convenient for statistical modeling.


III. Python Implementation

import numpy as np
import pandas as pd


def roc(close: pd.Series, period: int = 10) -> pd.Series:
    """
    Calculate the Rate of Change (ROC)

    Parameters
    ----------
    close : pd.Series
        Close price series
    period : int
        Lookback period, default 10

    Returns
    -------
    pd.Series
        ROC values (percentage)
    """
    return ((close - close.shift(period)) / close.shift(period)) * 100


def roc_log(close: pd.Series, period: int = 10) -> pd.Series:
    """
    Calculate logarithmic ROC (log return)
    """
    return np.log(close / close.shift(period)) * 100


# ========== Usage Example ==========
if __name__ == "__main__":
    np.random.seed(42)
    dates = pd.date_range("2024-01-01", periods=120, freq="D")
    price = 100 + np.cumsum(np.random.randn(120) * 1.0)

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

    # Calculate ROC with different periods
    df["ROC_5"]  = roc(df["close"], period=5)
    df["ROC_10"] = roc(df["close"], period=10)
    df["ROC_20"] = roc(df["close"], period=20)

    print("=== ROC Results (last 15 rows) ===")
    print(df[["close", "ROC_5", "ROC_10", "ROC_20"]].tail(15).to_string())

    # Zero line crossover signals
    df["signal"] = np.where(
        (df["ROC_10"] > 0) & (df["ROC_10"].shift(1) <= 0), 1,
        np.where(
            (df["ROC_10"] < 0) & (df["ROC_10"].shift(1) >= 0), -1, 0
        )
    )

    print("\n=== Zero Line Crossover Signals ===")
    signals = df[df["signal"] != 0]
    print(signals[["close", "ROC_10", "signal"]].to_string())

    # ROC extreme value statistics
    print(f"\n=== ROC(10) Descriptive Statistics ===")
    print(df["ROC_10"].describe().to_string())

    # Compare with log ROC
    df["ROC_10_log"] = roc_log(df["close"], period=10)
    print("\n=== ROC vs Log ROC (last 5 rows) ===")
    print(df[["ROC_10", "ROC_10_log"]].tail(5).to_string())

IV. Problems the Indicator Solves

1. Momentum Direction and Strength

  • ROC > 0: Price has risen over the past nn periods, bullish momentum
  • ROC < 0: Price has fallen over the past nn periods, bearish momentum
  • Larger |ROC|: Stronger momentum

2. Zero Line Crossover Signals

SignalConditionMeaning
BuyROC crosses above zeroMomentum shifts from bearish to bullish
SellROC crosses below zeroMomentum shifts from bullish to bearish

3. Extreme Value Alerts

By analyzing the historical distribution of ROC, extreme zones can be defined:

  • ROC exceeds historical mean + 2 sigma — rising too fast, beware of pullback
  • ROC below historical mean - 2 sigma — falling too fast, beware of bounce

4. Cross-Sectional Comparison and Ranking

ROC’s percentage form makes it ideal for horizontal comparison:

  • Stocks with different price levels can be ranked by ROC (momentum stock selection)
  • Different asset classes can compare ROC (asset rotation strategy)

5. Divergence Analysis

  • Bearish Divergence: Price new high + ROC peak declining — upward speed decelerating
  • Bullish Divergence: Price new low + ROC trough rising — downward speed decelerating
Caution

ROC is very sensitive to the base period price CtnC_{t-n}. If the price nn periods ago happened to be an extreme value (e.g., a flash crash low), the current ROC will be abnormally amplified. Watch out for this “base effect” to avoid being misled by false signals.


V. Advantages, Disadvantages, and Use Cases

Advantages

AdvantageDescription
NormalizedPercentage form eliminates the influence of price level
ComparableDirect horizontal comparison across different assets
Intuitively clearROC = 5% has a meaning everyone can understand
Widely applicableFrom technical analysis to quant factors, extremely broad usage

Disadvantages

DisadvantageDescription
NoisyNo built-in smoothing, short-term fluctuations affect signals
Base effectAnomalous base period values cause ROC distortion
No boundariesNo fixed overbought/oversold thresholds
Simple signalsRelies solely on zero line crossovers, single signal dimension

Use Cases

  • Momentum stock selection: ROC is one of the most commonly used cross-sectional momentum factors
  • Asset rotation: Compare ROC across different assets, allocate to the strongest momentum assets
  • Trend confirmation: As a filter condition for other strategies
  • Component of composite indicators such as the Coppock Curve
ComparisonROCMomentumRSIMACD
Output FormPercentageAbsolute value0-100Absolute value
NormalizedYesNoYesNo
Cross-comparableYesNoYesNo
SmoothingNoneNoneWilderEMA
Signal RichnessLowLowHighHigh
Practical Tips
  1. Momentum stock selection formula: Go long on the top 10% of assets by 12-month ROC — this is a classic momentum factor strategy.
  2. Smoothing: Apply SMA(10) or EMA(10) smoothing to ROC before generating crossover signals.
  3. Base period check: Before trading, verify whether an abnormal price exists nn periods ago (ex-dividend, gap, etc.).
  4. Complement with Momentum: Use ROC for cross-sectional stock selection, and Momentum for single-asset longitudinal analysis.