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.
- 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
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.
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()



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 withplot()instead. - yfinance: recent versions warn when
download()is called withoutauto_adjust, and added amulti_level_indexoption (yfinance changelog). Setting both explicitly gives you a plainClosecolumn 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 frequency | Suggested lambda | What you get |
|---|---|---|
| Annual | 6.25 | Long-run trend only |
| Quarterly | 1,600 | Business cycle trend (the original use) |
| Monthly | 129,600 | Smooth multi-year trend |
| Daily prices with lambda 1,600 (as in this post) | 1,600 | A 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).
Learn by watching: free videos every week
Market Profile, order flow, Python, AI and OpenAlgo tutorials on YouTube.


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?
Yeh Agree but it is said that Prescott Windows is tradeable http://www.neuroshell.com/manuals/ais1/hodrickprescottwindow.htm which eliminates recalculation for previous bars. And one can manage strategy that is tradeable.
Where is the code? I couldn’t find in this page
You should be able to see the code now. Coz of site updates some of the components got missed out.
Now back to normal.