RollingHurst#
Description#
Rolling-window estimator of the Hurst exponent \(H\), a measure of long-range dependence in a time series:
\(H \approx 0.5\) - uncorrelated (e.g. Brownian / white noise / GBM log-returns).
\(H > 0.5\) - persistent (trends tend to continue).
\(H < 0.5\) - anti-persistent (reversion).
Estimation is via rescaled-range (R/S) analysis with the Anis-Lloyd
small-sample correction. Within the trailing window of window_size samples,
the series is partitioned into non-overlapping blocks of length
\(n_k = \texttt{min\_scale}, 2\cdot\texttt{min\_scale}, \dots, W/2\) (dyadic
scales). For each scale we compute the average rescaled range across blocks,
adjust for the small-sample bias, and regress on \(\log n\):
where \(\text{ers}(n)\) is the Anis-Lloyd theoretical R/S for \(H = 1/2\):
Parameters#
window_size(int): trailing-window length \(W\). Must satisfy \(W \ge 4\,\texttt{min\_scale}\) so the regression has at least 3 scales.min_scale(int, default4): smallest block size \(n_0\). Larger values reduce small-sample bias but cost scales for the regression.method(str, default'rs'): only'rs'is supported in v1 (Anis-Lloyd corrected R/S, matching method 2 of the reference paper).
Warmup: first valid output at sample index window_size - 1. Earlier samples
return NaN. Also returns NaN if any block in the window has zero variance.
Notes on usage#
For asset prices, apply
RollingHurstto returns (or log-returns), not the price itself. Prices are typically integrated (random-walk-like) and would estimate \(H \approx 1\) regardless of return-level memory.With
window_size=256andmin_scale=4the regression has 6 scales (4, 8, 16, 32, 64, 128). Smaller windows are noisier; larger windows trade responsiveness for stability.'rs'is the classical Hurst estimator; alternative families (DFA, generalised-Hurst from \(|\Delta x|^q\) moments) may follow in later releases as additionalmethodoptions.
NaN handling#
Policy: ignore. A NaN in any input at index t causes the function to skip that step: output at t is NaN and internal state is unchanged. Subsequent finite samples are processed as if step t had not occurred.
Examples#
Usage example#
import numpy as np
import plotly.graph_objects as go
from screamer import RollingHurst
rng = np.random.default_rng(0)
n = 1500
# Stitch three regimes: white noise, trend (persistent), oscillating (anti-persistent).
wn = rng.normal(0, 1, n // 3)
drift = 0.05 * np.arange(n // 3) + rng.normal(0, 1, n // 3)
osc = rng.normal(0, 1, n - 2 * (n // 3))
osc[1::2] *= -1 # zig-zag → strong mean reversion
x = np.concatenate([wn, drift, osc])
H = RollingHurst(window_size=512)(x)
fig = go.Figure()
fig.add_trace(go.Scatter(y=x, mode='lines', name='signal',
line=dict(color='lightsteelblue'), yaxis='y1'))
fig.add_trace(go.Scatter(y=H, mode='lines', name='RollingHurst(W=512)',
line=dict(color='crimson'), yaxis='y2'))
fig.add_hline(y=0.5, line_dash="dot", line_color="gray",
annotation_text="H = 0.5 (uncorrelated)", yref='y2')
fig.update_layout(
title="Hurst exponent across regime changes",
xaxis_title="Index",
yaxis=dict(title="signal", side='left'),
yaxis2=dict(title="Hurst H", side='right', overlaying='y', range=[0, 1]),
margin=dict(l=20, r=20, t=60, b=20),
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()
Implementation Details#
Circular buffer of size
W.Dyadic scale list and the
ers(n_k)/ \(\sqrt{\pi n_k / 2}\) / \(\log n_k\) tables are precomputed in the constructor (independent of data).Per step: recompute average R/S at each of \(\sim \log_2 W\) scales, then a closed-form OLS slope of \(\log \text{rsal}\) on \(\log n\).
Time complexity: O(W log W) per step.
Space complexity: O(W).
Reference#
Anis, A.A. and Lloyd, E.H. (1976); Peters (1994); Weron (2002);
"Estimating the Hurst exponent" (arXiv:1805.08931). Validated against the
reference Python implementation of the algorithm in tests/test_rolling_hurst.py.