Source code for backtrader.indicators.hurst
#!/usr/bin/env python
"""Hurst Exponent Module - Hurst exponent indicator.
This module provides the Hurst Exponent indicator for measuring
long-term memory of time series.
Classes:
HurstExponent: Hurst exponent indicator (alias: Hurst).
Example:
class MyStrategy(bt.Strategy):
def __init__(self):
# Use at least 2000 samples for stable Hurst values
self.hurst = bt.indicators.Hurst(self.data.close, period=2000)
def next(self):
# H > 0.5: trending series, H < 0.5: mean-reverting
if len(self.data) >= 2000:
if self.hurst[0] > 0.5:
# Trend following strategy
pass
"""
from numpy import asarray, isnan, log10, polyfit, sqrt, std, subtract
from . import PeriodN
__all__ = ["HurstExponent", "Hurst"]
[docs]
class HurstExponent(PeriodN):
"""
References:
- https://www.quantopian.com/posts/hurst-exponent
- https://www.quantopian.com/posts/some-code-from-ernie-chans-new-book-implemented-in-python
Interpretation of the results
1. Geometric random walk (H=0.5)
2. Mean-reverting series (H<0.5)
3. Trending Series (H>0.5)
Important notes:
- The default period is ``40``, but experimentation by users has shown
that it would be advisable to have at least 2000 samples (i.e.: a
period of at least 2000) to have stable values.
- The `lag_start` and `lag_end` values will default to be ``2`` and
``self.p.period / 2`` unless the parameters are specified.
Experimentation by users has also shown that values of around 10 and 500 produce good results
The original values (40, 2, self.p.period / 2) are kept for backwards
compatibility
"""
frompackages = (("numpy", ("asarray", "log10", "polyfit", "sqrt", "std", "subtract")),)
alias = ("Hurst",)
lines = ("hurst",)
params = (
("period", 40), # 2000 was proposed
("lag_start", None), # 10 was proposed
("lag_end", None), # 500 was proposed
)
def _plotlabel(self):
plabels = [self.p.period]
plabels += [self._lag_start]
plabels += [self._lag_end]
return plabels
def __init__(self):
"""Initialize the Hurst Exponent indicator.
Prepares lag arrays for calculating the Hurst exponent,
which measures the long-term memory of a time series.
"""
super().__init__()
# Prepare the lag array
self._lag_start = lag_start = self.p.lag_start or 2
self._lag_end = lag_end = self.p.lag_end or (self.p.period // 2)
self.lags = asarray(range(lag_start, lag_end))
self.log10lags = log10(self.lags)
[docs]
def next(self):
"""Calculate Hurst Exponent for the current bar."""
# Fetch the data
ts = asarray(self.data.get(size=self.p.period))
# Calculate the array of the variances of the lagged differences
tau = [sqrt(std(subtract(ts[lag:], ts[:-lag]))) for lag in self.lags]
# Use a linear fit to estimate the Hurst Exponent
poly = polyfit(self.log10lags, log10(tau), 1)
# Return the Hurst exponent from the polyfit output
self.lines.hurst[0] = poly[0] * 2.0
[docs]
def once(self, start, end):
"""Calculate Hurst Exponent in runonce mode"""
dst = self.lines[0].array
src = self.data.array
period = self.p.period
lags = self.lags
log10lags = self.log10lags
# Ensure destination array is large enough
while len(dst) < end:
dst.append(0.0)
# Calculate Hurst Exponent for each index
for i in range(start, end):
if i >= period - 1:
# Get data slice for this period
start_idx = i - period + 1
end_idx = i + 1
if end_idx <= len(src):
ts = asarray([float(x) for x in src[start_idx:end_idx]])
# Calculate the array of the variances of the lagged differences
tau = []
for lag in lags:
if lag < len(ts):
lagged_diff = subtract(ts[lag:], ts[:-lag])
if len(lagged_diff) > 0:
tau_val = sqrt(std(lagged_diff))
if not isnan(tau_val) and tau_val > 0:
tau.append(tau_val)
# Use a linear fit to estimate the Hurst Exponent
if len(tau) > 1 and len(tau) == len(lags):
try:
log10tau = log10(tau)
poly = polyfit(log10lags, log10tau, 1)
hurst = poly[0] * 2.0
dst[i] = float(hurst) if not isnan(hurst) else float("nan")
except Exception:
dst[i] = float("nan")
else:
dst[i] = float("nan")
else:
dst[i] = float("nan")
else:
dst[i] = float("nan")
Hurst = HurstExponent