Trend-following systems often fail during sideways markets. A common solution is to apply a regime filter that activates the strategy only when the market shows persistent directional behavior. One useful statistical measure for this purpose is the Hurst exponent.
This article explains how to combine the Hurst exponent with a simple
moving average crossover to build a trend-following strategy and
implement it in Python using vectorbt.
The strategy trades only when the market demonstrates persistent trending behavior.
The logic combines two components.
A trend signal
Fast moving average above slow moving average indicates bullish momentum.
A market regime filter
The Hurst exponent measures whether price movements behave like a trend, random walk, or mean-reverting process.
Trades are only taken when the regime suggests trending conditions.
The Hurst exponent (H) quantifies the persistence of a time series.
Interpretation
(H = 0.5) → random walk
(H > 0.5) → persistent / trending
(H < 0.5) → mean reverting
The classical estimation method is the Rescaled Range (R/S) method.
\[ \frac{R(n)}{S(n)} \propto n^H \]
Taking logs gives an estimate of (H)
\[ H = \frac{\log(R(n)/S(n))}{\log(n)} \]
Where
\(R(n)\) is the range of cumulative deviations
\(S(n)\) is the standard deviation
\(Y_k = \sum_{t=1}^{k}(X_t - \bar{X})\)
Values above roughly 0.6 often indicate strong trend persistence.
Entry conditions
Fast SMA above slow SMA
Hurst exponent above the trending threshold
Exit conditions
Fast SMA below slow SMA
Hurst exponent drops below the trending threshold
The filter prevents trades during noisy or mean-reverting periods.
The following code downloads historical data using
yfinance.
import numpy as np
import pandas as pd
import yfinance as yf
import vectorbt as vbt
from hurst import compute_Hc
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use("inline")
ticker = "ETH-USD"
start = "2022-01-01"
end = "2025-12-31"
df = yf.download(ticker, start=start, end=end, auto_adjust=True).droplevel(1, 1)
close = df["Close"].dropna()The .droplevel(1, 1) removes the multi-index created by
yfinance so the dataframe is easier to work with.
The Hurst exponent is estimated from log returns, not prices.
log_ret = np.log(close / close.shift(1)).dropna()Log returns stabilize variance and make statistical analysis more reliable.
A rolling window allows the regime to adapt over time.
hurst_window = 120
hurst_values = []
for i in range(len(log_ret)):
if i < hurst_window:
hurst_values.append(np.nan)
else:
window = log_ret.iloc[i-hurst_window:i]
H, _, _ = compute_Hc(window, kind="price", simplified=True)
hurst_values.append(H)
hurst = pd.Series(hurst_values, index=log_ret.index)This produces a time series representing the local persistence of the market.
Simple moving averages provide the trend signal.
fast_sma = 10
slow_sma = 30
fast = close.rolling(fast_sma).mean()
slow = close.rolling(slow_sma).mean()Short windows respond quickly to price changes while longer windows define the broader trend.
The regime filter activates trading only in trending conditions.
hurst_trending = 0.6
trending = hurst > hurst_trendingWhen the Hurst value exceeds the threshold the market is treated as persistent.
Signals combine the trend signal with the regime filter.
entries = (fast > slow) & trending
exits = (fast < slow) | (~trending)Entries require both trend confirmation and favorable market structure.
vectorbt makes it straightforward to simulate the
strategy.
fee = 0.001
pf = vbt.Portfolio.from_signals(
close,
entries,
exits,
fees=fee
)The framework automatically handles position management, transaction costs, and performance metrics.
A quick performance overview can be produced with:
print(pf.stats())
pf.total_return().vbt.plot()The approach can be improved in several ways.
Adaptive thresholds
Instead of a fixed Hurst threshold, use quantiles of historical values.
Multi-timeframe confirmation
Combine Hurst estimates from different windows.
Volatility filtering
Trade only when both volatility and persistence support trend continuation.
Asset diversification
Apply the same regime filter across multiple markets.
The Hurst-Filtered SMA Trend Strategy enhances a classic moving average crossover by incorporating statistical market structure. The Hurst exponent acts as a regime filter that allows trades only during persistent trending conditions.
Combining simple indicators with statistical measures often produces strategies that are both robust and interpretable, making them useful foundations for more advanced systematic trading systems.