Rajandran R Creator of OpenAlgo Sketchmaker, RagZ. Building GenAI Applications. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Now the recent curiosity on building Trading AI Agents.

Hodrick Prescott Filter (HP Filter) Analysis – Python

6 min read

The Hodrick Prescott filter in Python takes one line: cycle, trend = sm.tsa.filters.hpfilter(series, lamb=1600) from the statsmodels library. It splits a price series into a smooth trend and a cycle that swings around zero, and the smoothing parameter lambda decides how smooth that trend is. Below is the original TCS example, an updated 2026 version of the code that runs on current libraries, and the one caveat every trader must know before using the HP filter for signals.

Key takeaways
  • The HP filter decomposes a time series into a trend component and a cycle component.
  • A Hodrick Prescott filter Python workflow needs only statsmodels, pandas, yfinance and Matplotlib.
  • In statsmodels, hpfilter() returns the cycle first and the trend second.
  • Lambda controls smoothness: 1600 is the textbook value for quarterly data; daily prices need far larger values.
  • The filter is two-sided, so the trend at any past bar uses future prices. Backtests built on it are biased unless you recompute it bar by bar.
  • The cycle component is useful for spotting stretched prices; the trend is best for describing direction, not predicting it.

What Is the Hodrick Prescott Filter?

Wikipedia says

The Hodrick–Prescott filter (also known as Hodrick–Prescott decomposition) is a mathematical tool used in macroeconomics, especially in real business cycle theory, to remove the cyclical component of a time series from raw data.

Hodrick Prescott Filter (HP Filter) does Time series decomposition which involves separating a time series into several distinct components(Cycle component and Trend Component). The trend represents the long-term movement or underlying growth of the series, while the cyclical component captures short-term fluctuations around the trend.

OpenAlgo: one API for 35+ Indian brokers

FREE AND OPEN SOURCE

OpenAlgo: one API for 35+ Indian brokers

Self-hosted algo trading platform. Send orders from Python, AmiBroker, TradingView, Excel or a no-code flow through one consistent API.

  • Unified REST and WebSocket API
  • Works with AmiBroker, TradingView, ChartInk and Excel
  • Sandbox mode to test before going live
Get OpenAlgo →by Marketcalls

The filter involves a smoothing parameter, often denoted as λ (lambda). The choice of λ determines the trade-off between fitting the data closely and capturing fluctuations. A higher λ results in a smoother trend, while a lower λ allows the trend to capture more short-term fluctuations.

And this filter looks like it can be applied to any time-series data, especially with stock prices to understand the underlying trend and the cycle involved in it.

Formally, the filter picks the trend T that minimises the squared distance between price and trend plus lambda times the squared second difference of the trend. A large lambda penalises any bend in the trend line, so the trend becomes almost a straight curve; a small lambda lets the trend hug the price (statsmodels documentation).

Hodrick Prescott Filter Python Example: TCS

Here is a simple ipython notebook example for Hodrick Prescott Filter Analysis. We use statsmodel library to compute the Hodrick Prescott Filter Components, Matplotlib to plot the data, yFinance to retrieve the stock data from NSEIndia and Pandas to handle the time series data.

import yfinance as yf
from datetime import datetime
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm

# Import TCS data from Yahoo Finance
TCS = yf.download('TCS.NS', start='2020-01-01', end='2023-12-31')
TCS

 Output

	Open	High	Low	Close	Adj Close	Volume
Date						
2020-01-01	2168.000000	2183.899902	2154.000000	2167.600098	2008.583252	1354908
2020-01-02	2179.949951	2179.949951	2149.199951	2157.649902	1999.362549	2380752
2020-01-03	2164.000000	2223.000000	2164.000000	2200.649902	2039.208374	4655761
2020-01-06	2205.000000	2225.949951	2187.899902	2200.449951	2039.022583	3023209
2020-01-07	2200.500000	2214.649902	2183.800049	2205.850098	2044.026733	2429317
...	...	...	...	...	...	...
2023-12-06	3532.600098	3612.850098	3525.149902	3604.100098	3604.100098	1896572
2023-12-07	3605.000000	3630.550049	3591.699951	3614.899902	3614.899902	1967653
2023-12-08	3633.000000	3645.000000	3602.050049	3626.699951	3626.699951	1641155
2023-12-11	3622.899902	3653.000000	3615.000000	3642.899902	3642.899902	1102503
2023-12-12	3638.949951	3698.399902	3631.000000	3669.949951	3669.949951	1666711

Compute and Plot the Hodrick Prescott Filter Components

# Use Statsmodels to compute Hodrick Prescott Filter Components
X = TCS['Close']
cycle, trend = sm.tsa.filters.hpfilter(X, lamb=1600)  # You can adjust the smoothing parameter 'lamb'


# Plot Stock Price, HP Cycle Component & HP Trend Component
fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 12
fig_size[1] = 12

plt.subplot(3, 1, 1)
plt.rcParams["figure.figsize"] = fig_size
plt.plot_date(x=TCS.index, y=TCS['Close'], fmt="r-")
plt.title("Stock:TCS - Hodrick Prescott Filter Analysis")
plt.ylabel("Price")
plt.xlabel("Date")
plt.grid(True)
plt.show()

plt.subplot(3, 1, 2)
plt.rcParams["figure.figsize"] = fig_size
plt.plot_date(x=TCS.index, y=trend, fmt="r-")
plt.title("Stock:TCS - Trend Component")
plt.ylabel("Trend")
plt.xlabel("Date")
plt.grid(True)
plt.show()

plt.subplot(3, 1, 3)
plt.rcParams["figure.figsize"] = fig_size
plt.plot_date(x=TCS.index, y=cycle, fmt="r-")
plt.title("Stock:TCS - Cycle Component")
plt.ylabel("Cycle")
plt.xlabel("Date")
plt.grid(True)
plt.show()
Hodrick Prescott filter Python analysis: TCS daily closing price chart
HP filter trend component of TCS stock price computed in Python
HP filter cycle component of TCS stock price oscillating around zero

The above chart shows the Stock TCS price and HP Filter components trend and cycles component. You might have noticed that the trend component is ultra-smooth and very good in forecasting the future of medium TCS price direction. And the Cycle Component extreme values suggest a possible trend reversal. To my view, it should be a better tool for traders and investors to know the underlying trend. Especially HP filter suits both trend followers and mean reversion traders!

Updated version (2026)

The original code was written in December 2023. Two library changes affect it today:

  • Matplotlib: plt.plot_date() was deprecated in Matplotlib 3.9 and scheduled for removal in 3.11 (Matplotlib docs). Plot datetime data with plot() instead.
  • yfinance: recent versions warn when download() is called without auto_adjust, and added a multi_level_index option (yfinance changelog). Setting both explicitly gives you a plain Close column for a single ticker.

This Hodrick Prescott filter Python version uses one figure with three stacked panels, overlays the trend on price, and was tested with statsmodels 0.15, pandas 3.1, Matplotlib 3.11 and yfinance 1.8:

import yfinance as yf
import matplotlib.pyplot as plt
import statsmodels.api as sm

# Daily data for TCS (NSE). auto_adjust and multi_level_index are set explicitly
# so the columns are plain Open, High, Low, Close, Volume.
tcs = yf.download("TCS.NS", start="2020-01-01", end="2026-10-31",
                  auto_adjust=True, multi_level_index=False, progress=False)

close = tcs["Close"].dropna()

# hpfilter returns (cycle, trend). 1600 is the textbook value for quarterly data;
# for daily prices try much larger values and compare the curves.
cycle, trend = sm.tsa.filters.hpfilter(close, lamb=1600)

fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 12), sharex=True)

ax1.plot(close.index, close, color="red", label="Close")
ax1.plot(trend.index, trend, color="black", label="HP trend")
ax1.set_title("TCS - Hodrick Prescott Filter Analysis")
ax1.set_ylabel("Price")
ax1.legend()
ax1.grid(True)

ax2.plot(trend.index, trend, color="red")
ax2.set_title("Trend Component")
ax2.set_ylabel("Trend")
ax2.grid(True)

ax3.plot(cycle.index, cycle, color="red")
ax3.axhline(0, color="grey", linewidth=1)
ax3.set_title("Cycle Component")
ax3.set_ylabel("Cycle")
ax3.set_xlabel("Date")
ax3.grid(True)

plt.tight_layout()
plt.show()

Choosing Lambda for Daily Stock Prices

Lambda is the only setting, and the right value depends on how often your data is sampled. The statsmodels documentation suggests 1600 for quarterly data and, following Ravn and Uhlig, 6.25 for annual and 129,600 for monthly data (source). Those numbers follow a power-of-four rule on the sampling frequency.

Data frequencySuggested lambdaWhat you get
Annual6.25Long-run trend only
Quarterly1,600Business cycle trend (the original use)
Monthly129,600Smooth multi-year trend
Daily prices with lambda 1,600 (as in this post)1,600A trend that follows swings of a few weeks; good for short cycles

Applying the same rule to daily data (about 63 trading days per quarter) gives a lambda in the tens of billions, which produces a trend close to a straight line. In practice, traders treat lambda as a tuning knob: try 1,600, 100,000 and 1,000,000 on the same chart and pick the value whose trend matches the swing length you trade.

The Look-Ahead Problem: Read This Before Backtesting

The HP filter is a two-sided filter. When you run it on the full 2020 to 2023 TCS series, the trend value for, say, March 2021 is computed using prices from after March 2021 too. That is why the historical trend looks so smooth and appears to turn at exactly the right places. In live trading you never have those future prices, and the value at the right edge of the chart keeps getting revised as new bars arrive.

To test an HP-based signal honestly, compute a one-sided (causal) trend: run the filter on data up to each bar and keep only the last value. It is slower, but it shows what you would actually have seen in real time:

import numpy as np
import pandas as pd
import statsmodels.api as sm

def causal_hp_trend(series, lamb=1600, min_bars=250):
    """One-sided HP trend: for each bar, run the filter only on data up to that bar
    and keep the last trend value. Slow, but free of look-ahead bias."""
    values = []
    for i in range(len(series)):
        if i + 1 < min_bars:
            values.append(np.nan)
            continue
        _, trend = sm.tsa.filters.hpfilter(series.iloc[: i + 1], lamb=lamb)
        values.append(trend.iloc[-1])
    return pd.Series(values, index=series.index, name="hp_trend_causal")

When I compared the two on a test series, the causal trend lagged the full-sample trend and only matched it at the final bar. Expect the same with stock prices, and judge the filter by the causal version.

How Traders Can Use the HP Filter

  • Trend direction: the slope of the (causal) trend tells you whether the underlying move is up or down, with less noise than a short moving average.
  • Mean reversion: extreme readings in the cycle component mark prices that are stretched far from trend; combine them with a stop and a time exit.
  • Regime context: a flat trend with a large cycle suggests a sideways market; a steep trend with a small cycle suggests a trending one. For a statistical take on regimes, see market regimes with the Wasserstein K-Means algorithm.
  • Comparison with other smoothers: if you need an adaptive, real-time estimate, the Kalman filter estimation of mean in Python updates bar by bar without look-ahead.

Limitations of the HP Filter

  • End-point instability: the last few trend values change as new data arrives.
  • Lambda is subjective, and results can change a lot with it.
  • The filter assumes a smooth trend; sharp gaps, splits or corporate actions distort it, so use adjusted prices.
  • It describes a series; it does not forecast it. Any trading rule needs out-of-sample testing.

If you are new to downloading market data, the complete yfinance guide covers intervals, adjusted prices and multi-ticker downloads.

Frequently Asked Questions

What does the Hodrick Prescott filter do?

It separates a time series into a smooth trend and a cyclical component, so you can see the underlying direction and how far the current value has moved away from it.

What lambda should I use for the HP filter?

1600 is standard for quarterly data, 129,600 for monthly and 6.25 for annual. For daily stock prices there is no fixed standard; test a range of values and pick the one that matches the cycle length you trade.

Does the HP filter repaint?

Yes, in effect. Because it is two-sided, the trend for past bars changes as new data arrives. Use a one-sided version that recalculates on data up to each bar when you backtest.

What does hpfilter return in statsmodels?

It returns two series, cycle first and trend second, so unpack it as cycle, trend = sm.tsa.filters.hpfilter(x, lamb=1600).

Python Elite membership

MEMBERSHIP

Python Elite membership

15 Python, AI and algo trading programs in one plan, including QuantFlow and AI Bootcamp live mentorship.

  • Python from fundamentals to trading apps
  • Backtesting, broker APIs and OpenAlgo
  • Live QuantFlow and AI Bootcamp sessions
View Python Elite →by Marketcalls

Learn by watching: free videos every week

Market Profile, order flow, Python, AI and OpenAlgo tutorials on YouTube.

Rajandran R Creator of OpenAlgo Sketchmaker, RagZ. Building GenAI Applications. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Now the recent curiosity on building Trading AI Agents.

4 Replies to “Hodrick Prescott Filter (HP Filter) Analysis – Python”

  1. I know for a fact that Prescott filter is re-calculating the past bars and so is not reliable due to repainting. So how can it be used for trading?

Leave a Reply

Get Notifications, Alerts on Market Updates, Trading Tools, Automation & More