Want the full code behind this test? The Mega Backtrader Strategy Pack includes 500+ Backtrader-ready Python strategies, batch runners, dashboards, CSV metrics, chart exports, and documentation. You can get the full package here: Mega Backtrader Strategy Pack.
Trend following usually starts with moving averages, but the Hodrick-Prescott filter takes a different route. It decomposes price into a smoother trend component and a cyclical component, then lets the strategy trade only when trend direction, trend strength, and cycle position line up.
In this BTC-USD daily backtest, HPTrendFollowingStrategy
returned 38.81% while BTC buy-and-hold lost
3.69% over the same period. The strategy finished with
a 42.50% excess return, a 1.04 Sharpe
ratio, and a 12.51% maximum drawdown.
The test used BTC-USD daily candles from July 27, 2024 to July 27, 2026. The benchmark was BTC buy-and-hold.
| Result | Value |
|---|---|
| Strategy return | 38.81% |
| BTC buy-and-hold return | -3.69% |
| Excess return | 42.50% |
| Final portfolio value | $13,881.12 |
| Sharpe ratio | 1.04 |
| Max drawdown | 12.51% |
| Closed trades | 11 |
| Open trades | 1 |
| Win rate | 54.55% |
HPTrendFollowingStrategy is a trend-following system
built around the Hodrick-Prescott filter. Instead of reacting to every
short-term price move, it separates price into a smoothed trend and a
cycle around that trend.
That matters because many trend strategies fail by buying every small breakout and exiting on every pullback. This strategy asks for more confirmation. It wants the fast trend to point in the right direction, the slow trend to support the move, trend strength to clear a threshold, and the cycle component to avoid buying into a weak part of the move.
The strategy logic is:
The package run used the default long-only execution mode. The strategy file includes short-side logic too, but the reported result should be read as the long-only batch-run result unless shorting is explicitly enabled.
The foundation is a custom Backtrader indicator that calculates the Hodrick-Prescott trend and cycle:
class HodrickPrescottFilter(bt.Indicator):
lines = ('hp_trend', 'hp_cycle')
params = (
('lambda_param', 129600),
('lookback', 100),
('min_periods', 50),
)Inside the indicator, the HP filter solves for a smooth trend component. The cycle is simply the difference between price and that trend:
A = I + lambda_param * D2.T @ D2
try:
trend = spsolve(A, data)
cycle = data - trend
return trend, cycle
except:
trend = np.convolve(data, np.ones(min(20, n)) / min(20, n), mode='same')
cycle = data - trend
return trend, cycleThe strategy creates two versions of the HP filter. The fast version responds more quickly, while the slow version acts as the broader trend anchor:
self.fast_hp = HodrickPrescottFilter(
lambda_param=self.params.fast_lambda,
lookback=self.params.lookback,
min_periods=self.params.lookback // 2,
)
self.slow_hp = HodrickPrescottFilter(
lambda_param=self.params.slow_lambda,
lookback=self.params.lookback,
min_periods=self.params.lookback // 2,
)Then it derives trend direction, trend strength, and price position versus the slow trend:
self.fast_trend_direction = self.fast_hp.hp_trend - self.fast_hp.hp_trend(-1)
self.slow_trend_direction = self.slow_hp.hp_trend - self.slow_hp.hp_trend(-1)
self.trend_strength = self.fast_hp.hp_trend / self.slow_hp.hp_trend - 1
self.price_vs_trend = (
self.data.close - self.slow_hp.hp_trend
) / self.slow_hp.hp_trendThe entry rule is selective. A long signal needs fast-trend strength, slow-trend agreement, positive trend strength, and cycle confirmation:
long_trend = (
fast_trend_dir > abs(fast_trend) * self.params.trend_threshold
and slow_trend_dir > 0
and trend_strength > self.params.trend_threshold
)
long_cycle = (
fast_cycle > -abs(slow_trend) * self.params.cycle_threshold
and price_vs_trend > -self.params.cycle_threshold
)
if long_trend and long_cycle:
self.order = self.buy()After entry, the strategy manages risk with a trailing stop, a trend reversal exit, a cycle exit, and a fixed stop loss:
new_stop = current_price * (1 - self.params.trailing_stop_pct)
if new_stop > self.trailing_stop_price:
self.trailing_stop_price = new_stop
trend_reversal = (
fast_trend_dir < -abs(fast_trend) * self.params.trend_threshold
or slow_trend_dir < 0
)
cycle_exit = fast_cycle < -abs(slow_trend) * self.params.cycle_threshold
if (
current_price <= self.trailing_stop_price
or trend_reversal
or cycle_exit
or current_price <= self.entry_price * (1 - self.params.stop_loss_pct)
):
self.order = self.sell()That combination makes the strategy more than a simple trend indicator. It has a signal layer, a confirmation layer, and a position-management layer.
| Setting | Value |
|---|---|
| Asset | BTC-USD |
| Benchmark | BTC-USD |
| Period | 2y |
| Data window | July 27, 2024 to July 27, 2026 |
| Interval | 1d |
| Starting cash | $10,000.00 |
| Final value | $13,881.12 |
| Strategy file | HPTrendFollowingStrategy.py |
| Strategy class | HPTrendFollowingStrategy |
| Metric | Value |
|---|---|
| Starting portfolio value | $10,000.00 |
| Final portfolio value | $13,881.12 |
| Strategy return | 38.81% |
| BTC buy-and-hold return | -3.69% |
| Excess return vs BTC buy-hold | 42.50% |
| Sharpe ratio | 1.04 |
| Max drawdown | 12.51% |
| Total trades | 12 |
| Closed trades | 11 |
| Open trades | 1 |
| Winning trades | 6 |
| Losing trades | 5 |
| Win rate | 54.55% |
| Runtime | 3.53 seconds |
The run ended with one open trade. Final portfolio value includes the mark-to-market value of that position at the final bar, while win rate is calculated from the 11 closed trades.
This is exactly why closed-trade count and return can look disconnected in a backtest report. A strategy can have an open position at the end of the test, and that open position still contributes to final equity even though it is not counted as a closed trade.
The equity curve shows the strategy growing from $10,000.00 to $13,881.12 while BTC buy-and-hold ended slightly negative over the same two-year window.
The important detail is not just the final number. The strategy avoided some of the weaker stretches in BTC and kept enough exposure to participate when the trend improved.
Maximum drawdown was 12.51%, which is moderate for a BTC daily strategy over this window. That is one of the stronger parts of the result: the strategy did not need a 30%, 40%, or 50% drawdown to produce the return.
For research, this chart is as important as the equity curve. A strategy that beats buy-and-hold but does it with uncontrolled drawdowns is much harder to use in practice.
The rolling return chart shows when the edge appeared. This helps separate a usable trend model from a strategy that only looks good because of one final price move.
For this HP-filter version, the value comes from regime selectivity. The strategy is not trying to stay invested every day. It is trying to participate when the smoothed trend structure improves and step aside when the setup weakens.
The daily return distribution excludes zero-return strategy days. In this run, 571 flat strategy-equity days were removed, leaving 159 non-zero strategy return days plotted.
Removing zero-return days makes the distribution easier to read for strategies that spend long periods in cash. Otherwise the histogram gets dominated by a large spike at zero and hides the shape of the actual trading days.
This backtest is a useful example of applying a signal-processing idea to trading. The HP filter is not a trading system by itself. It becomes more useful when it is wrapped in explicit rules for trend direction, trend strength, cycle position, entry confirmation, and exits.
The result also shows why a large strategy library is valuable. You do not need every strategy to work on every asset. You need a repeatable way to scan many complete strategies, find the ones that fit a market, inspect the code, and decide which ideas deserve deeper testing.
That is what the Mega Backtrader Strategy Pack is built for: strategy research, batch backtesting, chart review, and code-level learning across hundreds of Backtrader implementations.
Get the full package here: Mega Backtrader Strategy Pack.
This article is for research and educational use only. Backtest results are not financial advice, investment advice, or a guarantee of future performance. Always validate strategy logic, data quality, execution assumptions, costs, slippage, and risk before using any trading system.