Fractal-based time series analysis and forecasting.
FracTime uses fractal geometry to analyze and forecast time series data. It computes the Hurst exponent (via R/S, DFA, or wavelet methods), detects regime transitions with HMMs, estimates how long regimes will persist, and generates probabilistic forecasts through Monte Carlo simulation.
pip install fractimeFrom source:
git clone https://github.com/wayy-research/fracTime.git
cd fracTime
pip install -e .import fractime as ft
import numpy as np
prices = 100 * np.cumprod(1 + np.random.default_rng(42).normal(0.0003, 0.01, 600))
# 1. Analyze fractal properties
analyzer = ft.Analyzer(prices, method='dfa')
print(f"Hurst: {analyzer.hurst}") # hurst=0.62
print(f"Regime: {analyzer.regime}") # 'trending'
# 2. Understand regime persistence
hra = ft.HurstRegimeAnalyzer(prices, method='dfa', window=252)
print(f"Hurst regime: {hra.current_regime}")
print(f"Expected remaining: {hra.expected_remaining:.0f} days")
print(f"Recommended horizon: {hra.recommended_horizon} days")
# 3. Forecast with regime-informed horizon
result = ft.Forecaster(prices).predict(steps=hra.recommended_horizon)
print(f"Forecast: {result.forecast[-1]:.2f}")
# 4. Visualize
ft.plot(result)| Method | When to Use | Trade-off |
|---|---|---|
'dfa' |
Default choice. Robust to trends and non-stationarity. Best for financial data. | Slower than R/S |
'rs' |
Quick exploratory analysis. Classic Mandelbrot approach. | Biased by trends |
'wavelet' |
Multi-scale analysis. Good when you need scale-specific Hurst. | Requires PyWavelets |
# Recommended: DFA for production analysis
analyzer = ft.Analyzer(prices, method='dfa', window=63)
# Quick exploration
analyzer = ft.Analyzer(prices, method='rs')
# Multi-scale research
analyzer = ft.Analyzer(prices, method='wavelet')1. Analyze ft.Analyzer(prices, method='dfa')
-> Hurst, fractal dim, volatility, regime
2. Regime check ft.HurstRegimeAnalyzer(prices, method='dfa', window=252)
-> How long will this regime last?
-> What forecast horizon should I use?
3. Forecast ft.Forecaster(prices).predict(steps=horizon)
-> Probabilistic paths weighted by fractal similarity
4. Risk manage ft.RegimeStrategy(bull_allocation=1.0, bear_allocation=0.3)
-> Position sizing based on HMM regime detection
| Window | Period | Use Case |
|---|---|---|
| 21 | ~1 month | Short-term trading signals |
| 63 | ~1 quarter | Default for Analyzer rolling analysis |
| 126 | ~6 months | Medium-term regime analysis |
| 252 | ~1 year | Default for HurstRegimeAnalyzer. Long-term regime persistence |
Empirical testing shows fractal properties are most valuable for risk management (position sizing, horizon selection, regime awareness) rather than direct price prediction. Build strategies around regime detection and adaptive allocation, not Hurst-based directional bets.
The Hurst exponent (H) measures long-term memory in a time series:
| Hurst Value | Behavior | Implication |
|---|---|---|
| H < 0.45 | Mean-reverting | Moves tend to reverse. Contrarian strategies. |
| 0.45 <= H <= 0.55 | Random walk | No exploitable memory. |
| H > 0.55 | Trending | Moves tend to continue. Momentum strategies. |
analyzer = ft.Analyzer(prices, method='dfa')
h = analyzer.hurst.value
ci = analyzer.hurst.ci(0.95)
print(f"H = {h:.3f}, 95% CI: ({ci[0]:.3f}, {ci[1]:.3f})")Measures complexity/roughness of the series (1.0 = smooth, 2.0 = space-filling):
analyzer = ft.Analyzer(prices)
print(f"Fractal dim: {analyzer.fractal_dim}")FracTime offers two complementary regime detection approaches:
Hurst-based regimes (from Analyzer or HurstRegimeAnalyzer):
trending(H > 0.55) /random(0.45-0.55) /mean_reverting(H < 0.45)- Based on the memory structure of the process
HMM-based regimes (from RegimeDetector):
bull/bear/sideways- Based on return distributions
Use both together: HMM tells you what the market is doing, Hurst tells you how persistent that behavior is.
Computes fractal properties with lazy evaluation and caching.
analyzer = ft.Analyzer(
prices,
dates=None, # Optional date array
method='dfa', # 'rs', 'dfa', or 'wavelet'
window=63, # Rolling window size
n_samples=1000, # Bootstrap samples for CIs
min_scale=10, # Min scale for fractal analysis
max_scale=100, # Max scale for fractal analysis
)| Property | Type | Description |
|---|---|---|
hurst |
Metric |
Hurst exponent (0-1) |
fractal_dim |
Metric |
Fractal dimension (1-2) |
volatility |
Metric |
Annualized volatility |
regime |
str |
'trending', 'mean_reverting', or 'random' |
regime_probabilities |
dict |
Probability distribution over regimes |
result |
AnalysisResult |
All metrics as a structured object |
Each Metric has three views:
analyzer.hurst.value # Point estimate: 0.67
analyzer.hurst.rolling # Polars DataFrame with rolling values
analyzer.hurst.ci(0.95) # Bootstrap CI: (0.61, 0.73)
analyzer.hurst.std # Bootstrap standard error
float(analyzer.hurst) # Use as float directly# Multi-dimensional analysis
analyzer = ft.Analyzer({'price': prices, 'volume': volumes})
analyzer['price'].hurst
analyzer.coherence.value # Cross-dimensional fractal coherenceFits an HMM to the rolling Hurst series to detect regime transitions, estimate how long regimes persist, and recommend forecast horizons.
hra = ft.HurstRegimeAnalyzer(
prices,
dates=None, # Optional date array
method='dfa', # 'rs', 'dfa', or 'wavelet'
window=252, # Rolling Hurst window
n_regimes=3, # 2 or 3 HMM states
random_state=42,
trending_threshold=0.55, # H above -> trending
mean_reverting_threshold=0.45, # H below -> mean_reverting
)| Property | Type | Description |
|---|---|---|
current_regime |
str |
'trending', 'random', or 'mean_reverting' |
current_hurst |
float |
Latest rolling H estimate |
regime_age |
int |
Days in current regime |
regime_stability |
float |
Confidence in current regime (0-1) |
expected_duration |
float |
Expected total duration (HMM + empirical blend) |
expected_remaining |
float |
Expected remaining days in regime |
recommended_horizon |
int |
Suggested forecast horizon |
regime_history |
DataFrame |
Full history: date, hurst, regime, age, probability |
change_points |
list[int] |
Indices where regime changed |
transition_matrix |
ndarray |
P[i,j] between Hurst regimes |
hra = ft.HurstRegimeAnalyzer(prices, method='dfa', window=252)
print(hra.summary())
# Use recommended horizon for forecasting
horizon = hra.recommended_horizon
result = ft.Forecaster(prices).predict(steps=horizon)HMM-based market regime detection from returns (bull/bear/sideways).
detector = ft.RegimeDetector(n_regimes=2, random_state=42)
detector.fit(returns)
# Current regime
regime, prob = detector.get_current_regime(returns)
# Transition probabilities
print(detector.transition_matrix)
print(detector.expected_duration())
print(detector.summary())Regime-based position sizing for risk management.
strategy = ft.RegimeStrategy(
bull_allocation=1.0,
bear_allocation=0.3,
sideways_allocation=0.5,
vol_scale=True, # Scale by inverse volatility
vol_target=0.15,
)
results = strategy.backtest(prices)
print(results.summary())
print(f"Sharpe: {results.sharpe:.2f}, Max DD: {results.max_drawdown:.1%}")
# Quick one-liner
results = ft.quick_backtest(prices)Probabilistic forecasting via fractal-weighted Monte Carlo.
model = ft.Forecaster(
prices,
dates=None,
exogenous=None, # {'VIX': vix_prices, ...}
analyzer=None, # Pre-computed Analyzer (for reuse)
lookback=252,
method='rs',
time_warp=False, # Mandelbrot trading time
path_weights={ # How to weight paths
'hurst': 0.3,
'volatility': 0.3,
'pattern': 0.4,
},
)
result = model.predict(steps=30, n_paths=1000, confidence=0.95)
result.forecast # Median forecast
result.mean # Mean forecast
result.ci(0.90) # 90% CI: (lower_array, upper_array)
result.paths # All paths: (n_paths, n_steps)
result.to_frame() # Export to Polars DataFrameDirect Monte Carlo path generation.
sim = ft.Simulator(prices, time_warp=False)
paths = sim.generate(n_paths=1000, steps=30, method='auto')
# Methods: 'auto', 'fbm', 'pattern', 'bootstrap'| Method | Best For |
|---|---|
auto |
General use (selects based on data) |
fbm |
Short histories, strong fractal properties |
pattern |
Long histories with repeating patterns |
bootstrap |
Preserving exact historical distribution |
Combine multiple forecasters.
ensemble = ft.Ensemble(
prices,
models=[
ft.Forecaster(prices, method='rs'),
ft.Forecaster(prices, method='dfa'),
ft.Forecaster(prices, time_warp=True),
],
strategy='weighted', # 'average', 'weighted', 'stacking', 'boosting'
)
result = ensemble.predict(steps=30, n_paths=500)Specialized fractal functions for research:
import fractime as ft
import numpy as np
# Wavelet Hurst estimation
h = ft.compute_wavelet_hurst(prices, wavelet='db4')
h_rolling = ft.compute_rolling_wavelet_hurst(prices, window=252)
# Multifractal Asymmetric Detrended Cross-Correlation (MF-ADCCA)
rho_q, h_xy = ft.compute_mf_adcca(prices_x, prices_y)
# Quantum Price Levels (support/resistance from Schrodinger equation)
levels = ft.compute_qpl(prices, n_levels=5)
# Fractal coherence across dimensions
coherence = ft.compute_fractal_coherence(prices, volume, method='dfa')
# Asymmetric Fractal Trend Dimension (up vs down complexity)
d_plus, d_minus = ft.compute_ftd(prices, window=63)
# DTW-corrected beta (handles lead/lag between assets)
beta = ft.compute_dtw_beta(stock_returns, market_returns)import fractime as ft
import numpy as np
prices = np.cumprod(1 + np.random.default_rng(42).normal(0.0003, 0.01, 600)) * 100
# Step 1: Fractal analysis
analyzer = ft.Analyzer(prices, method='dfa')
print(analyzer.summary())
# Step 2: Regime persistence
hra = ft.HurstRegimeAnalyzer(prices, method='dfa', window=252)
print(f"Regime: {hra.current_regime} (age: {hra.regime_age}d)")
print(f"Stability: {hra.regime_stability:.2f}")
print(f"Remaining: ~{hra.expected_remaining:.0f}d")
# Step 3: Forecast with informed horizon
horizon = hra.recommended_horizon
model = ft.Forecaster(prices, method='dfa')
result = model.predict(steps=horizon)
# Step 4: Examine results
ci = result.ci(0.95)
print(f"{horizon}-day forecast: {result.forecast[-1]:.2f}")
print(f"95% CI: ({ci[0][-1]:.2f}, {ci[1][-1]:.2f})")import fractime as ft
import numpy as np
prices = np.cumprod(1 + np.random.default_rng(42).normal(0.0003, 0.01, 600)) * 100
# HMM regime detection for position sizing
strategy = ft.RegimeStrategy(
bull_allocation=1.0,
bear_allocation=0.3,
vol_scale=True,
vol_target=0.15,
)
results = strategy.backtest(prices)
print(results.summary())
# Combine with Hurst regime for horizon awareness
hra = ft.HurstRegimeAnalyzer(prices, method='dfa', window=252)
if hra.regime_stability > 0.6:
print(f"Stable {hra.current_regime} regime, hold position")
else:
print(f"Unstable regime, reduce exposure")import fractime as ft
ensemble = ft.Ensemble(
prices,
models=[
ft.Forecaster(prices, method='rs'),
ft.Forecaster(prices, method='dfa'),
ft.Forecaster(prices, time_warp=True),
],
strategy='weighted',
)
result = ensemble.predict(steps=30, n_paths=500)
ci = result.ci(0.95)
print(f"Forecast: {result.forecast[-1]:.2f}")
print(f"95% CI: ({ci[0][-1]:.2f}, {ci[1][-1]:.2f})")import fractime as ft
assets = {'SPY': spy_prices, 'TLT': bond_prices, 'GLD': gold_prices}
for name, prices in assets.items():
analyzer = ft.Analyzer(prices, method='dfa')
hra = ft.HurstRegimeAnalyzer(prices, method='dfa', window=252)
print(
f"{name}: H={analyzer.hurst.value:.2f} "
f"regime={hra.current_regime} "
f"remaining={hra.expected_remaining:.0f}d "
f"horizon={hra.recommended_horizon}d"
)import fractime as ft
import polars as pl
analyzer = ft.Analyzer(prices, dates=dates, method='dfa', window=63)
rolling = analyzer.hurst.rolling
# Classify each period
classified = rolling.with_columns(
pl.when(pl.col('value') > 0.55)
.then(pl.lit('trending'))
.when(pl.col('value') < 0.45)
.then(pl.lit('mean_reverting'))
.otherwise(pl.lit('random'))
.alias('regime')
)
print(classified.group_by('regime').count())import fractime as ft
import numpy as np
train, test = prices[:-30], prices[-30:]
# Compare methods
results = {}
for label, kwargs in [
('RS', {'method': 'rs'}),
('DFA', {'method': 'dfa'}),
('Time Warp', {'method': 'rs', 'time_warp': True}),
]:
model = ft.Forecaster(train, **kwargs)
pred = model.predict(steps=30)
rmse = np.sqrt(np.mean((test - pred.forecast) ** 2))
results[label] = rmse
print(f"{label}: RMSE = {rmse:.4f}")import fractime as ft
# Classes
ft.Analyzer # Fractal analysis (Hurst, fractal dim, volatility)
ft.Forecaster # Probabilistic Monte Carlo forecasting
ft.Simulator # Direct path generation
ft.Ensemble # Multi-model combination
ft.RegimeDetector # HMM regime detection (bull/bear/sideways)
ft.RegimeStrategy # Regime-based position sizing
ft.HurstRegimeAnalyzer # Hurst regime persistence & horizon estimation
# Convenience functions
ft.analyze(prices) # Quick analysis
ft.forecast(prices) # Quick forecast
ft.plot(obj) # Plot any FracTime object
ft.quick_backtest(prices) # Quick regime strategy backtest
# Advanced analytics
ft.compute_wavelet_hurst(prices)
ft.compute_rolling_wavelet_hurst(prices)
ft.compute_mf_adcca(x, y)
ft.compute_qpl(prices, n_levels=5)
ft.compute_fractal_coherence(price, volume)
ft.compute_ftd(prices)
ft.compute_dtw_alignment(x, y)
ft.compute_dtw_beta(stock_returns, market_returns)
# Result types
ft.Metric # Single metric with point/rolling/distribution views
ft.AnalysisResult # Complete analysis result
ft.ForecastResult # Forecast with paths and CIs
ft.BacktestResult # Strategy backtest metricsFracTime is optimized for speed:
- Numba JIT for Hurst, DFA, fractal dimension, fBm generation
- Lazy computation with caching (only compute what you access)
- Polars for fast DataFrame operations
| Operation | Time (600 data points) |
|---|---|
| Hurst (point, DFA) | ~5ms |
| Hurst (rolling, 252 window) | ~100ms |
| Hurst (bootstrap, 1000 samples) | ~500ms |
| HurstRegimeAnalyzer | ~200ms |
| Forecast (1000 paths, 30 steps) | ~200ms |
Tips:
- Reuse analyzers across forecasters:
ft.Forecaster(prices, analyzer=analyzer) - Use
method='fbm'for path generation with short histories - Reduce
n_samplesif bootstrap precision isn't critical
MIT License - see LICENSE for details.
@software{fractime,
title = {FracTime: Fractal-based Time Series Analysis and Forecasting},
author = {Wayy Research},
year = {2024},
url = {https://github.com/wayy-research/fracTime}
}