Time Series (wraquant.ts)¶
Time series analysis and forecasting for financial data: decomposition, seasonality detection, changepoint detection, stationarity tests and transformations, feature extraction, anomaly detection, and forecasting.
Submodules:
Decomposition – STL, seasonal, SSA, EMD, wavelets, unobserved components
Forecasting – auto ARIMA, exponential smoothing, Holt-Winters, theta, ensemble, GARCH residual
Stationarity – ADF, KPSS, Phillips-Perron, fractional differencing, detrending
Seasonality – detection, Fourier features, multi-seasonal decomposition
Changepoint – CUSUM, Bayesian changepoint detection
Anomaly – isolation forest, Prophet-based, Grubbs test
Features – autocorrelation, spectral, complexity features
Stochastic – OU, jump-diffusion, regime-switching, and VAR forecasts
Advanced – tsfresh, stumpy, wavelets, sktime, statsforecast, tslearn, darts
Quick Example¶
from wraquant.ts import auto_arima, stl_decompose, adf_test
# Test stationarity
adf = adf_test(returns)
print(f"ADF statistic: {adf['statistic']:.4f}")
print(f"p-value: {adf['p_value']:.4f}")
print(f"Stationary: {adf['is_stationary']}")
# STL decomposition (trend + seasonal + residual)
decomp = stl_decompose(prices, period=252)
trend = decomp['trend']
seasonal = decomp['seasonal']
residual = decomp['residual']
# Auto ARIMA forecasting
forecast = auto_arima(returns, h=21)
print(f"21-day forecast mean: {forecast['mean'].mean():.6f}")
print(f"AIC: {forecast['aic']:.2f}")
print(f"Order: {forecast['order']}")
Ensemble Forecasting¶
from wraquant.ts import ensemble_forecast, forecast_evaluation
# Combine multiple forecasting methods
ensemble = ensemble_forecast(returns, h=21, methods=["arima", "ets", "theta"])
print(f"Ensemble forecast: {ensemble['mean']}")
# Evaluate forecast accuracy with proper metrics
eval_result = forecast_evaluation(actual, predicted)
print(f"MAE: {eval_result['mae']:.6f}")
print(f"RMSE: {eval_result['rmse']:.6f}")
print(f"MAPE: {eval_result['mape']:.2%}")
Stochastic Process Forecasting¶
from wraquant.ts import ornstein_uhlenbeck_forecast, regime_switching_forecast
# OU process forecast (mean-reverting -- good for spreads)
ou = ornstein_uhlenbeck_forecast(spread, h=21)
print(f"OU mean-reversion speed: {ou['theta']:.4f}")
print(f"Long-run mean: {ou['mu']:.4f}")
# Regime-switching forecast
rs = regime_switching_forecast(returns, n_regimes=2, h=21)
print(f"Forecast (accounting for regime probabilities): {rs['mean']}")
See also
Statistics (wraquant.stats) – Statistical tests and distribution fitting
Volatility Modeling (wraquant.vol) – GARCH volatility forecasting
Regime Detection (wraquant.regimes) – Changepoint and regime detection
API Reference¶
Time series analysis and forecasting.
Provides a complete pipeline for financial time series modeling: from decomposing a series into trend, seasonal, and residual components, through stationarity testing and transformation, to forecasting with classical, stochastic-process, and machine-learning-backed methods. Designed for the challenges of financial data – non-stationarity, regime changes, fat tails, and low signal-to-noise ratios.
Key sub-modules:
Decomposition (
decomposition) –seasonal_decompose(additive/ multiplicative),stl_decompose(STL – robust to outliers),ssa_decompose(Singular Spectrum Analysis),emd_decompose(Empirical Mode Decomposition),wavelet_decompose, andunobserved_components(Harvey structural model).Seasonality (
seasonality) –detect_seasonality(auto-detect period),fourier_features(generate sin/cos regressors),multi_fourier_features,seasonal_strength, andmulti_seasonal_decompose.Change-point detection (
changepoint) –detect_changepoints(Bayesian or PELT) andcusumfor detecting structural breaks in mean, variance, or trend.Stationarity (
stationarity) –adf_test,kpss_test,phillips_perron,variance_ratio_test,difference,fractional_difference(preserve memory while achieving stationarity),detrend, andoptimal_differencing.Features (
features) –autocorrelation_features,spectral_features, andcomplexity_featuresfor summarizing time series structure in ML pipelines.Anomaly detection (
anomaly) –isolation_forest_ts,prophet_anomaly, andgrubbs_test_ts.Forecasting (
forecasting) –auto_arima,auto_forecast(automatic model selection),exponential_smoothing,theta_forecast,ensemble_forecast,rolling_forecast,holt_winters_forecast, andforecast_evaluation.Stochastic processes (
stochastic) –ornstein_uhlenbeck_forecast,jump_diffusion_forecast,regime_switching_forecast, andvar_forecast.Advanced integrations (
advanced) – Wrappers for tsfresh, stumpy (matrix profile), tslearn (DTW, k-means), darts, sktime, and statsforecast.
Example
>>> from wraquant.ts import stl_decompose, auto_forecast, adf_test
>>> decomp = stl_decompose(prices, period=252)
>>> stationarity = adf_test(returns)
>>> forecast = auto_forecast(returns, h=20)
Use wraquant.ts for time series modeling and forecasting. For
statistical tests on returns (normality, autocorrelation), see
wraquant.stats. For volatility-specific forecasting (GARCH), see
wraquant.vol. For regime-switching models, see wraquant.regimes.
- seasonal_decompose(data, period=None, model='additive')[source]¶
Decompose a time series into trend, seasonal, and residual components.
Classical decomposition extracts the trend via a centred moving average of width equal to the seasonal period, then computes the seasonal component as the average deviation from the trend for each season.
- When to use:
Use classical decomposition for quick EDA and when the seasonal pattern is approximately constant over time. For production forecasting or when outliers are present, prefer
stl_decomposewhich is more robust.- Mathematical formulation:
Additive: y_t = T_t + S_t + R_t Multiplicative: y_t = T_t * S_t * R_t
where T is trend, S is seasonal, and R is residual.
- How to interpret:
result.trend: long-term direction of the series. NaN at endpoints (half the period on each side).result.seasonal: periodic pattern that repeats everyperiodobservations. Constant across years.result.resid: what remains after removing trend and seasonal. Should look like white noise if the decomposition is adequate.
- Parameters:
data (
Series) – Time series to decompose.period (
int|None, default:None) – Seasonal period (e.g., 12 for monthly data with annual seasonality, 252 for daily with annual). Inferred from the index frequency when None.model (
str, default:'additive') –"additive"(default) – use when seasonal amplitude is roughly constant."multiplicative"– use when seasonal amplitude scales with the level.
- Return type:
- Returns:
DecomposeResultwithtrend,seasonal, andresidattributes, each apd.Series.
Example
>>> import pandas as pd, numpy as np >>> idx = pd.date_range("2020-01-01", periods=120, freq="MS") >>> data = pd.Series(np.arange(120.0) + 5 * np.sin(np.arange(120) * 2 * np.pi / 12), index=idx) >>> result = seasonal_decompose(data, period=12) >>> result.seasonal.iloc[0] != 0 True
See also
stl_decompose: Robust decomposition with time-varying seasonality. trend_filter: Hodrick-Prescott trend extraction.
- stl_decompose(data, period=None)[source]¶
STL (Seasonal and Trend decomposition using Loess) decomposition.
STL uses locally weighted regression (Loess) to extract trend and seasonal components. Unlike classical decomposition, STL allows the seasonal pattern to evolve over time and is robust to outliers via iterative re-weighting.
- When to use:
STL is the recommended default decomposition method. Use it when: - The seasonal pattern changes strength or shape over time. - The data contains outliers or level shifts. - You need values at the endpoints (no NaN padding). Prefer classical
seasonal_decomposeonly for quick EDA where simplicity matters more than accuracy.- Mathematical background:
STL applies two nested Loess smoothers iteratively: 1. Inner loop: extract seasonal component via Loess on
sub-series (one per season), then extract trend via Loess on the deseasonalised series.
Outer loop: compute robustness weights based on residual magnitude, then re-run the inner loop.
- How to interpret:
Same as classical decomposition:
trend,seasonal,resid. The key difference is thatseasonalcan vary over time (plot it to see evolution). Theresidshould be approximately stationary with no remaining seasonal pattern.
- Parameters:
- Return type:
- Returns:
STLForecastresult withtrend,seasonal, andresidattributes, each apd.Series.
Example
>>> import pandas as pd, numpy as np >>> idx = pd.date_range("2020-01-01", periods=365, freq="D") >>> data = pd.Series(np.arange(365.0) + 3 * np.sin(np.arange(365) * 2 * np.pi / 7), index=idx) >>> result = stl_decompose(data, period=7) >>> hasattr(result, "trend") True
See also
seasonal_decompose: Simpler classical decomposition. trend_filter: Hodrick-Prescott trend extraction.
References
Cleveland et al. (1990), “STL: A Seasonal-Trend Decomposition Procedure Based on Loess”
- trend_filter(data, method='hp', lamb=1600)[source]¶
Extract the trend component from a time series.
Currently supports the Hodrick-Prescott (HP) filter, which separates a time series into a smooth trend and a cyclical component by minimising the sum of squared deviations from the trend subject to a penalty on the trend’s second difference (acceleration).
- When to use:
The HP filter is standard in macroeconomic analysis (GDP, unemployment, inflation) where convention dictates its use. For financial time series, prefer STL or moving averages – the HP filter can create spurious cycles and has well-documented endpoint instability (Hamilton 2018).
- Mathematical formulation:
- min_tau sum_{t=1}^T (y_t - tau_t)^2 +
lambda * sum_{t=3}^T ((tau_t - tau_{t-1}) - (tau_{t-1} - tau_{t-2}))^2
where tau is the trend and lambda controls smoothness. Higher lambda => smoother trend.
- How to interpret:
The returned series is the trend component. The cyclical component is
data - trend. Common lambda values: - Monthly data: lambda = 129600 (Ravn & Uhlig) - Quarterly data: lambda = 1600 (the Hodrick-Prescott default) - Annual data: lambda = 6.25
- Parameters:
- Return type:
- Returns:
Trend component as a Series.
- Raises:
ValueError – If method is not recognized.
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(100)) + np.arange(100) * 0.5) >>> trend = trend_filter(data, lamb=1600) >>> len(trend) == len(data) True
See also
stl_decompose: Preferred for most decomposition tasks. seasonal_decompose: Classical moving-average decomposition.
References
Hodrick & Prescott (1997), “Postwar U.S. Business Cycles”
Hamilton (2018), “Why You Should Never Use the HP Filter”
- ssa_decompose(data, window=None, n_components=None, groups=None)[source]¶
Singular Spectrum Analysis (SSA) decomposition.
SSA embeds a time series into a trajectory (Hankel) matrix, applies SVD to extract orthogonal components, and reconstructs interpretable signal components (trend, oscillatory modes, noise).
- SSA vs STL:
STL assumes a fixed seasonal period that you specify up front. It decomposes into exactly three components: trend, seasonal, residual. Works best when the periodicity is known and stable.
SSA is data-adaptive and makes no assumption about periodicity. It discovers the dominant oscillatory modes directly from the data via singular values. Better for non-stationary signals, signals with multiple or changing periodicities, or when you want to separate trend from multiple oscillatory components.
- Algorithm:
Embedding: construct the L x K trajectory matrix (Hankel) where L = window length, K = N - L + 1.
SVD: decompose the trajectory matrix into singular triplets.
Grouping: assign singular triplets to interpretable groups (trend, oscillatory, noise) either automatically or via user specification.
Reconstruction: diagonal averaging (Hankelisation) of each group’s partial matrix to recover the time-domain component.
- Parameters:
data (
Series) – Time series to decompose. NaN values are dropped.window (
int|None, default:None) – Embedding window length L. Should be large enough to capture the longest period of interest but not exceed N/2. IfNone, defaults toN // 2.n_components (
int|None, default:None) – Number of leading singular triplets to retain. IfNone, all are retained.groups (
dict[str,list[int]] |None, default:None) – Optional grouping of singular triplet indices into named components. For example{"trend": [0], "seasonal": [1, 2]}. Each key maps to a list of 0-based component indices. IfNone, each singular triplet is returned as its own component ("component_0","component_1", …).
- Returns:
components: dict mapping component name to reconstructedpd.Series.singular_values: 1-D numpy array of singular values.explained_variance: 1-D numpy array of explained variance ratio per singular value (sums to 1.0 over all values).window: embedding window length used.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> t = np.arange(200, dtype=float) >>> trend = 0.05 * t >>> osc = 3 * np.sin(2 * np.pi * t / 20) >>> noise = np.random.default_rng(42).normal(0, 0.3, 200) >>> data = pd.Series(trend + osc + noise) >>> result = ssa_decompose(data, window=40, n_components=5) >>> # Components sum back to approximately the original >>> recon = sum(result['components'].values()) >>> np.allclose(recon.values, data.values, atol=1e-8) True
References
Golyandina, N. & Zhigljavsky, A. (2013), Singular Spectrum Analysis for Time Series. Springer.
Vautard, R. & Ghil, M. (1989), “Singular spectrum analysis in nonlinear dynamics, with applications to paleoclimatic time series”, Physica D.
- emd_decompose(data, max_imfs=10, max_siftings=100, sifting_threshold=0.05)[source]¶
Empirical Mode Decomposition (EMD) via the sifting algorithm.
EMD adaptively decomposes a non-stationary, non-linear signal into a finite set of Intrinsic Mode Functions (IMFs) and a monotonic residual. Each IMF captures a narrow-band oscillatory mode whose frequency and amplitude can vary over time.
Unlike Fourier or wavelet analysis, EMD makes no assumption about basis functions – the decomposition is entirely data-driven.
- Algorithm (sifting):
Identify local maxima and minima of the signal.
Construct upper and lower envelopes by cubic spline interpolation through the extrema.
Compute the mean of the envelopes.
Subtract the mean from the signal to obtain a candidate IMF.
Repeat steps 1-4 until the candidate satisfies the IMF criteria (symmetric envelopes, near-zero mean).
Subtract the extracted IMF from the signal and repeat from step 1 to extract the next IMF.
- Parameters:
data (
Series) – Time series to decompose. NaN values are dropped.max_imfs (
int, default:10) – Maximum number of IMFs to extract (default 10).max_siftings (
int, default:100) – Maximum sifting iterations per IMF (default 100).sifting_threshold (
float, default:0.05) – Convergence threshold for the normalised squared difference between successive sifting iterations (default 0.05).
- Returns:
imfs: 2-D numpy array of shape(n_imfs, N)where each row is an IMF ordered from highest to lowest frequency.residual: 1-D numpy array of the monotonic residual.n_imfs: number of IMFs extracted.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> t = np.linspace(0, 1, 500) >>> sig = np.sin(2 * np.pi * 5 * t) + np.sin(2 * np.pi * 20 * t) + t >>> data = pd.Series(sig) >>> result = emd_decompose(data, max_imfs=5) >>> result['imfs'].shape[0] >= 1 True >>> np.all(np.isfinite(result['imfs'])) True
References
Huang, N.E. et al. (1998), “The empirical mode decomposition and the Hilbert spectrum for nonlinear and non-stationary time series analysis”, Proceedings of the Royal Society A.
- wavelet_decompose(data, wavelet='db4', level=None)[source]¶
Multi-level discrete wavelet decomposition.
Decomposes a time series into approximation and detail coefficients at multiple resolution levels using the Discrete Wavelet Transform (DWT). At each level, the signal is split into a low-frequency approximation and a high-frequency detail component.
- When to use:
Analysing time-frequency structure at multiple scales.
Denoising (threshold the detail coefficients).
Feature extraction for ML (energy at each scale).
When the signal has transient or localised frequency content that Fourier analysis would miss.
- Parameters:
data (
Series) – Time series to decompose. NaN values are dropped.wavelet (
str, default:'db4') – Wavelet family and order. Common choices:"db4"(Daubechies-4, good general purpose),"haar"(simplest, piecewise constant),"sym5"(near-symmetric Daubechies),"coif3"(Coiflet, near-symmetric with vanishing moments).level (
int|None, default:None) – Decomposition level. IfNone, the maximum useful level is computed automatically viapywt.dwt_max_level.
- Returns:
approximation: 1-D numpy array of the final-level approximation coefficients (lowest frequency content).details: list of 1-D numpy arrays, one per level, ordered from the finest (highest frequency, level 1) to the coarsest (levellevel).details[0]is level 1 (finest detail).wavelet: wavelet name used.level: decomposition level used.coeffs: raw coefficient list[cA_n, cD_n, ..., cD_1]as returned bypywt.wavedecfor advanced use.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> data = pd.Series(np.random.default_rng(42).normal(0, 1, 256)) >>> result = wavelet_decompose(data, wavelet="db4") >>> len(result['details']) == result['level'] True
References
Mallat, S. (2009), A Wavelet Tour of Signal Processing. Academic Press.
- unobserved_components(data, level=True, trend=False, cycle=False, seasonal=None, stochastic_level=True, stochastic_trend=True, stochastic_cycle=True, stochastic_seasonal=True)[source]¶
Unobserved Components Model (UCM) decomposition.
Fits a structural time series model (Harvey 1989) that decomposes the series into interpretable latent components estimated via the Kalman filter and smoother:
Level (local level / random walk): stochastic intercept.
Trend (local linear trend): level + stochastic slope.
Cycle: damped stochastic trigonometric cycle.
Seasonal: time-varying seasonal pattern.
Irregular: white noise observation error.
- The model is:
y_t = mu_t + gamma_t + c_t + epsilon_t
where mu is the trend component (level + slope), gamma is seasonal, c is cycle, and epsilon is irregular.
- When to use:
Macro-economic or business time series where you want interpretable, probabilistic decomposition.
When you need confidence intervals on components (STL does not provide these).
Structural break analysis: sudden changes show up as jumps in the stochastic level.
- Parameters:
data (
Series) – Time series to decompose. Should have a DatetimeIndex for best results, but integer index also works.level (
bool, default:True) – Include a stochastic level component (default True).trend (
bool, default:False) – Include a stochastic trend / slope component (default False). When True, the model is a local linear trend.cycle (
bool, default:False) – Include a damped stochastic cycle (default False).seasonal (
int|None, default:None) – Seasonal period. IfNone, no seasonal component is included.stochastic_level (
bool, default:True) – Allow the level to vary over time (default True).stochastic_trend (
bool, default:True) – Allow the slope to vary over time (default True).stochastic_cycle (
bool, default:True) – Allow the cycle amplitude/phase to vary (default True).stochastic_seasonal (
bool, default:True) – Allow the seasonal pattern to evolve (default True).
- Returns:
trend: pd.Series of the estimated trend (level + slope).trend_ci: pd.DataFrame withloweranduppercolumns (95% confidence interval for the trend).seasonal: pd.Series of the seasonal component (or None if no seasonal was specified).seasonal_ci: pd.DataFrame or None.cycle: pd.Series of the cycle component (or None).cycle_ci: pd.DataFrame or None.irregular: pd.Series of the irregular / residual component.model: the fitted statsmodelsUnobservedComponentsResultsobject for further inspection.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> idx = pd.date_range("2015-01-01", periods=120, freq="MS") >>> t = np.arange(120, dtype=float) >>> y = 0.5 * t + 3 * np.sin(2 * np.pi * t / 12) + np.random.default_rng(42).normal(0, 1, 120) >>> data = pd.Series(y, index=idx) >>> result = unobserved_components(data, level=True, trend=True, seasonal=12) >>> result['trend'] is not None True
References
Harvey, A.C. (1989), Forecasting, Structural Time Series Models and the Kalman Filter. Cambridge University Press.
Durbin, J. & Koopman, S.J. (2012), Time Series Analysis by State Space Methods. Oxford University Press.
- detect_seasonality(data, max_period=365)[source]¶
Detect dominant seasonal periods via spectral analysis.
Uses Welch’s periodogram to identify significant frequency peaks.
- fourier_features(index, period, n_harmonics)[source]¶
Generate Fourier sine/cosine features for a datetime index.
Useful for encoding seasonality as regression features.
- Parameters:
index (
DatetimeIndex) – Datetime index.period (
int) – Seasonal period (in the same time unit as the index).n_harmonics (
int) – Number of Fourier harmonics to generate.
- Return type:
- Returns:
DataFrame with
sin_kandcos_kcolumns for each harmonic.
- multi_fourier_features(index, periods, n_harmonics=3)[source]¶
Generate Fourier terms for multiple seasonal periods.
Creates sin/cos pairs for each period-harmonic combination, producing a feature matrix suitable for regression-based seasonal modelling (e.g., linear regression, Prophet-style models, or as exogenous regressors for ARIMA).
- For a period P and harmonic k, the features are:
sin(2 * pi * k * t / P)andcos(2 * pi * k * t / P)
This captures up to k-th order seasonal variation within each period.
- Parameters:
index (
DatetimeIndex) – Datetime index of the time series.periods (
list[int]) – List of seasonal periods (in the same units as the index). For example,[7, 365]for daily data with weekly and yearly seasonality.n_harmonics (
int|list[int], default:3) – Number of harmonics per period. Can be a single int (applied to all periods) or a list of ints (one per period). Default 3.
- Return type:
- Returns:
DataFrame with columns named
sin_P{period}_H{harmonic}andcos_P{period}_H{harmonic}for each period-harmonic pair. Shape is(len(index), 2 * sum(harmonics)).
Example
>>> import pandas as pd >>> idx = pd.date_range("2020-01-01", periods=365, freq="D") >>> df = multi_fourier_features(idx, periods=[7, 365], n_harmonics=3) >>> df.shape[1] 12
- seasonal_strength(data, period=None)[source]¶
Quantify the strength of seasonality in a time series.
- Uses the Wang, Smith, and Hyndman (2006) measure:
F_s = max(0, 1 - Var(R_t) / Var(S_t + R_t))
where S_t is the seasonal component and R_t is the remainder from an STL decomposition.
- Returns a float in [0, 1]:
0.0: no detectable seasonality (remainder dominates).
1.0: perfectly seasonal (no noise).
> 0.9 is typically considered “strong” seasonality.
< 0.4 is typically “weak”.
- Parameters:
- Return type:
- Returns:
Strength of seasonality as a float in [0, 1].
Example
>>> import numpy as np, pandas as pd >>> t = np.arange(200, dtype=float) >>> pure_seasonal = pd.Series(10 * np.sin(2 * np.pi * t / 20)) >>> strength = seasonal_strength(pure_seasonal, period=20) >>> strength > 0.9 True
References
Wang, X., Smith, K. & Hyndman, R. (2006), “Characteristic- Based Clustering for Time Series Data”, Data Mining and Knowledge Discovery.
- multi_seasonal_decompose(data, periods)[source]¶
Decompose a time series with multiple seasonal periods.
Handles complex seasonality (e.g., daily data with both weekly and yearly patterns) by using MSTL (Multiple Seasonal-Trend decomposition using Loess) from statsmodels when available, or an iterative STL approach as fallback.
- Parameters:
- Returns:
trend: pd.Series of the trend component.seasonal: dict mapping each period to its pd.Series seasonal component.residual: pd.Series of the residual after removing trend and all seasonal components.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> t = np.arange(730, dtype=float) >>> weekly = 3 * np.sin(2 * np.pi * t / 7) >>> yearly = 5 * np.sin(2 * np.pi * t / 365) >>> trend = 0.01 * t >>> data = pd.Series(trend + weekly + yearly) >>> result = multi_seasonal_decompose(data, periods=[7, 365]) >>> sorted(result['seasonal'].keys()) [7, 365]
References
Bandara, K. et al. (2021), “MSTL: A Seasonal-Trend Decomposition Algorithm for Time Series with Multiple Seasonal Patterns”, arXiv:2107.13462.
- cusum(data, threshold=1.0)[source]¶
Cumulative sum (CUSUM) control chart for change detection.
Returns a CUSUM statistic series. Values exceeding threshold standard deviations indicate a potential shift.
- detect_changepoints(data, method='pelt', penalty=None)[source]¶
Detect change-points using the
ruptureslibrary.- Parameters:
- Return type:
- Returns:
List of change-point indices (positions in the array, excluding the final point).
- Raises:
ValueError – If method is not recognized.
- fractional_difference(data, d=0.5, threshold=1e-05)[source]¶
Apply fractional differencing to preserve long-memory information.
Implements the fixed-width window fracdiff method from Advances in Financial Machine Learning (Lopez de Prado).
- detrend(data, method='linear')[source]¶
Remove trend from a time series.
- Parameters:
- Return type:
- Returns:
Detrended series.
- Raises:
ValueError – If method is not recognized.
- adf_test(data, max_lags=None, regression='c', significance=0.05)[source]¶
Augmented Dickey-Fuller (ADF) unit root test.
Tests the null hypothesis that a unit root is present (i.e., the series is non-stationary). A low p-value leads to rejection of the null, concluding stationarity.
- The test regression is:
Delta y_t = alpha + beta*t + gamma*y_{t-1} + sum_i delta_i * Delta y_{t-i} + e_t
where alpha is a constant (if
regression='c'), beta*t is a time trend (ifregression='ct'), and the number of lagged differences is chosen to remove serial correlation in the residuals.- ADF vs KPSS:
ADF: null = unit root (non-stationary). Rejecting H0 supports stationarity.
KPSS: null = stationary. Rejecting H0 supports non-stationarity.
Best practice: run both. If ADF rejects and KPSS does not reject, strong evidence of stationarity. If both reject or both fail to reject, the series may be trend-stationary or difference-stationary.
- Parameters:
data (
Series) – Time series to test. NaN values are dropped.max_lags (
int|None, default:None) – Maximum number of lags for the lagged differences. IfNone, the optimal lag is selected automatically using the AIC criterion.regression (
str, default:'c') – Deterministic terms to include:"c"– constant only (default, most common)."ct"– constant and linear trend."n"– no constant, no trend.significance (
float, default:0.05) – Significance level for theis_stationaryconvenience flag (default 0.05).
- Returns:
test_statistic: float, the ADF t-statistic.p_value: float, MacKinnon approximate p-value.optimal_lag: int, number of lags used.n_obs: int, number of observations used in the regression.critical_values: dict mapping significance levels ("1%","5%","10%") to critical values.is_stationary: bool, True if p-value < significance.interpretation: str, human-readable conclusion.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> # White noise is stationary >>> stationary = pd.Series(rng.normal(0, 1, 500)) >>> result = adf_test(stationary) >>> result['is_stationary'] True
References
Dickey, D.A. & Fuller, W.A. (1979), “Distribution of the Estimators for Autoregressive Time Series With a Unit Root”, JASA.
Said, S.E. & Dickey, D.A. (1984), “Testing for Unit Roots in Autoregressive-Moving Average Models of Unknown Order”, Biometrika.
- kpss_test(data, regression='c', n_lags='auto', significance=0.05)[source]¶
Kwiatkowski-Phillips-Schmidt-Shin (KPSS) stationarity test.
Tests the null hypothesis that the series is stationary around a deterministic trend. This is the opposite of the ADF test: here, rejecting H0 implies non-stationarity.
- When to use ADF vs KPSS vs both:
ADF alone: quick check; but has low power against near-unit-root alternatives.
KPSS alone: confirms stationarity; but may over-reject for long-memory processes.
Both (recommended): a confirmatory strategy. Four cases: 1. ADF rejects, KPSS does not reject -> stationary. 2. ADF does not reject, KPSS rejects -> non-stationary. 3. Both reject -> trend-stationary (difference to remove trend). 4. Neither rejects -> inconclusive, may need more data.
- Parameters:
data (
Series) – Time series to test. NaN values are dropped.regression (
str, default:'c') – Deterministic component:"c"– test for level stationarity (default)."ct"– test for trend stationarity.n_lags (
int|str, default:'auto') – Number of lags for the Newey-West estimator."auto"(default) uses the data-dependent rule. An integer value fixes the lag truncation.significance (
float, default:0.05) – Significance level for theis_stationaryconvenience flag (default 0.05).
- Returns:
test_statistic: float, the KPSS statistic.p_value: float, interpolated p-value (may be bounded at 0.01 or 0.10 depending on the table).n_lags: int, number of lags used.critical_values: dict mapping significance levels to critical values.is_stationary: bool, True if p-value >= significance (i.e., cannot reject the null of stationarity).interpretation: str, human-readable conclusion.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> stationary = pd.Series(rng.normal(0, 1, 500)) >>> result = kpss_test(stationary) >>> result['is_stationary'] True
References
Kwiatkowski, D. et al. (1992), “Testing the null hypothesis of stationarity against the alternative of a unit root”, Journal of Econometrics.
- phillips_perron(data, regression='c', significance=0.05)[source]¶
Phillips-Perron (PP) unit root test.
Like the ADF test, the PP test has the null hypothesis of a unit root (non-stationarity). However, the PP test uses a non-parametric correction to the t-statistic that is robust to heteroskedasticity and serial correlation in the error term, without needing to specify a lag order.
- When to prefer PP over ADF:
When the error process exhibits heteroskedasticity (e.g., financial returns with volatility clustering).
When you are uncertain about the appropriate lag length for ADF.
As a robustness check alongside ADF.
The PP test statistic modifies the ADF statistic with a correction factor based on the Newey-West long-run variance estimate.
- Parameters:
data (
Series) – Time series to test. NaN values are dropped.regression (
str, default:'c') – Deterministic terms:"c"– constant only (default)."ct"– constant and trend."n"– no constant, no trend.significance (
float, default:0.05) – Significance level for theis_stationaryconvenience flag (default 0.05).
- Returns:
test_statistic: float, the PP t-statistic.p_value: float, MacKinnon approximate p-value.n_lags: int, truncation lag for Newey-West.critical_values: dict mapping significance levels to critical values.is_stationary: bool, True if p-value < significance.interpretation: str, human-readable conclusion.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> stationary = pd.Series(rng.normal(0, 1, 500)) >>> result = phillips_perron(stationary) >>> result['is_stationary'] True
References
Phillips, P.C.B. & Perron, P. (1988), “Testing for a Unit Root in Time Series Regression”, Biometrika.
- optimal_differencing(data, max_d=2, significance=0.05)[source]¶
Automatically determine the optimal differencing order for stationarity.
Sequentially applies integer differencing (d = 0, 1, 2, …) and runs the ADF test at each order. Returns the smallest d for which the ADF test rejects the unit root null.
- Parameters:
- Returns:
optimal_d: int, the smallest differencing order that achieves stationarity (ormax_dif stationarity is not achieved).test_results: dict mapping each d to itsadf_testresult dictionary.is_stationary: bool, True if stationarity was achieved withinmax_ddifferences.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> # Random walk needs d=1 >>> rw = pd.Series(np.cumsum(rng.normal(0, 1, 500))) >>> result = optimal_differencing(rw) >>> result['optimal_d'] 1
References
Hyndman, R.J. & Khandakar, Y. (2008), “Automatic Time Series Forecasting: The forecast Package for R”, JSS.
- variance_ratio_test(data, lags=2, overlap=True, significance=0.05)[source]¶
Lo-MacKinlay variance ratio test for the random walk hypothesis.
Tests whether the variance of k-period returns scales linearly with k, as implied by a random walk. A variance ratio VR(k) = 1 is consistent with a random walk; VR(k) > 1 suggests positive autocorrelation (momentum); VR(k) < 1 suggests mean reversion.
- The test statistic under homoskedasticity is:
VR(k) = Var(r_t(k)) / (k * Var(r_t))z = (VR(k) - 1) / sqrt(2*(2k-1)*(k-1) / (3*k*T))
A heteroskedasticity-robust version is also computed.
- Parameters:
data (
Series) – Time series of prices or levels (NOT returns). NaN values are dropped.lags (
int, default:2) – Holding period / aggregation interval k (default 2). Common choices: 2, 4, 8, 16.overlap (
bool, default:True) – Use overlapping returns for better power (default True). Non-overlapping uses fewer observations.significance (
float, default:0.05) – Significance level for theis_random_walkconvenience flag (default 0.05).
- Returns:
variance_ratio: float, the VR(k) statistic.z_statistic: float, the z-statistic (homoskedastic).z_robust: float, heteroskedasticity-robust z-statistic.p_value: float, two-sided p-value from the robust z-statistic.is_random_walk: bool, True if the random walk null cannot be rejected at the given significance level.interpretation: str, human-readable conclusion.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> # Random walk: VR should be close to 1 >>> rw = pd.Series(np.cumsum(rng.normal(0, 1, 1000))) >>> result = variance_ratio_test(rw, lags=2) >>> 0.5 < result['variance_ratio'] < 1.5 True
References
Lo, A.W. & MacKinlay, A.C. (1988), “Stock Market Prices Do Not Follow Random Walks: Evidence from a Simple Specification Test”, Review of Financial Studies.
- autocorrelation_features(data, n_lags=40, significance=0.05)[source]¶
Compute autocorrelation and partial autocorrelation features.
Extracts the ACF and PACF values up to
n_lags, identifies statistically significant lags, and runs the Ljung-Box test for overall serial correlation.- Interpretation:
ACF measures linear dependence between y_t and y_{t-k}. For AR(p) processes, ACF decays exponentially. For MA(q), ACF cuts off after lag q.
PACF measures the correlation between y_t and y_{t-k} after removing the effect of intermediate lags. For AR(p), PACF cuts off after lag p.
Ljung-Box: tests H0 that the first m autocorrelations are jointly zero. A significant result indicates the series has exploitable temporal structure.
- Parameters:
- Returns:
acf: 1-D numpy array of ACF values (lags 0 to n_lags).pacf: 1-D numpy array of PACF values (lags 0 to n_lags).significant_acf_lags: list of lag indices where ACF exceeds the Bartlett confidence band.significant_pacf_lags: list of lag indices where PACF exceeds the confidence band.ljung_box: dict withstatistic,p_value, andis_significantfor the Ljung-Box test at the maximum lag.first_significant_lag: int or None, the first lag with significant ACF (excluding lag 0).
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> # AR(1) process >>> x = np.zeros(500) >>> for i in range(1, 500): ... x[i] = 0.7 * x[i-1] + rng.normal() >>> result = autocorrelation_features(pd.Series(x), n_lags=20) >>> result['acf'][1] > 0.5 # strong lag-1 autocorrelation True
- spectral_features(data, fs=1.0)[source]¶
Compute power spectral density features.
Extracts summary statistics from the periodogram including the dominant frequency, spectral entropy (a measure of spectral complexity), and spectral flatness (how noise-like the signal is).
- Parameters:
- Returns:
dominant_frequency: float, the frequency with the highest spectral power (excluding DC component).dominant_period: float, the period corresponding to the dominant frequency (1 / dominant_frequency).spectral_entropy: float, Shannon entropy of the normalised power spectral density. Higher values indicate a more complex, broadband signal; lower values indicate a few dominant frequencies. Range: [0, log2(N/2)].spectral_flatness: float in [0, 1]. Ratio of geometric mean to arithmetic mean of the PSD. A value near 1 indicates white noise; near 0 indicates a tonal/periodic signal. Also known as Wiener entropy.spectral_centroid: float, the weighted mean frequency (centre of mass of the spectrum).psd: 1-D numpy array of power spectral density values.frequencies: 1-D numpy array of corresponding frequencies.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> t = np.arange(500, dtype=float) >>> pure_tone = pd.Series(np.sin(2 * np.pi * t / 20)) # period=20 >>> result = spectral_features(pure_tone) >>> abs(result['dominant_period'] - 20) < 2 True >>> result['spectral_flatness'] < 0.3 # tonal signal True
- complexity_features(data, m=2, r=None, order=3)[source]¶
Compute entropy-based complexity measures for a time series.
Extracts three entropy measures that quantify the regularity and predictability of the signal:
Sample entropy (SampEn): probability that patterns similar for m points remain similar for m+1 points. Lower values indicate more regularity (self-similarity); higher values indicate more complexity. Defined as -ln(A/B) where A is the count of m+1 length template matches and B is the count of m length matches, within tolerance r.
Approximate entropy (ApEn): similar to SampEn but includes self-matches, making it slightly biased toward regularity. Historically introduced first (Pincus 1991).
Permutation entropy (PeEn): based on the frequency of ordinal patterns of length
order. Robust to noise and monotonic transformations. Normalised to [0, 1].
- Parameters:
data (
Series) – Time series. NaN values are dropped.m (
int, default:2) – Embedding dimension for SampEn and ApEn (default 2). Typical values: 2 or 3.r (
float|None, default:None) – Tolerance threshold for SampEn and ApEn. IfNone, defaults to0.2 * std(data)(standard choice).order (
int, default:3) – Ordinal pattern length for permutation entropy (default 3). Typical values: 3-7.
- Returns:
sample_entropy: float, SampEn(m, r). Values typically range from 0 (perfectly regular) to ~2.5 (random).approximate_entropy: float, ApEn(m, r).permutation_entropy: float, normalised PeEn in [0, 1]. 0 = perfectly predictable, 1 = maximally complex/random.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> regular = pd.Series(np.sin(np.arange(500) * 0.1)) >>> random = pd.Series(rng.normal(0, 1, 500)) >>> reg_feat = complexity_features(regular) >>> rnd_feat = complexity_features(random) >>> reg_feat['sample_entropy'] < rnd_feat['sample_entropy'] True
References
Richman, J.S. & Moorman, J.R. (2000), “Physiological time-series analysis using approximate entropy and sample entropy”, AJP.
Pincus, S.M. (1991), “Approximate entropy as a measure of system complexity”, PNAS.
Bandt, C. & Pompe, B. (2002), “Permutation Entropy: A Natural Complexity Measure for Time Series”, PRL.
- isolation_forest_ts(data, window=20, contamination=0.05, features=None, random_state=42)[source]¶
Anomaly detection using Isolation Forest on rolling features.
Rather than applying Isolation Forest directly to the raw values, this function first computes rolling window features (mean, std, skew, min, max) to capture the context of each observation. This approach detects contextual anomalies – values that are unusual given their local context, even if they are not globally extreme.
- Parameters:
data (
Series) – Time series. NaN values are dropped.window (
int, default:20) – Rolling window size for feature computation (default 20).contamination (
float, default:0.05) – Expected proportion of anomalies in the data (default 0.05). Controls the threshold for the Isolation Forest decision function.features (
list[str] |None, default:None) – List of rolling features to compute. IfNone, uses["mean", "std", "skew", "min", "max"]. Supported values:"mean","std","skew","min","max","median","range".random_state (
int, default:42) – Random seed for reproducibility (default 42).
- Returns:
anomaly_scores: pd.Series of anomaly scores from the Isolation Forest decision function (lower = more anomalous).anomaly_labels: pd.Series of int labels (1 = normal, -1 = anomaly).threshold: float, the decision function threshold.anomaly_indices: list of index values flagged as anomalies.n_anomalies: int, number of detected anomalies.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> x = rng.normal(0, 1, 500) >>> x[100] = 20.0 # inject anomaly >>> x[300] = -15.0 # inject anomaly >>> data = pd.Series(x) >>> result = isolation_forest_ts(data, window=20) >>> result['n_anomalies'] > 0 True
References
Liu, F.T. et al. (2008), “Isolation Forest”, ICDM.
- prophet_anomaly(data, k=3.0, seasonal_period=None, trend=True)[source]¶
Forecast-based anomaly detection using statsmodels.
Fits an Unobserved Components Model (local level + optional seasonal) and flags observations where the actual value deviates from the smoothed state by more than
kstandard deviations of the residuals.This approach does NOT require fbprophet or Prophet. It uses pure statsmodels for a fully open-source, lightweight solution.
- Parameters:
data (
Series) – Time series. NaN values are dropped.k (
float, default:3.0) – Number of standard deviations for the anomaly threshold (default 3.0). Lower values flag more anomalies.seasonal_period (
int|None, default:None) – Seasonal period for the model. IfNone, no seasonal component is included.trend (
bool, default:True) – Include a trend component (default True).
- Returns:
forecast: pd.Series of smoothed / fitted values.residuals: pd.Series of (actual - forecast).anomaly_mask: pd.Series of bool, True for anomalies.anomaly_indices: list of index values flagged as anomalies.upper_bound: pd.Series, forecast + k * sigma.lower_bound: pd.Series, forecast - k * sigma.sigma: float, standard deviation of residuals.n_anomalies: int, number of detected anomalies.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> x = rng.normal(0, 1, 300) >>> x[50] = 15.0 # anomaly >>> x[200] = -12.0 # anomaly >>> data = pd.Series(x) >>> result = prophet_anomaly(data, k=3.0) >>> result['n_anomalies'] >= 2 True
References
Harvey, A.C. (1989), Forecasting, Structural Time Series Models and the Kalman Filter. Cambridge University Press.
- grubbs_test_ts(data, window=50, significance=0.05)[source]¶
Rolling Grubbs test for outlier detection in time series.
Applies the Grubbs test (maximum normed residual test) within a rolling window to detect observations that are statistically unlikely given the local distribution.
- The Grubbs test statistic for a sample of size n is:
G = max|x_i - mean| / std- and the critical value is derived from the t-distribution:
G_crit = ((n-1) / sqrt(n)) * sqrt(t^2 / (n - 2 + t^2))
where
tis the critical value of the t-distribution withn-2degrees of freedom at significancealpha / (2n).- Parameters:
- Returns:
outlier_mask: pd.Series of bool, True for detected outliers.test_statistics: pd.Series of Grubbs G statistics at each position.outlier_indices: list of index values flagged as outliers.n_outliers: int, number of detected outliers.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> x = rng.normal(0, 1, 200) >>> x[75] = 20.0 # obvious outlier >>> data = pd.Series(x) >>> result = grubbs_test_ts(data, window=50) >>> 75 in result['outlier_indices'] True
References
Grubbs, F.E. (1950), “Sample Criteria for Testing Outlying Observations”, Annals of Mathematical Statistics.
- exponential_smoothing(data, **kwargs)[source]¶
Fit a Holt-Winters exponential smoothing model.
Holt-Winters decomposes the series into level, trend, and seasonal components, updating each with exponentially decaying weights. It is one of the most widely used forecasting methods for business and macro time series.
- When to use:
Use exponential smoothing when: - The series has a clear trend and/or seasonal pattern. - You need fast, interpretable forecasts. - The forecast horizon is short (1-2 seasonal cycles). Prefer ARIMA (
auto_arima) when the autocorrelation structure is more complex or when you need differencing to achieve stationarity. Prefer ML models (wraquant.ml) for nonlinear patterns.- Mathematical formulation:
Simple: l_t = alpha * y_t + (1 - alpha) * l_{t-1} Double: adds trend b_t = beta * (l_t - l_{t-1}) + (1 - beta) * b_{t-1} Triple: adds seasonal s_t (additive or multiplicative)
- How to interpret:
Call
.forecast(h)on the returned result for h-step ahead forecasts. Use.summary()to inspect smoothing parameters (alpha, beta, gamma). Low alpha => model relies on history; high alpha => model follows recent data closely.
- Parameters:
- Return type:
- Returns:
Fitted
HoltWintersResultsWrapper. Call.forecast(h)for predictions.
Example
>>> import pandas as pd, numpy as np >>> idx = pd.date_range("2020-01-01", periods=120, freq="MS") >>> data = pd.Series(np.arange(120.0) + np.random.randn(120) * 5, index=idx) >>> result = exponential_smoothing(data, trend="add") >>> forecast = result.forecast(12) >>> len(forecast) 12
See also
auto_arima: Automatic ARIMA model selection. wraquant.ts.decomposition.stl_decompose: Decompose before
forecasting.
References
Holt (1957), “Forecasting seasonals and trends by exponentially weighted moving averages”
Winters (1960), “Forecasting Sales by Exponentially Weighted Moving Averages”
- auto_arima(data, **kwargs)[source]¶
Automatically select and fit the best ARIMA model.
Performs a stepwise or grid search over ARIMA(p,d,q) orders, selecting the model that minimises AIC (by default). This automates the Box-Jenkins methodology: differencing to achieve stationarity, then selecting AR and MA orders.
- When to use:
Use
auto_arimawhen: - You want ARIMA but are unsure of the correct order. - The series has complex autocorrelation (beyond what ETShandles).
You need forecast confidence intervals.
Prefer
exponential_smoothingfor simple trend/seasonal series where speed matters. Preferarima_model_selectionwhen you want to compare all candidate orders explicitly.- How to interpret:
The returned model object has: -
.predict(n_periods)for point forecasts. -.predict_in_sample()for fitted values. -.summary()for model diagnostics (coefficients,AIC/BIC, residual tests).
.order()for the selected (p,d,q) order.
Use
arima_diagnosticsto validate the residuals.
- Parameters:
data (
Series) – Time series to fit. Should have >50 observations for reliable order selection.**kwargs (
Any) – Keyword arguments forwarded topmdarima.auto_arima. Key arguments includeseasonal(bool),m(seasonal period),stepwise(bool, default True for speed),information_criterion("aic"or"bic").
- Return type:
- Returns:
Fitted ARIMA model from pmdarima with
.predict(),.summary(), and.order()methods.- Raises:
ImportError – If the
timeseriesoptional dependency group (pmdarima) is not installed.
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200))) >>> model = auto_arima(data) >>> model.order() (1, 1, 0)
See also
exponential_smoothing: Holt-Winters ETS models. arima_diagnostics: Residual checks for fitted ARIMA models. arima_model_selection: Manual grid search over ARIMA orders.
References
Hyndman & Khandakar (2008), “Automatic Time Series Forecasting: the forecast Package for R”
- arima_diagnostics(model_result, nlags=10, alpha=0.05)[source]¶
Run comprehensive residual diagnostics on a fitted ARIMA model.
After fitting an ARIMA model, it is critical to check that the residuals behave like white noise. This function runs:
Ljung-Box test: no remaining autocorrelation in residuals.
Jarque-Bera test: normality of residuals.
ARCH-LM test: no remaining heteroskedasticity (ARCH effects).
Durbin-Watson statistic: first-order autocorrelation check.
ACF/PACF values: for visual inspection of residual structure.
- Parameters:
model_result (
Any) – Fitted model result fromstatsmodelsARIMA/SARIMAX (must have a.residattribute) orpmdarimaARIMA (must have a.resid()method).nlags (
int, default:10) – Number of lags for autocorrelation tests (default 10).alpha (
float, default:0.05) – Significance level for pass/fail decisions (default 0.05).
- Returns:
ljung_box: dict withstatistic,p_value,pass.jarque_bera: dict withstatistic,p_value,pass.arch_lm: dict withstatistic,p_value,pass.durbin_watson: Durbin-Watson statistic (near 2 = no autocorrelation).acf_values: autocorrelation function values.pacf_values: partial autocorrelation function values.model_adequate:Trueif all diagnostic tests pass.
- Return type:
Example
>>> from statsmodels.tsa.arima.model import ARIMA >>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200))) >>> fit = ARIMA(data, order=(1, 1, 1)).fit() >>> arima_diagnostics(fit)
- arima_model_selection(data, p_range=range(0, 4), d_range=range(0, 3), q_range=range(0, 4), criterion='aic')[source]¶
Compare ARIMA(p,d,q) combinations and rank by information criterion.
Performs a grid search over combinations of ARIMA orders and ranks models by AIC or BIC. Use this to systematically find the best ARIMA specification for a time series.
- Parameters:
data (
Series) – Time series to model.p_range (
range|list[int], default:range(0, 4)) – Range of AR orders to test (default 0-3).d_range (
range|list[int], default:range(0, 3)) – Range of differencing orders to test (default 0-2).q_range (
range|list[int], default:range(0, 4)) – Range of MA orders to test (default 0-3).criterion (
str, default:'aic') – Ranking criterion,"aic"(default) or"bic".
- Returns:
order(tuple),aic,bic,converged, sorted by the chosen criterion (ascending).- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200))) >>> arima_model_selection(data, p_range=range(0, 3), d_range=range(0, 2), q_range=range(0, 3))
- auto_forecast(data, h=10, holdout_pct=0.2, seasonal_period=None)[source]¶
Unified auto-forecasting: try multiple models, pick the best.
Fits several forecasting methods on a training subset, evaluates them on a held-out test set, and returns the forecast from the best model re-fitted on the full data. This is the recommended starting point when you have no prior knowledge about the series dynamics.
- Models tried:
SES: Simple Exponential Smoothing.
Holt-Winters (additive): trend + additive seasonality.
Theta: Theta method.
Seasonal naive: repeat the last seasonal cycle.
Drift: random walk with drift.
- When to use auto_forecast vs manual model selection:
Use
auto_forecastfor rapid prototyping, exploratory work, or when you need a robust default. Switch to manual model selection (arima_model_selection,auto_arima) when you need fine-grained control, interpretability, or domain-specific constraints.
- Parameters:
data (
Series) – Time series (at least 30 observations recommended).h (
int, default:10) – Forecast horizon (default 10).holdout_pct (
float, default:0.2) – Fraction of data reserved for model evaluation (default 0.20).seasonal_period (
int|None, default:None) – Seasonal period for Holt-Winters and seasonal naive. Auto-detected ifNone.
- Returns:
best_model: name of the winning model.forecast: pd.Series of point forecasts from the best model (re-fitted on full data).confidence_intervals: dict withlowerandupperpd.Series (approximate, based on residual std).model_comparison: pd.DataFrame with RMSE, MAE, MAPE per model.residual_diagnostics: dict withmean,std,ljung_box_pvalueof residuals from the best model.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200)) + 100) >>> result = auto_forecast(data, h=10) >>> result['best_model'] in [ ... 'ses', 'holt_winters', 'theta', 'seasonal_naive', 'drift', ... ] True
- theta_forecast(data, h=10, theta=2.0)[source]¶
Theta method forecast (Assimakopoulos & Nikolopoulos, 2000).
The Theta method decomposes the series into two “theta lines”:
Theta-0: the linear regression line (long-run trend).
Theta-2: an amplified version of the original curvature, forecast with Simple Exponential Smoothing (SES).
The final forecast is the average of these two components. Despite its simplicity, Theta consistently ranks among the best methods in forecasting competitions (M3, M4).
- When to use:
Quick, competitive baseline for non-seasonal or deseasonalised series.
When you need a robust method that does not require order selection (unlike ARIMA).
- Math:
Given a series y_t, define the second differences
D^2(y_t) = y_t - 2*y_{t-1} + y_{t-2}. The theta lineZ_theta(t)satisfiesD^2(Z) = theta * D^2(y). Fortheta=0the solution is a straight line; fortheta=2the curvature is doubled.
- Parameters:
- Returns:
forecast: pd.Series of point forecasts.fitted_values: pd.Series of in-sample fitted values.theta_params: dict withtheta,ses_alpha,drift.
- Return type:
References
Assimakopoulos, V., & Nikolopoulos, K. (2000). The Theta model: a decomposition approach to forecasting. International Journal of Forecasting, 16(4), 521-530.
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(100)) + 50) >>> result = theta_forecast(data, h=12) >>> result['forecast'].shape (12,)
- ses_forecast(data, h=10, alpha=None)[source]¶
Simple Exponential Smoothing (SES) with optimal alpha.
SES is the simplest exponential smoothing method. It assigns exponentially decreasing weights to past observations. The single smoothing parameter
alphacontrols how fast old observations are discounted:alphanear 1 => recent data dominates (series is volatile).alphanear 0 => old data persists (series is stable).
- When to use:
Stationary series with no clear trend or seasonality.
Quick baseline forecast.
As a building block inside ensemble or Theta methods.
- Math:
l_t = alpha * y_t + (1 - alpha) * l_{t-1}Forecast:
y_{t+h|t} = l_t(flat forecast for all horizons).
- Parameters:
- Returns:
forecast: pd.Series of point forecasts.alpha: optimised smoothing parameter.fitted_values: pd.Series of in-sample fitted values.residuals: pd.Series of in-sample residuals.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.random.randn(100).cumsum() + 50) >>> result = ses_forecast(data, h=5) >>> len(result['forecast']) 5
- holt_winters_forecast(data, h=10, seasonal='add', seasonal_periods=None, trend='add')[source]¶
Holt-Winters exponential smoothing with trend and seasonality.
This is the full triple exponential smoothing method. It extends SES by adding a trend component (Holt) and a seasonal component (Winters), supporting both additive and multiplicative seasonality.
- When to use:
Series with clear trend and seasonality (e.g., monthly sales, quarterly GDP).
Choose
seasonal='mul'when seasonal fluctuations grow with the level of the series.
- Math (additive):
l_t = alpha * (y_t - s_{t-m}) + (1 - alpha) * (l_{t-1} + b_{t-1})b_t = beta * (l_t - l_{t-1}) + (1 - beta) * b_{t-1}s_t = gamma * (y_t - l_t) + (1 - gamma) * s_{t-m}
- Parameters:
data (
Series) – Time series (must have at least 2 full seasonal cycles).h (
int, default:10) – Forecast horizon (default 10).seasonal (
str, default:'add') –"add"(default) or"mul"for additive or multiplicative seasonality.seasonal_periods (
int|None, default:None) – Number of periods in a season. Auto-detected from the index frequency ifNone.trend (
str|None, default:'add') – Trend component type:"add"(default),"mul", orNonefor no trend.
- Returns:
forecast: pd.Series of point forecasts.params: dict withalpha,beta,gamma.fitted_values: pd.Series of in-sample fitted values.seasonal_components: pd.Series of estimated seasonal factors.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> idx = pd.date_range('2020-01-01', periods=120, freq='MS') >>> seasonal = 10 * np.sin(2 * np.pi * np.arange(120) / 12) >>> data = pd.Series( ... np.arange(120) * 0.5 + seasonal + np.random.randn(120), ... index=idx, ... ) >>> result = holt_winters_forecast(data, h=12, seasonal_periods=12) >>> len(result['forecast']) 12
- ensemble_forecast(data, h=10, methods=None, holdout_pct=0.2, seasonal_period=None)[source]¶
Combine multiple forecasting models via inverse-RMSE weighting.
Ensemble forecasting often outperforms any single model because it diversifies model risk. Weights are proportional to the inverse RMSE on a held-out validation set, so better-performing models contribute more.
- When to use:
When no single model clearly dominates.
For production pipelines where robustness matters more than interpretability.
- Parameters:
data (
Series) – Time series to forecast.h (
int, default:10) – Forecast horizon (default 10).methods (
Optional[Sequence[str]], default:None) – List of method names to include. Supported:"ses","theta","holt_winters","drift". IfNone, uses all four.holdout_pct (
float, default:0.2) – Fraction of data for validation (default 0.20).seasonal_period (
int|None, default:None) – Seasonal period for Holt-Winters. Auto-detected ifNone.
- Returns:
forecast: pd.Series of weighted-average forecasts.weights: dict mapping method name to its weight.individual_forecasts: dict mapping method name to pd.Series forecast.rmse_per_model: dict mapping method name to its hold-out RMSE.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200)) + 100) >>> result = ensemble_forecast(data, h=10) >>> abs(sum(result['weights'].values()) - 1.0) < 1e-6 True
- forecast_evaluation(actual, forecast, naive_forecast=None, benchmark_forecast=None)[source]¶
Comprehensive forecast accuracy metrics.
Computes a wide set of point-forecast accuracy measures used in the forecasting literature. When a benchmark_forecast is provided the Diebold-Mariano test is run to determine whether the two forecasts have significantly different accuracy.
- Metrics included:
RMSE (Root Mean Squared Error): penalises large errors.
MAE (Mean Absolute Error): robust to outliers.
MAPE (Mean Absolute Percentage Error): scale-independent but undefined when actuals are zero.
SMAPE (Symmetric MAPE): bounded version of MAPE.
MdAPE (Median Absolute Percentage Error): robust MAPE.
MASE (Mean Absolute Scaled Error): scaled by in-sample naive error; needs naive_forecast or falls back to
MAE / mean(|diff(actual)|).Directional accuracy: fraction of periods where the forecast correctly predicts the sign of the change.
Diebold-Mariano test: compares this forecast against benchmark_forecast (two-sided, squared-error loss).
- Parameters:
forecast (
Series|ndarray) – Predicted values (same length as actual).naive_forecast (
Series|ndarray|None, default:None) – Optional naive forecast for MASE scaling. IfNone, MASE is computed using the mean absolute first difference of actual.benchmark_forecast (
Series|ndarray|None, default:None) – Optional second forecast for Diebold-Mariano comparison.
- Returns:
rmse,mae,mape,smape,mdape,mase,directional_accuracy.diebold_mariano: dict withstatisticandp_value(only present when benchmark_forecast is given).
- Return type:
References
Diebold, F.X. & Mariano, R.S. (1995). Comparing Predictive Accuracy. Journal of Business & Economic Statistics, 13(3).
Example
>>> import numpy as np >>> actual = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> pred = np.array([1.1, 2.2, 2.8, 4.3, 4.7]) >>> metrics = forecast_evaluation(actual, pred) >>> 'rmse' in metrics True
- rolling_forecast(data, forecast_fn, h=1, initial_window=None, step=1)[source]¶
Walk-forward (expanding window) out-of-sample forecasting.
At each step the model is re-fitted on all data up to time t, then forecasts h steps ahead. The result is a time series of true out-of-sample predictions that can be compared against actuals.
This is the gold standard for evaluating forecasting models because it mimics how the model would be used in production.
- Parameters:
data (
Series) – Full time series.forecast_fn (
Callable[[Series,int],ndarray|Series]) – Callable(train_series, h) -> array-likethat returns h point forecasts.h (
int, default:1) – Forecast horizon at each step (default 1).initial_window (
int|None, default:None) – Minimum training window size. Defaults tomax(30, len(data) // 3).step (
int, default:1) – Number of observations to advance between re-fits (default 1).
- Returns:
forecasts: pd.Series of out-of-sample predictions.actuals: pd.Series of corresponding actual values.errors: pd.Series of forecast errors.cumulative_metrics: dict with cumulative RMSE, MAE.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(100)) + 50) >>> def my_model(train, h): ... return np.full(h, train.iloc[-1]) >>> result = rolling_forecast( ... data, my_model, h=1, initial_window=50, ... ) >>> len(result['forecasts']) > 0 True
- garch_residual_forecast(returns, vol_model='GARCH', forecast_method='ses', horizon=10, garch_p=1, garch_q=1)[source]¶
Forecast returns using GARCH-filtered residuals.
This function chains the
volandtsmodules together:Fit a GARCH model (from
wraquant.vol.models) to extract conditional volatility and standardised residuals.Forecast the standardised residuals (which should be approximately iid) using a classical time-series method (SES, Theta, or Holt- Winters from
wraquant.ts.forecasting).Forecast conditional volatility forward using the GARCH model.
Recombine:
return_forecast = residual_forecast * vol_forecast.
The key insight is that raw financial returns are not iid (they exhibit volatility clustering). GARCH filtering removes the heteroskedasticity, producing residuals that classical forecasting methods can handle. The recombination step re-introduces the time-varying volatility into the forecast.
Chains:
vol.garch_fit->ts.ses_forecast(or other) ->vol.garch_forecast-> recombine.- Parameters:
returns (
Series) – Return series (daily or intraday).vol_model (
str, default:'GARCH') – GARCH variant to use for filtering. Options:"GARCH","EGARCH","GJR".forecast_method (
str, default:'ses') – Method for forecasting the standardised residuals. Options:"ses"(default),"theta","holt_winters".horizon (
int, default:10) – Number of periods to forecast ahead (default 10).garch_p (
int, default:1) – GARCH lag order (default 1).garch_q (
int, default:1) – ARCH lag order (default 1).
- Returns:
'return_forecast'(np.ndarray) – Forecasted returns (residual_forecast * vol_forecast), shape(horizon,).'vol_forecast'(np.ndarray) – GARCH conditional volatility forecast, shape(horizon,).'residual_forecast'(np.ndarray) – Forecasted standardised residuals, shape(horizon,).'standardized_residuals'(np.ndarray) – In-sample standardised residuals from the GARCH fit.'garch_params'(dict) – Fitted GARCH parameters.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> np.random.seed(42) >>> returns = pd.Series(np.random.randn(500) * 0.01) >>> result = garch_residual_forecast(returns, horizon=5) >>> len(result['return_forecast']) 5 >>> len(result['vol_forecast']) 5
Notes
This approach is related to the “GARCH-in-mean” literature but separates the volatility and level dynamics for modularity.
See also
wraquant.vol.models.garch_fit: GARCH model fitting. wraquant.vol.models.garch_forecast: GARCH volatility forecasting. ses_forecast: SES for residual forecasting. theta_forecast: Theta method for residual forecasting.
- ornstein_uhlenbeck_forecast(data, h=10, dt=None, n_simulations=1000, confidence_level=0.95)[source]¶
Forecast using an Ornstein-Uhlenbeck (OU) mean-reverting process.
The OU process is the continuous-time analogue of an AR(1) process and is the workhorse model for mean-reverting financial quantities.
Use for mean-reverting assets like spreads, interest rates, volatility (VIX), pairs-trading residuals, or any series that fluctuates around a long-run equilibrium.
- SDE:
dX_t = theta * (mu - X_t) * dt + sigma * dW_t- where:
theta> 0: speed of mean reversion (higher = faster).mu: long-run mean level.sigma: volatility of the diffusion.W_t: standard Brownian motion.
The half-life of mean reversion is
ln(2) / theta, giving the expected time for a deviation to halve.- Parameter estimation:
Parameters are estimated via Maximum Likelihood on the discrete-time transition density, which is Gaussian:
X_{t+dt} | X_t ~ N(mu + (X_t - mu) * exp(-theta*dt), sigma^2 / (2*theta) * (1 - exp(-2*theta*dt)))
- Parameters:
data (
Series) – Time series of observed values.h (
int, default:10) – Number of steps to forecast (default 10).dt (
float|None, default:None) – Time step between observations. IfNone, inferred from the index (1/252 for business days, 1.0 otherwise).n_simulations (
int, default:1000) – Number of Monte Carlo paths for confidence bands (default 1000).confidence_level (
float, default:0.95) – Confidence level for intervals (default 0.95).
- Returns:
params: dict withtheta,mu,sigma.half_life: half-life of mean reversion (in observation units).forecast: pd.Series of expected path (conditional mean).confidence_intervals: dict withlowerandupperpd.Series.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> rng = np.random.default_rng(42) >>> # Simulate OU process >>> n, theta, mu, sigma = 500, 5.0, 100.0, 2.0 >>> x = np.zeros(n); x[0] = 100.0 >>> dt = 1 / 252 >>> for i in range(1, n): ... x[i] = x[i-1] + theta * (mu - x[i-1]) * dt + sigma * np.sqrt(dt) * rng.normal() >>> data = pd.Series(x) >>> result = ornstein_uhlenbeck_forecast(data, h=20) >>> result['half_life'] > 0 True
References
Uhlenbeck, G.E. & Ornstein, L.S. (1930). On the theory of Brownian motion. Physical Review, 36(5), 823.
- jump_diffusion_forecast(data, h=10, dt=None, n_paths=1000, confidence_level=0.95, seed=None)[source]¶
Merton jump-diffusion model forecast via Monte Carlo simulation.
Extends geometric Brownian motion (GBM) by adding Poisson-driven jumps, capturing the fat tails and sudden moves observed in real asset returns.
- SDE:
dS/S = (mu - lambda*k) * dt + sigma * dW + J * dN- where:
mu: drift of the diffusion component.sigma: diffusion volatility.lambda: jump intensity (expected jumps per unit time).J: jump size,ln(1+J) ~ N(mu_j, sigma_j^2).N_t: Poisson process with intensitylambda.k = exp(mu_j + sigma_j^2/2) - 1.
- Parameter estimation:
A moment-matching approach is used on log-returns: - Jump intensity estimated from excess kurtosis. - Jump mean and variance from the non-Gaussian tail structure.
- Parameters:
data (
Series) – Price series (positive values).h (
int, default:10) – Forecast horizon in steps (default 10).dt (
float|None, default:None) – Time step between observations (default 1/252 for daily).n_paths (
int, default:1000) – Number of Monte Carlo simulation paths (default 1000).confidence_level (
float, default:0.95) – Confidence level for intervals (default 0.95).seed (
int|None, default:None) – Random seed for reproducibility.
- Returns:
params: dict withmu,sigma,lambda_,mu_j,sigma_j.forecast_paths: np.ndarray of shape(n_paths, h)with simulated price paths.mean_forecast: pd.Series of mean across paths.confidence_intervals: dict withlowerandupperpd.Series.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> prices = pd.Series(100 * np.exp(np.cumsum(np.random.randn(200) * 0.01))) >>> result = jump_diffusion_forecast(prices, h=10, n_paths=500, seed=42) >>> result['forecast_paths'].shape (500, 10)
References
Merton, R.C. (1976). Option pricing when underlying stock returns are discontinuous. Journal of Financial Economics, 3(1-2), 125-144.
- regime_switching_forecast(data, n_regimes=2, h=10)[source]¶
Forecast using a Markov regime-switching model.
Fits a Markov-switching autoregression where the series parameters (mean, variance) switch between discrete hidden regimes. Forecasts are blended across regimes weighted by the predicted regime probabilities.
- When to use:
Series that alternate between distinct states (e.g., bull/bear markets, high/low volatility regimes).
When a single-model forecast is inadequate because the data generation process changes over time.
- Parameters:
- Returns:
forecast: pd.Series of blended point forecasts.regime_forecasts: dict mapping regime index to its pd.Series forecast.regime_probs: np.ndarray of shape(h, n_regimes)with predicted regime probabilities.blended: same asforecast(for clarity).
- Return type:
Example
>>> import pandas as pd, numpy as np >>> rng = np.random.default_rng(42) >>> # Bull/bear regime data >>> regimes = np.concatenate([rng.normal(0.1, 0.5, 150), ... rng.normal(-0.05, 1.0, 150)]) >>> data = pd.Series(np.cumsum(regimes)) >>> result = regime_switching_forecast(data, n_regimes=2, h=10) >>> result['regime_probs'].shape[1] 2
References
Hamilton, J.D. (1989). A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle. Econometrica, 57(2), 357-384.
- var_forecast(data, h=10, maxlags=None, ic='aic')[source]¶
Vector Autoregression (VAR) for multi-asset forecasting.
VAR models each variable as a linear function of its own lags and the lags of all other variables. This captures lead-lag relationships (e.g., one asset predicting another) and provides impulse response functions (IRF) and forecast error variance decomposition (FEVD).
- When to use:
Forecasting multiple related time series simultaneously (e.g., a portfolio of assets, yield curve factors).
Analysing dynamic interactions: which variables Granger-cause which.
Computing impulse responses: how a shock to one variable propagates to others.
- Math:
Y_t = c + A_1 * Y_{t-1} + ... + A_p * Y_{t-p} + e_twhere
Y_tis a (k x 1) vector,A_iare (k x k) coefficient matrices, ande_t ~ N(0, Sigma).
- Parameters:
data (
DataFrame) – DataFrame with multiple columns (one per variable). Should be stationary (difference if needed).h (
int, default:10) – Forecast horizon (default 10).maxlags (
int|None, default:None) – Maximum lag order to consider. IfNone, uses12 * (nobs/100)^(1/4)rule of thumb.ic (
str, default:'aic') – Information criterion for lag selection:"aic","bic","hqic","fpe"(default"aic").
- Returns:
forecast: pd.DataFrame of point forecasts.irf: impulse response function results (dict of DataFrames keyed by shocked variable).fevd: forecast error variance decomposition (dict of DataFrames keyed by target variable).granger_causality: dict of p-values for Granger causality tests.lag_order: selected lag order.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> rng = np.random.default_rng(42) >>> n = 200 >>> x1 = np.cumsum(rng.normal(0, 1, n)) >>> x2 = 0.3 * np.roll(x1, 1) + rng.normal(0, 1, n) >>> data = pd.DataFrame({'x1': np.diff(x1), 'x2': np.diff(x2)}) >>> result = var_forecast(data, h=5) >>> result['forecast'].shape[0] 5
References
Lutkepohl, H. (2005). New Introduction to Multiple Time Series Analysis. Springer.
- tsfresh_features(df, column_id='id', column_sort='time')[source]¶
Extract time series features using tsfresh.
Wraps
tsfresh.extract_featureswith efficient defaults and returns a cleaned DataFrame with no NaN/infinite columns.- Parameters:
df (
DataFrame) – Long-format DataFrame containing time series data. Must include the columns specified by column_id and column_sort, plus one or more value columns.column_id (
str, default:'id') – Column identifying distinct time series.column_sort (
str, default:'time') – Column used for temporal ordering.
- Returns:
Extracted features indexed by column_id values. Columns with NaN or infinite values are dropped.
- Return type:
- stumpy_matrix_profile(ts, m=50)[source]¶
Compute the matrix profile of a time series using STUMPY.
The matrix profile identifies the nearest-neighbour distance for every subsequence of length m, enabling motif discovery and discord (anomaly) detection.
- Parameters:
- Returns:
Dictionary containing:
matrix_profile – 1-D array of nearest-neighbour distances.
profile_index – 1-D array of indices of the nearest neighbour.
motif_idx – index of the top motif (lowest MP value).
discord_idx – index of the top discord (highest MP value).
- Return type:
- wavelet_transform(data, wavelet='db4', level=None)[source]¶
Perform discrete wavelet transform (DWT) using PyWavelets.
- Parameters:
- Returns:
Dictionary containing:
coeffs – list of coefficient arrays
[cA_n, cD_n, ..., cD_1].wavelet – wavelet name used.
level – decomposition level used.
- Return type:
- wavelet_denoise(data, wavelet='db4', level=None, threshold='soft')[source]¶
Denoise a signal using wavelet thresholding.
Applies universal (VisuShrink) thresholding to the detail coefficients after a DWT decomposition.
- Parameters:
- Returns:
Denoised signal with the same length as the input.
- Return type:
- sktime_forecast(y, model='naive', horizon=10)[source]¶
Forecast a time series using sktime’s unified interface.
- Parameters:
y (
Series) – Historical observations indexed by a pandas PeriodIndex or integer index.model (
str, default:'naive') –Forecasting model. Supported values:
'naive'– last-value forecast'theta'– Theta method'ets'– exponential smoothing (AutoETS)
horizon (
int, default:10) – Number of steps ahead to forecast.
- Returns:
DataFrame with columns
forecastandindexcovering the forecast horizon.- Return type:
- statsforecast_auto(y, season_length=1, horizon=10)[source]¶
Automatic forecasting using Nixtla’s StatsForecast.
Runs AutoARIMA and returns point forecasts.
- Parameters:
- Returns:
DataFrame with column
forecastindexed over the forecast horizon.- Return type:
- tslearn_kmeans(dataset, n_clusters=3)[source]¶
Cluster time series using DTW-based K-means from tslearn.
- Parameters:
- Returns:
Dictionary containing:
labels – cluster assignments (1-D int array).
inertia – sum of distances to nearest cluster centre.
cluster_centers – array of cluster centre time series.
n_clusters – number of clusters used.
- Return type:
- darts_forecast(data, model='nbeats', horizon=10, **kwargs)[source]¶
Forecast using Darts deep learning models.
Darts provides production-quality implementations of N-BEATS, N-HiTS, Temporal Fusion Transformer, and other deep forecasting models. Use when you need state-of-the-art accuracy and have sufficient data (>1000 observations).
The model is trained on the provided data and produces a forecast of the specified horizon. Default training parameters are tuned for quick experimentation; pass additional keyword arguments to override them (e.g.,
n_epochs=200).- Parameters:
data (
ndarray|Series) – Price or return series. Must be univariate.model (
str, default:'nbeats') –Deep learning model to use:
'nbeats'– N-BEATS (Neural Basis Expansion Analysis).'nhits'– N-HiTS (Neural Hierarchical Interpolation).'tcn'– Temporal Convolutional Network.'rnn'– Simple RNN (LSTM-based).'transformer'– Vanilla Transformer.
horizon (
int, default:10) – Number of steps ahead to forecast.**kwargs (
Any) – Additional keyword arguments passed to the model constructor (e.g.,n_epochs,input_chunk_length).
- Returns:
Dictionary containing:
forecast – 1-D numpy array of forecasted values.
model_name – name of the model used.
training_loss – final training loss (when available).
- Return type:
Example
>>> import numpy as np >>> from wraquant.ts.advanced import darts_forecast >>> data = np.cumsum(np.random.default_rng(0).normal(0, 1, 500)) >>> result = darts_forecast(data, model="nbeats", horizon=5, n_epochs=5) >>> len(result["forecast"]) 5
Notes
Reference: Oreshkin et al. (2020). “N-BEATS: Neural basis expansion analysis for interpretable time series forecasting.” ICLR 2020.
See also
sktime_forecastSimpler statistical forecasting models.
statsforecast_autoAutoARIMA and friends.
auto_forecastwraquant’s built-in automatic forecasting.
Decomposition¶
Time series decomposition methods.
Decomposition separates a time series into interpretable components – typically trend, seasonal, and residual – enabling cleaner analysis and forecasting. This module provides three approaches:
Classical decomposition (
seasonal_decompose) – simple moving- average-based extraction of trend and seasonal components. Fast and easy to understand, but uses fixed seasonal patterns and loses data at endpoints.STL decomposition (
stl_decompose) – Seasonal and Trend decomposition using Loess. Robust to outliers, allows the seasonal component to vary over time, and preserves all data points. This is the recommended default for most applications.Hodrick-Prescott filter (
trend_filter) – extracts a smooth trend by penalising acceleration. Popular in macroeconomics but controversial due to endpoint instability and spurious cycle extraction.
- When to use each:
STL: best general-purpose choice. Handles outliers, time- varying seasonality, and does not lose endpoints. Use this unless you have a specific reason not to.
Classical decompose: when you need a simple, fast decomposition and are comfortable with fixed seasonality. Good for EDA and presentation.
HP filter: for macroeconomic trend extraction where convention dictates its use (GDP, unemployment). Avoid for financial time series where it can create spurious cycles.
References
Cleveland et al. (1990), “STL: A Seasonal-Trend Decomposition Procedure Based on Loess”
Hodrick & Prescott (1997), “Postwar U.S. Business Cycles: An Empirical Investigation”
Hamilton (2018), “Why You Should Never Use the Hodrick-Prescott Filter”
- seasonal_decompose(data, period=None, model='additive')[source]¶
Decompose a time series into trend, seasonal, and residual components.
Classical decomposition extracts the trend via a centred moving average of width equal to the seasonal period, then computes the seasonal component as the average deviation from the trend for each season.
- When to use:
Use classical decomposition for quick EDA and when the seasonal pattern is approximately constant over time. For production forecasting or when outliers are present, prefer
stl_decomposewhich is more robust.- Mathematical formulation:
Additive: y_t = T_t + S_t + R_t Multiplicative: y_t = T_t * S_t * R_t
where T is trend, S is seasonal, and R is residual.
- How to interpret:
result.trend: long-term direction of the series. NaN at endpoints (half the period on each side).result.seasonal: periodic pattern that repeats everyperiodobservations. Constant across years.result.resid: what remains after removing trend and seasonal. Should look like white noise if the decomposition is adequate.
- Parameters:
data (
Series) – Time series to decompose.period (
int|None, default:None) – Seasonal period (e.g., 12 for monthly data with annual seasonality, 252 for daily with annual). Inferred from the index frequency when None.model (
str, default:'additive') –"additive"(default) – use when seasonal amplitude is roughly constant."multiplicative"– use when seasonal amplitude scales with the level.
- Return type:
- Returns:
DecomposeResultwithtrend,seasonal, andresidattributes, each apd.Series.
Example
>>> import pandas as pd, numpy as np >>> idx = pd.date_range("2020-01-01", periods=120, freq="MS") >>> data = pd.Series(np.arange(120.0) + 5 * np.sin(np.arange(120) * 2 * np.pi / 12), index=idx) >>> result = seasonal_decompose(data, period=12) >>> result.seasonal.iloc[0] != 0 True
See also
stl_decompose: Robust decomposition with time-varying seasonality. trend_filter: Hodrick-Prescott trend extraction.
- stl_decompose(data, period=None)[source]¶
STL (Seasonal and Trend decomposition using Loess) decomposition.
STL uses locally weighted regression (Loess) to extract trend and seasonal components. Unlike classical decomposition, STL allows the seasonal pattern to evolve over time and is robust to outliers via iterative re-weighting.
- When to use:
STL is the recommended default decomposition method. Use it when: - The seasonal pattern changes strength or shape over time. - The data contains outliers or level shifts. - You need values at the endpoints (no NaN padding). Prefer classical
seasonal_decomposeonly for quick EDA where simplicity matters more than accuracy.- Mathematical background:
STL applies two nested Loess smoothers iteratively: 1. Inner loop: extract seasonal component via Loess on
sub-series (one per season), then extract trend via Loess on the deseasonalised series.
Outer loop: compute robustness weights based on residual magnitude, then re-run the inner loop.
- How to interpret:
Same as classical decomposition:
trend,seasonal,resid. The key difference is thatseasonalcan vary over time (plot it to see evolution). Theresidshould be approximately stationary with no remaining seasonal pattern.
- Parameters:
- Return type:
- Returns:
STLForecastresult withtrend,seasonal, andresidattributes, each apd.Series.
Example
>>> import pandas as pd, numpy as np >>> idx = pd.date_range("2020-01-01", periods=365, freq="D") >>> data = pd.Series(np.arange(365.0) + 3 * np.sin(np.arange(365) * 2 * np.pi / 7), index=idx) >>> result = stl_decompose(data, period=7) >>> hasattr(result, "trend") True
See also
seasonal_decompose: Simpler classical decomposition. trend_filter: Hodrick-Prescott trend extraction.
References
Cleveland et al. (1990), “STL: A Seasonal-Trend Decomposition Procedure Based on Loess”
- trend_filter(data, method='hp', lamb=1600)[source]¶
Extract the trend component from a time series.
Currently supports the Hodrick-Prescott (HP) filter, which separates a time series into a smooth trend and a cyclical component by minimising the sum of squared deviations from the trend subject to a penalty on the trend’s second difference (acceleration).
- When to use:
The HP filter is standard in macroeconomic analysis (GDP, unemployment, inflation) where convention dictates its use. For financial time series, prefer STL or moving averages – the HP filter can create spurious cycles and has well-documented endpoint instability (Hamilton 2018).
- Mathematical formulation:
- min_tau sum_{t=1}^T (y_t - tau_t)^2 +
lambda * sum_{t=3}^T ((tau_t - tau_{t-1}) - (tau_{t-1} - tau_{t-2}))^2
where tau is the trend and lambda controls smoothness. Higher lambda => smoother trend.
- How to interpret:
The returned series is the trend component. The cyclical component is
data - trend. Common lambda values: - Monthly data: lambda = 129600 (Ravn & Uhlig) - Quarterly data: lambda = 1600 (the Hodrick-Prescott default) - Annual data: lambda = 6.25
- Parameters:
- Return type:
- Returns:
Trend component as a Series.
- Raises:
ValueError – If method is not recognized.
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(100)) + np.arange(100) * 0.5) >>> trend = trend_filter(data, lamb=1600) >>> len(trend) == len(data) True
See also
stl_decompose: Preferred for most decomposition tasks. seasonal_decompose: Classical moving-average decomposition.
References
Hodrick & Prescott (1997), “Postwar U.S. Business Cycles”
Hamilton (2018), “Why You Should Never Use the HP Filter”
- ssa_decompose(data, window=None, n_components=None, groups=None)[source]¶
Singular Spectrum Analysis (SSA) decomposition.
SSA embeds a time series into a trajectory (Hankel) matrix, applies SVD to extract orthogonal components, and reconstructs interpretable signal components (trend, oscillatory modes, noise).
- SSA vs STL:
STL assumes a fixed seasonal period that you specify up front. It decomposes into exactly three components: trend, seasonal, residual. Works best when the periodicity is known and stable.
SSA is data-adaptive and makes no assumption about periodicity. It discovers the dominant oscillatory modes directly from the data via singular values. Better for non-stationary signals, signals with multiple or changing periodicities, or when you want to separate trend from multiple oscillatory components.
- Algorithm:
Embedding: construct the L x K trajectory matrix (Hankel) where L = window length, K = N - L + 1.
SVD: decompose the trajectory matrix into singular triplets.
Grouping: assign singular triplets to interpretable groups (trend, oscillatory, noise) either automatically or via user specification.
Reconstruction: diagonal averaging (Hankelisation) of each group’s partial matrix to recover the time-domain component.
- Parameters:
data (
Series) – Time series to decompose. NaN values are dropped.window (
int|None, default:None) – Embedding window length L. Should be large enough to capture the longest period of interest but not exceed N/2. IfNone, defaults toN // 2.n_components (
int|None, default:None) – Number of leading singular triplets to retain. IfNone, all are retained.groups (
dict[str,list[int]] |None, default:None) – Optional grouping of singular triplet indices into named components. For example{"trend": [0], "seasonal": [1, 2]}. Each key maps to a list of 0-based component indices. IfNone, each singular triplet is returned as its own component ("component_0","component_1", …).
- Returns:
components: dict mapping component name to reconstructedpd.Series.singular_values: 1-D numpy array of singular values.explained_variance: 1-D numpy array of explained variance ratio per singular value (sums to 1.0 over all values).window: embedding window length used.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> t = np.arange(200, dtype=float) >>> trend = 0.05 * t >>> osc = 3 * np.sin(2 * np.pi * t / 20) >>> noise = np.random.default_rng(42).normal(0, 0.3, 200) >>> data = pd.Series(trend + osc + noise) >>> result = ssa_decompose(data, window=40, n_components=5) >>> # Components sum back to approximately the original >>> recon = sum(result['components'].values()) >>> np.allclose(recon.values, data.values, atol=1e-8) True
References
Golyandina, N. & Zhigljavsky, A. (2013), Singular Spectrum Analysis for Time Series. Springer.
Vautard, R. & Ghil, M. (1989), “Singular spectrum analysis in nonlinear dynamics, with applications to paleoclimatic time series”, Physica D.
- emd_decompose(data, max_imfs=10, max_siftings=100, sifting_threshold=0.05)[source]¶
Empirical Mode Decomposition (EMD) via the sifting algorithm.
EMD adaptively decomposes a non-stationary, non-linear signal into a finite set of Intrinsic Mode Functions (IMFs) and a monotonic residual. Each IMF captures a narrow-band oscillatory mode whose frequency and amplitude can vary over time.
Unlike Fourier or wavelet analysis, EMD makes no assumption about basis functions – the decomposition is entirely data-driven.
- Algorithm (sifting):
Identify local maxima and minima of the signal.
Construct upper and lower envelopes by cubic spline interpolation through the extrema.
Compute the mean of the envelopes.
Subtract the mean from the signal to obtain a candidate IMF.
Repeat steps 1-4 until the candidate satisfies the IMF criteria (symmetric envelopes, near-zero mean).
Subtract the extracted IMF from the signal and repeat from step 1 to extract the next IMF.
- Parameters:
data (
Series) – Time series to decompose. NaN values are dropped.max_imfs (
int, default:10) – Maximum number of IMFs to extract (default 10).max_siftings (
int, default:100) – Maximum sifting iterations per IMF (default 100).sifting_threshold (
float, default:0.05) – Convergence threshold for the normalised squared difference between successive sifting iterations (default 0.05).
- Returns:
imfs: 2-D numpy array of shape(n_imfs, N)where each row is an IMF ordered from highest to lowest frequency.residual: 1-D numpy array of the monotonic residual.n_imfs: number of IMFs extracted.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> t = np.linspace(0, 1, 500) >>> sig = np.sin(2 * np.pi * 5 * t) + np.sin(2 * np.pi * 20 * t) + t >>> data = pd.Series(sig) >>> result = emd_decompose(data, max_imfs=5) >>> result['imfs'].shape[0] >= 1 True >>> np.all(np.isfinite(result['imfs'])) True
References
Huang, N.E. et al. (1998), “The empirical mode decomposition and the Hilbert spectrum for nonlinear and non-stationary time series analysis”, Proceedings of the Royal Society A.
- wavelet_decompose(data, wavelet='db4', level=None)[source]¶
Multi-level discrete wavelet decomposition.
Decomposes a time series into approximation and detail coefficients at multiple resolution levels using the Discrete Wavelet Transform (DWT). At each level, the signal is split into a low-frequency approximation and a high-frequency detail component.
- When to use:
Analysing time-frequency structure at multiple scales.
Denoising (threshold the detail coefficients).
Feature extraction for ML (energy at each scale).
When the signal has transient or localised frequency content that Fourier analysis would miss.
- Parameters:
data (
Series) – Time series to decompose. NaN values are dropped.wavelet (
str, default:'db4') – Wavelet family and order. Common choices:"db4"(Daubechies-4, good general purpose),"haar"(simplest, piecewise constant),"sym5"(near-symmetric Daubechies),"coif3"(Coiflet, near-symmetric with vanishing moments).level (
int|None, default:None) – Decomposition level. IfNone, the maximum useful level is computed automatically viapywt.dwt_max_level.
- Returns:
approximation: 1-D numpy array of the final-level approximation coefficients (lowest frequency content).details: list of 1-D numpy arrays, one per level, ordered from the finest (highest frequency, level 1) to the coarsest (levellevel).details[0]is level 1 (finest detail).wavelet: wavelet name used.level: decomposition level used.coeffs: raw coefficient list[cA_n, cD_n, ..., cD_1]as returned bypywt.wavedecfor advanced use.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> data = pd.Series(np.random.default_rng(42).normal(0, 1, 256)) >>> result = wavelet_decompose(data, wavelet="db4") >>> len(result['details']) == result['level'] True
References
Mallat, S. (2009), A Wavelet Tour of Signal Processing. Academic Press.
- unobserved_components(data, level=True, trend=False, cycle=False, seasonal=None, stochastic_level=True, stochastic_trend=True, stochastic_cycle=True, stochastic_seasonal=True)[source]¶
Unobserved Components Model (UCM) decomposition.
Fits a structural time series model (Harvey 1989) that decomposes the series into interpretable latent components estimated via the Kalman filter and smoother:
Level (local level / random walk): stochastic intercept.
Trend (local linear trend): level + stochastic slope.
Cycle: damped stochastic trigonometric cycle.
Seasonal: time-varying seasonal pattern.
Irregular: white noise observation error.
- The model is:
y_t = mu_t + gamma_t + c_t + epsilon_t
where mu is the trend component (level + slope), gamma is seasonal, c is cycle, and epsilon is irregular.
- When to use:
Macro-economic or business time series where you want interpretable, probabilistic decomposition.
When you need confidence intervals on components (STL does not provide these).
Structural break analysis: sudden changes show up as jumps in the stochastic level.
- Parameters:
data (
Series) – Time series to decompose. Should have a DatetimeIndex for best results, but integer index also works.level (
bool, default:True) – Include a stochastic level component (default True).trend (
bool, default:False) – Include a stochastic trend / slope component (default False). When True, the model is a local linear trend.cycle (
bool, default:False) – Include a damped stochastic cycle (default False).seasonal (
int|None, default:None) – Seasonal period. IfNone, no seasonal component is included.stochastic_level (
bool, default:True) – Allow the level to vary over time (default True).stochastic_trend (
bool, default:True) – Allow the slope to vary over time (default True).stochastic_cycle (
bool, default:True) – Allow the cycle amplitude/phase to vary (default True).stochastic_seasonal (
bool, default:True) – Allow the seasonal pattern to evolve (default True).
- Returns:
trend: pd.Series of the estimated trend (level + slope).trend_ci: pd.DataFrame withloweranduppercolumns (95% confidence interval for the trend).seasonal: pd.Series of the seasonal component (or None if no seasonal was specified).seasonal_ci: pd.DataFrame or None.cycle: pd.Series of the cycle component (or None).cycle_ci: pd.DataFrame or None.irregular: pd.Series of the irregular / residual component.model: the fitted statsmodelsUnobservedComponentsResultsobject for further inspection.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> idx = pd.date_range("2015-01-01", periods=120, freq="MS") >>> t = np.arange(120, dtype=float) >>> y = 0.5 * t + 3 * np.sin(2 * np.pi * t / 12) + np.random.default_rng(42).normal(0, 1, 120) >>> data = pd.Series(y, index=idx) >>> result = unobserved_components(data, level=True, trend=True, seasonal=12) >>> result['trend'] is not None True
References
Harvey, A.C. (1989), Forecasting, Structural Time Series Models and the Kalman Filter. Cambridge University Press.
Durbin, J. & Koopman, S.J. (2012), Time Series Analysis by State Space Methods. Oxford University Press.
Forecasting¶
Time series forecasting wrappers.
Provides classical univariate forecasting models (ARIMA, exponential smoothing), automated model selection, and residual diagnostics.
The two primary approaches are:
Exponential Smoothing (ETS / Holt-Winters) – weighted averages of past observations with exponentially decaying weights. Best for series with clear trend and/or seasonal patterns and relatively stable structure. Fast to fit, interpretable, and often competitive with more complex models for short-horizon forecasts.
ARIMA / auto_arima – Box-Jenkins methodology that models the series as a linear combination of its own lags (AR), differences (I), and past forecast errors (MA). More flexible than ETS for capturing complex autocorrelation patterns, but requires careful order selection (handled automatically by
auto_arima).
- When to use each:
ETS: strong seasonality, smooth trend, short horizon (1-12 steps). Typical for retail sales, energy demand, macro indicators.
ARIMA: irregular patterns, no obvious seasonality, or when you need confidence intervals that account for parameter uncertainty.
auto_arimahandles order selection via AIC minimisation.Neither: for high-frequency financial returns (near-random- walk), consider GARCH (
wraquant.vol) for volatility forecasting or ML models (wraquant.ml).
References
Hyndman & Athanasopoulos (2021), “Forecasting: Principles and Practice” (3rd ed.)
Box, Jenkins & Reinsel (2015), “Time Series Analysis”
- exponential_smoothing(data, **kwargs)[source]¶
Fit a Holt-Winters exponential smoothing model.
Holt-Winters decomposes the series into level, trend, and seasonal components, updating each with exponentially decaying weights. It is one of the most widely used forecasting methods for business and macro time series.
- When to use:
Use exponential smoothing when: - The series has a clear trend and/or seasonal pattern. - You need fast, interpretable forecasts. - The forecast horizon is short (1-2 seasonal cycles). Prefer ARIMA (
auto_arima) when the autocorrelation structure is more complex or when you need differencing to achieve stationarity. Prefer ML models (wraquant.ml) for nonlinear patterns.- Mathematical formulation:
Simple: l_t = alpha * y_t + (1 - alpha) * l_{t-1} Double: adds trend b_t = beta * (l_t - l_{t-1}) + (1 - beta) * b_{t-1} Triple: adds seasonal s_t (additive or multiplicative)
- How to interpret:
Call
.forecast(h)on the returned result for h-step ahead forecasts. Use.summary()to inspect smoothing parameters (alpha, beta, gamma). Low alpha => model relies on history; high alpha => model follows recent data closely.
- Parameters:
- Return type:
- Returns:
Fitted
HoltWintersResultsWrapper. Call.forecast(h)for predictions.
Example
>>> import pandas as pd, numpy as np >>> idx = pd.date_range("2020-01-01", periods=120, freq="MS") >>> data = pd.Series(np.arange(120.0) + np.random.randn(120) * 5, index=idx) >>> result = exponential_smoothing(data, trend="add") >>> forecast = result.forecast(12) >>> len(forecast) 12
See also
auto_arima: Automatic ARIMA model selection. wraquant.ts.decomposition.stl_decompose: Decompose before
forecasting.
References
Holt (1957), “Forecasting seasonals and trends by exponentially weighted moving averages”
Winters (1960), “Forecasting Sales by Exponentially Weighted Moving Averages”
- auto_arima(data, **kwargs)[source]¶
Automatically select and fit the best ARIMA model.
Performs a stepwise or grid search over ARIMA(p,d,q) orders, selecting the model that minimises AIC (by default). This automates the Box-Jenkins methodology: differencing to achieve stationarity, then selecting AR and MA orders.
- When to use:
Use
auto_arimawhen: - You want ARIMA but are unsure of the correct order. - The series has complex autocorrelation (beyond what ETShandles).
You need forecast confidence intervals.
Prefer
exponential_smoothingfor simple trend/seasonal series where speed matters. Preferarima_model_selectionwhen you want to compare all candidate orders explicitly.- How to interpret:
The returned model object has: -
.predict(n_periods)for point forecasts. -.predict_in_sample()for fitted values. -.summary()for model diagnostics (coefficients,AIC/BIC, residual tests).
.order()for the selected (p,d,q) order.
Use
arima_diagnosticsto validate the residuals.
- Parameters:
data (
Series) – Time series to fit. Should have >50 observations for reliable order selection.**kwargs (
Any) – Keyword arguments forwarded topmdarima.auto_arima. Key arguments includeseasonal(bool),m(seasonal period),stepwise(bool, default True for speed),information_criterion("aic"or"bic").
- Return type:
- Returns:
Fitted ARIMA model from pmdarima with
.predict(),.summary(), and.order()methods.- Raises:
ImportError – If the
timeseriesoptional dependency group (pmdarima) is not installed.
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200))) >>> model = auto_arima(data) >>> model.order() (1, 1, 0)
See also
exponential_smoothing: Holt-Winters ETS models. arima_diagnostics: Residual checks for fitted ARIMA models. arima_model_selection: Manual grid search over ARIMA orders.
References
Hyndman & Khandakar (2008), “Automatic Time Series Forecasting: the forecast Package for R”
- arima_diagnostics(model_result, nlags=10, alpha=0.05)[source]¶
Run comprehensive residual diagnostics on a fitted ARIMA model.
After fitting an ARIMA model, it is critical to check that the residuals behave like white noise. This function runs:
Ljung-Box test: no remaining autocorrelation in residuals.
Jarque-Bera test: normality of residuals.
ARCH-LM test: no remaining heteroskedasticity (ARCH effects).
Durbin-Watson statistic: first-order autocorrelation check.
ACF/PACF values: for visual inspection of residual structure.
- Parameters:
model_result (
Any) – Fitted model result fromstatsmodelsARIMA/SARIMAX (must have a.residattribute) orpmdarimaARIMA (must have a.resid()method).nlags (
int, default:10) – Number of lags for autocorrelation tests (default 10).alpha (
float, default:0.05) – Significance level for pass/fail decisions (default 0.05).
- Returns:
ljung_box: dict withstatistic,p_value,pass.jarque_bera: dict withstatistic,p_value,pass.arch_lm: dict withstatistic,p_value,pass.durbin_watson: Durbin-Watson statistic (near 2 = no autocorrelation).acf_values: autocorrelation function values.pacf_values: partial autocorrelation function values.model_adequate:Trueif all diagnostic tests pass.
- Return type:
Example
>>> from statsmodels.tsa.arima.model import ARIMA >>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200))) >>> fit = ARIMA(data, order=(1, 1, 1)).fit() >>> arima_diagnostics(fit)
- arima_model_selection(data, p_range=range(0, 4), d_range=range(0, 3), q_range=range(0, 4), criterion='aic')[source]¶
Compare ARIMA(p,d,q) combinations and rank by information criterion.
Performs a grid search over combinations of ARIMA orders and ranks models by AIC or BIC. Use this to systematically find the best ARIMA specification for a time series.
- Parameters:
data (
Series) – Time series to model.p_range (
range|list[int], default:range(0, 4)) – Range of AR orders to test (default 0-3).d_range (
range|list[int], default:range(0, 3)) – Range of differencing orders to test (default 0-2).q_range (
range|list[int], default:range(0, 4)) – Range of MA orders to test (default 0-3).criterion (
str, default:'aic') – Ranking criterion,"aic"(default) or"bic".
- Returns:
order(tuple),aic,bic,converged, sorted by the chosen criterion (ascending).- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200))) >>> arima_model_selection(data, p_range=range(0, 3), d_range=range(0, 2), q_range=range(0, 3))
- theta_forecast(data, h=10, theta=2.0)[source]¶
Theta method forecast (Assimakopoulos & Nikolopoulos, 2000).
The Theta method decomposes the series into two “theta lines”:
Theta-0: the linear regression line (long-run trend).
Theta-2: an amplified version of the original curvature, forecast with Simple Exponential Smoothing (SES).
The final forecast is the average of these two components. Despite its simplicity, Theta consistently ranks among the best methods in forecasting competitions (M3, M4).
- When to use:
Quick, competitive baseline for non-seasonal or deseasonalised series.
When you need a robust method that does not require order selection (unlike ARIMA).
- Math:
Given a series y_t, define the second differences
D^2(y_t) = y_t - 2*y_{t-1} + y_{t-2}. The theta lineZ_theta(t)satisfiesD^2(Z) = theta * D^2(y). Fortheta=0the solution is a straight line; fortheta=2the curvature is doubled.
- Parameters:
- Returns:
forecast: pd.Series of point forecasts.fitted_values: pd.Series of in-sample fitted values.theta_params: dict withtheta,ses_alpha,drift.
- Return type:
References
Assimakopoulos, V., & Nikolopoulos, K. (2000). The Theta model: a decomposition approach to forecasting. International Journal of Forecasting, 16(4), 521-530.
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(100)) + 50) >>> result = theta_forecast(data, h=12) >>> result['forecast'].shape (12,)
- ses_forecast(data, h=10, alpha=None)[source]¶
Simple Exponential Smoothing (SES) with optimal alpha.
SES is the simplest exponential smoothing method. It assigns exponentially decreasing weights to past observations. The single smoothing parameter
alphacontrols how fast old observations are discounted:alphanear 1 => recent data dominates (series is volatile).alphanear 0 => old data persists (series is stable).
- When to use:
Stationary series with no clear trend or seasonality.
Quick baseline forecast.
As a building block inside ensemble or Theta methods.
- Math:
l_t = alpha * y_t + (1 - alpha) * l_{t-1}Forecast:
y_{t+h|t} = l_t(flat forecast for all horizons).
- Parameters:
- Returns:
forecast: pd.Series of point forecasts.alpha: optimised smoothing parameter.fitted_values: pd.Series of in-sample fitted values.residuals: pd.Series of in-sample residuals.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.random.randn(100).cumsum() + 50) >>> result = ses_forecast(data, h=5) >>> len(result['forecast']) 5
- holt_winters_forecast(data, h=10, seasonal='add', seasonal_periods=None, trend='add')[source]¶
Holt-Winters exponential smoothing with trend and seasonality.
This is the full triple exponential smoothing method. It extends SES by adding a trend component (Holt) and a seasonal component (Winters), supporting both additive and multiplicative seasonality.
- When to use:
Series with clear trend and seasonality (e.g., monthly sales, quarterly GDP).
Choose
seasonal='mul'when seasonal fluctuations grow with the level of the series.
- Math (additive):
l_t = alpha * (y_t - s_{t-m}) + (1 - alpha) * (l_{t-1} + b_{t-1})b_t = beta * (l_t - l_{t-1}) + (1 - beta) * b_{t-1}s_t = gamma * (y_t - l_t) + (1 - gamma) * s_{t-m}
- Parameters:
data (
Series) – Time series (must have at least 2 full seasonal cycles).h (
int, default:10) – Forecast horizon (default 10).seasonal (
str, default:'add') –"add"(default) or"mul"for additive or multiplicative seasonality.seasonal_periods (
int|None, default:None) – Number of periods in a season. Auto-detected from the index frequency ifNone.trend (
str|None, default:'add') – Trend component type:"add"(default),"mul", orNonefor no trend.
- Returns:
forecast: pd.Series of point forecasts.params: dict withalpha,beta,gamma.fitted_values: pd.Series of in-sample fitted values.seasonal_components: pd.Series of estimated seasonal factors.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> idx = pd.date_range('2020-01-01', periods=120, freq='MS') >>> seasonal = 10 * np.sin(2 * np.pi * np.arange(120) / 12) >>> data = pd.Series( ... np.arange(120) * 0.5 + seasonal + np.random.randn(120), ... index=idx, ... ) >>> result = holt_winters_forecast(data, h=12, seasonal_periods=12) >>> len(result['forecast']) 12
- forecast_evaluation(actual, forecast, naive_forecast=None, benchmark_forecast=None)[source]¶
Comprehensive forecast accuracy metrics.
Computes a wide set of point-forecast accuracy measures used in the forecasting literature. When a benchmark_forecast is provided the Diebold-Mariano test is run to determine whether the two forecasts have significantly different accuracy.
- Metrics included:
RMSE (Root Mean Squared Error): penalises large errors.
MAE (Mean Absolute Error): robust to outliers.
MAPE (Mean Absolute Percentage Error): scale-independent but undefined when actuals are zero.
SMAPE (Symmetric MAPE): bounded version of MAPE.
MdAPE (Median Absolute Percentage Error): robust MAPE.
MASE (Mean Absolute Scaled Error): scaled by in-sample naive error; needs naive_forecast or falls back to
MAE / mean(|diff(actual)|).Directional accuracy: fraction of periods where the forecast correctly predicts the sign of the change.
Diebold-Mariano test: compares this forecast against benchmark_forecast (two-sided, squared-error loss).
- Parameters:
forecast (
Series|ndarray) – Predicted values (same length as actual).naive_forecast (
Series|ndarray|None, default:None) – Optional naive forecast for MASE scaling. IfNone, MASE is computed using the mean absolute first difference of actual.benchmark_forecast (
Series|ndarray|None, default:None) – Optional second forecast for Diebold-Mariano comparison.
- Returns:
rmse,mae,mape,smape,mdape,mase,directional_accuracy.diebold_mariano: dict withstatisticandp_value(only present when benchmark_forecast is given).
- Return type:
References
Diebold, F.X. & Mariano, R.S. (1995). Comparing Predictive Accuracy. Journal of Business & Economic Statistics, 13(3).
Example
>>> import numpy as np >>> actual = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) >>> pred = np.array([1.1, 2.2, 2.8, 4.3, 4.7]) >>> metrics = forecast_evaluation(actual, pred) >>> 'rmse' in metrics True
- auto_forecast(data, h=10, holdout_pct=0.2, seasonal_period=None)[source]¶
Unified auto-forecasting: try multiple models, pick the best.
Fits several forecasting methods on a training subset, evaluates them on a held-out test set, and returns the forecast from the best model re-fitted on the full data. This is the recommended starting point when you have no prior knowledge about the series dynamics.
- Models tried:
SES: Simple Exponential Smoothing.
Holt-Winters (additive): trend + additive seasonality.
Theta: Theta method.
Seasonal naive: repeat the last seasonal cycle.
Drift: random walk with drift.
- When to use auto_forecast vs manual model selection:
Use
auto_forecastfor rapid prototyping, exploratory work, or when you need a robust default. Switch to manual model selection (arima_model_selection,auto_arima) when you need fine-grained control, interpretability, or domain-specific constraints.
- Parameters:
data (
Series) – Time series (at least 30 observations recommended).h (
int, default:10) – Forecast horizon (default 10).holdout_pct (
float, default:0.2) – Fraction of data reserved for model evaluation (default 0.20).seasonal_period (
int|None, default:None) – Seasonal period for Holt-Winters and seasonal naive. Auto-detected ifNone.
- Returns:
best_model: name of the winning model.forecast: pd.Series of point forecasts from the best model (re-fitted on full data).confidence_intervals: dict withlowerandupperpd.Series (approximate, based on residual std).model_comparison: pd.DataFrame with RMSE, MAE, MAPE per model.residual_diagnostics: dict withmean,std,ljung_box_pvalueof residuals from the best model.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200)) + 100) >>> result = auto_forecast(data, h=10) >>> result['best_model'] in [ ... 'ses', 'holt_winters', 'theta', 'seasonal_naive', 'drift', ... ] True
- ensemble_forecast(data, h=10, methods=None, holdout_pct=0.2, seasonal_period=None)[source]¶
Combine multiple forecasting models via inverse-RMSE weighting.
Ensemble forecasting often outperforms any single model because it diversifies model risk. Weights are proportional to the inverse RMSE on a held-out validation set, so better-performing models contribute more.
- When to use:
When no single model clearly dominates.
For production pipelines where robustness matters more than interpretability.
- Parameters:
data (
Series) – Time series to forecast.h (
int, default:10) – Forecast horizon (default 10).methods (
Optional[Sequence[str]], default:None) – List of method names to include. Supported:"ses","theta","holt_winters","drift". IfNone, uses all four.holdout_pct (
float, default:0.2) – Fraction of data for validation (default 0.20).seasonal_period (
int|None, default:None) – Seasonal period for Holt-Winters. Auto-detected ifNone.
- Returns:
forecast: pd.Series of weighted-average forecasts.weights: dict mapping method name to its weight.individual_forecasts: dict mapping method name to pd.Series forecast.rmse_per_model: dict mapping method name to its hold-out RMSE.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(200)) + 100) >>> result = ensemble_forecast(data, h=10) >>> abs(sum(result['weights'].values()) - 1.0) < 1e-6 True
- rolling_forecast(data, forecast_fn, h=1, initial_window=None, step=1)[source]¶
Walk-forward (expanding window) out-of-sample forecasting.
At each step the model is re-fitted on all data up to time t, then forecasts h steps ahead. The result is a time series of true out-of-sample predictions that can be compared against actuals.
This is the gold standard for evaluating forecasting models because it mimics how the model would be used in production.
- Parameters:
data (
Series) – Full time series.forecast_fn (
Callable[[Series,int],ndarray|Series]) – Callable(train_series, h) -> array-likethat returns h point forecasts.h (
int, default:1) – Forecast horizon at each step (default 1).initial_window (
int|None, default:None) – Minimum training window size. Defaults tomax(30, len(data) // 3).step (
int, default:1) – Number of observations to advance between re-fits (default 1).
- Returns:
forecasts: pd.Series of out-of-sample predictions.actuals: pd.Series of corresponding actual values.errors: pd.Series of forecast errors.cumulative_metrics: dict with cumulative RMSE, MAE.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> data = pd.Series(np.cumsum(np.random.randn(100)) + 50) >>> def my_model(train, h): ... return np.full(h, train.iloc[-1]) >>> result = rolling_forecast( ... data, my_model, h=1, initial_window=50, ... ) >>> len(result['forecasts']) > 0 True
- garch_residual_forecast(returns, vol_model='GARCH', forecast_method='ses', horizon=10, garch_p=1, garch_q=1)[source]¶
Forecast returns using GARCH-filtered residuals.
This function chains the
volandtsmodules together:Fit a GARCH model (from
wraquant.vol.models) to extract conditional volatility and standardised residuals.Forecast the standardised residuals (which should be approximately iid) using a classical time-series method (SES, Theta, or Holt- Winters from
wraquant.ts.forecasting).Forecast conditional volatility forward using the GARCH model.
Recombine:
return_forecast = residual_forecast * vol_forecast.
The key insight is that raw financial returns are not iid (they exhibit volatility clustering). GARCH filtering removes the heteroskedasticity, producing residuals that classical forecasting methods can handle. The recombination step re-introduces the time-varying volatility into the forecast.
Chains:
vol.garch_fit->ts.ses_forecast(or other) ->vol.garch_forecast-> recombine.- Parameters:
returns (
Series) – Return series (daily or intraday).vol_model (
str, default:'GARCH') – GARCH variant to use for filtering. Options:"GARCH","EGARCH","GJR".forecast_method (
str, default:'ses') – Method for forecasting the standardised residuals. Options:"ses"(default),"theta","holt_winters".horizon (
int, default:10) – Number of periods to forecast ahead (default 10).garch_p (
int, default:1) – GARCH lag order (default 1).garch_q (
int, default:1) – ARCH lag order (default 1).
- Returns:
'return_forecast'(np.ndarray) – Forecasted returns (residual_forecast * vol_forecast), shape(horizon,).'vol_forecast'(np.ndarray) – GARCH conditional volatility forecast, shape(horizon,).'residual_forecast'(np.ndarray) – Forecasted standardised residuals, shape(horizon,).'standardized_residuals'(np.ndarray) – In-sample standardised residuals from the GARCH fit.'garch_params'(dict) – Fitted GARCH parameters.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> np.random.seed(42) >>> returns = pd.Series(np.random.randn(500) * 0.01) >>> result = garch_residual_forecast(returns, horizon=5) >>> len(result['return_forecast']) 5 >>> len(result['vol_forecast']) 5
Notes
This approach is related to the “GARCH-in-mean” literature but separates the volatility and level dynamics for modularity.
See also
wraquant.vol.models.garch_fit: GARCH model fitting. wraquant.vol.models.garch_forecast: GARCH volatility forecasting. ses_forecast: SES for residual forecasting. theta_forecast: Theta method for residual forecasting.
Changepoint Detection¶
Change-point detection for time series.
- cusum(data, threshold=1.0)[source]¶
Cumulative sum (CUSUM) control chart for change detection.
Returns a CUSUM statistic series. Values exceeding threshold standard deviations indicate a potential shift.
- detect_changepoints(data, method='pelt', penalty=None)[source]¶
Detect change-points using the
ruptureslibrary.- Parameters:
- Return type:
- Returns:
List of change-point indices (positions in the array, excluding the final point).
- Raises:
ValueError – If method is not recognized.
Stationarity Tests¶
Stationarity tests and transformations for time series.
Provides both transformations (differencing, detrending) and formal statistical tests (ADF, KPSS, Phillips-Perron) for assessing and achieving stationarity in time series data.
- fractional_difference(data, d=0.5, threshold=1e-05)[source]¶
Apply fractional differencing to preserve long-memory information.
Implements the fixed-width window fracdiff method from Advances in Financial Machine Learning (Lopez de Prado).
- detrend(data, method='linear')[source]¶
Remove trend from a time series.
- Parameters:
- Return type:
- Returns:
Detrended series.
- Raises:
ValueError – If method is not recognized.
- adf_test(data, max_lags=None, regression='c', significance=0.05)[source]¶
Augmented Dickey-Fuller (ADF) unit root test.
Tests the null hypothesis that a unit root is present (i.e., the series is non-stationary). A low p-value leads to rejection of the null, concluding stationarity.
- The test regression is:
Delta y_t = alpha + beta*t + gamma*y_{t-1} + sum_i delta_i * Delta y_{t-i} + e_t
where alpha is a constant (if
regression='c'), beta*t is a time trend (ifregression='ct'), and the number of lagged differences is chosen to remove serial correlation in the residuals.- ADF vs KPSS:
ADF: null = unit root (non-stationary). Rejecting H0 supports stationarity.
KPSS: null = stationary. Rejecting H0 supports non-stationarity.
Best practice: run both. If ADF rejects and KPSS does not reject, strong evidence of stationarity. If both reject or both fail to reject, the series may be trend-stationary or difference-stationary.
- Parameters:
data (
Series) – Time series to test. NaN values are dropped.max_lags (
int|None, default:None) – Maximum number of lags for the lagged differences. IfNone, the optimal lag is selected automatically using the AIC criterion.regression (
str, default:'c') – Deterministic terms to include:"c"– constant only (default, most common)."ct"– constant and linear trend."n"– no constant, no trend.significance (
float, default:0.05) – Significance level for theis_stationaryconvenience flag (default 0.05).
- Returns:
test_statistic: float, the ADF t-statistic.p_value: float, MacKinnon approximate p-value.optimal_lag: int, number of lags used.n_obs: int, number of observations used in the regression.critical_values: dict mapping significance levels ("1%","5%","10%") to critical values.is_stationary: bool, True if p-value < significance.interpretation: str, human-readable conclusion.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> # White noise is stationary >>> stationary = pd.Series(rng.normal(0, 1, 500)) >>> result = adf_test(stationary) >>> result['is_stationary'] True
References
Dickey, D.A. & Fuller, W.A. (1979), “Distribution of the Estimators for Autoregressive Time Series With a Unit Root”, JASA.
Said, S.E. & Dickey, D.A. (1984), “Testing for Unit Roots in Autoregressive-Moving Average Models of Unknown Order”, Biometrika.
- kpss_test(data, regression='c', n_lags='auto', significance=0.05)[source]¶
Kwiatkowski-Phillips-Schmidt-Shin (KPSS) stationarity test.
Tests the null hypothesis that the series is stationary around a deterministic trend. This is the opposite of the ADF test: here, rejecting H0 implies non-stationarity.
- When to use ADF vs KPSS vs both:
ADF alone: quick check; but has low power against near-unit-root alternatives.
KPSS alone: confirms stationarity; but may over-reject for long-memory processes.
Both (recommended): a confirmatory strategy. Four cases: 1. ADF rejects, KPSS does not reject -> stationary. 2. ADF does not reject, KPSS rejects -> non-stationary. 3. Both reject -> trend-stationary (difference to remove trend). 4. Neither rejects -> inconclusive, may need more data.
- Parameters:
data (
Series) – Time series to test. NaN values are dropped.regression (
str, default:'c') – Deterministic component:"c"– test for level stationarity (default)."ct"– test for trend stationarity.n_lags (
int|str, default:'auto') – Number of lags for the Newey-West estimator."auto"(default) uses the data-dependent rule. An integer value fixes the lag truncation.significance (
float, default:0.05) – Significance level for theis_stationaryconvenience flag (default 0.05).
- Returns:
test_statistic: float, the KPSS statistic.p_value: float, interpolated p-value (may be bounded at 0.01 or 0.10 depending on the table).n_lags: int, number of lags used.critical_values: dict mapping significance levels to critical values.is_stationary: bool, True if p-value >= significance (i.e., cannot reject the null of stationarity).interpretation: str, human-readable conclusion.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> stationary = pd.Series(rng.normal(0, 1, 500)) >>> result = kpss_test(stationary) >>> result['is_stationary'] True
References
Kwiatkowski, D. et al. (1992), “Testing the null hypothesis of stationarity against the alternative of a unit root”, Journal of Econometrics.
- phillips_perron(data, regression='c', significance=0.05)[source]¶
Phillips-Perron (PP) unit root test.
Like the ADF test, the PP test has the null hypothesis of a unit root (non-stationarity). However, the PP test uses a non-parametric correction to the t-statistic that is robust to heteroskedasticity and serial correlation in the error term, without needing to specify a lag order.
- When to prefer PP over ADF:
When the error process exhibits heteroskedasticity (e.g., financial returns with volatility clustering).
When you are uncertain about the appropriate lag length for ADF.
As a robustness check alongside ADF.
The PP test statistic modifies the ADF statistic with a correction factor based on the Newey-West long-run variance estimate.
- Parameters:
data (
Series) – Time series to test. NaN values are dropped.regression (
str, default:'c') – Deterministic terms:"c"– constant only (default)."ct"– constant and trend."n"– no constant, no trend.significance (
float, default:0.05) – Significance level for theis_stationaryconvenience flag (default 0.05).
- Returns:
test_statistic: float, the PP t-statistic.p_value: float, MacKinnon approximate p-value.n_lags: int, truncation lag for Newey-West.critical_values: dict mapping significance levels to critical values.is_stationary: bool, True if p-value < significance.interpretation: str, human-readable conclusion.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> stationary = pd.Series(rng.normal(0, 1, 500)) >>> result = phillips_perron(stationary) >>> result['is_stationary'] True
References
Phillips, P.C.B. & Perron, P. (1988), “Testing for a Unit Root in Time Series Regression”, Biometrika.
- optimal_differencing(data, max_d=2, significance=0.05)[source]¶
Automatically determine the optimal differencing order for stationarity.
Sequentially applies integer differencing (d = 0, 1, 2, …) and runs the ADF test at each order. Returns the smallest d for which the ADF test rejects the unit root null.
- Parameters:
- Returns:
optimal_d: int, the smallest differencing order that achieves stationarity (ormax_dif stationarity is not achieved).test_results: dict mapping each d to itsadf_testresult dictionary.is_stationary: bool, True if stationarity was achieved withinmax_ddifferences.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> # Random walk needs d=1 >>> rw = pd.Series(np.cumsum(rng.normal(0, 1, 500))) >>> result = optimal_differencing(rw) >>> result['optimal_d'] 1
References
Hyndman, R.J. & Khandakar, Y. (2008), “Automatic Time Series Forecasting: The forecast Package for R”, JSS.
- variance_ratio_test(data, lags=2, overlap=True, significance=0.05)[source]¶
Lo-MacKinlay variance ratio test for the random walk hypothesis.
Tests whether the variance of k-period returns scales linearly with k, as implied by a random walk. A variance ratio VR(k) = 1 is consistent with a random walk; VR(k) > 1 suggests positive autocorrelation (momentum); VR(k) < 1 suggests mean reversion.
- The test statistic under homoskedasticity is:
VR(k) = Var(r_t(k)) / (k * Var(r_t))z = (VR(k) - 1) / sqrt(2*(2k-1)*(k-1) / (3*k*T))
A heteroskedasticity-robust version is also computed.
- Parameters:
data (
Series) – Time series of prices or levels (NOT returns). NaN values are dropped.lags (
int, default:2) – Holding period / aggregation interval k (default 2). Common choices: 2, 4, 8, 16.overlap (
bool, default:True) – Use overlapping returns for better power (default True). Non-overlapping uses fewer observations.significance (
float, default:0.05) – Significance level for theis_random_walkconvenience flag (default 0.05).
- Returns:
variance_ratio: float, the VR(k) statistic.z_statistic: float, the z-statistic (homoskedastic).z_robust: float, heteroskedasticity-robust z-statistic.p_value: float, two-sided p-value from the robust z-statistic.is_random_walk: bool, True if the random walk null cannot be rejected at the given significance level.interpretation: str, human-readable conclusion.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> # Random walk: VR should be close to 1 >>> rw = pd.Series(np.cumsum(rng.normal(0, 1, 1000))) >>> result = variance_ratio_test(rw, lags=2) >>> 0.5 < result['variance_ratio'] < 1.5 True
References
Lo, A.W. & MacKinlay, A.C. (1988), “Stock Market Prices Do Not Follow Random Walks: Evidence from a Simple Specification Test”, Review of Financial Studies.
Seasonality¶
Seasonality detection and feature engineering.
- detect_seasonality(data, max_period=365)[source]¶
Detect dominant seasonal periods via spectral analysis.
Uses Welch’s periodogram to identify significant frequency peaks.
- fourier_features(index, period, n_harmonics)[source]¶
Generate Fourier sine/cosine features for a datetime index.
Useful for encoding seasonality as regression features.
- Parameters:
index (
DatetimeIndex) – Datetime index.period (
int) – Seasonal period (in the same time unit as the index).n_harmonics (
int) – Number of Fourier harmonics to generate.
- Return type:
- Returns:
DataFrame with
sin_kandcos_kcolumns for each harmonic.
- multi_fourier_features(index, periods, n_harmonics=3)[source]¶
Generate Fourier terms for multiple seasonal periods.
Creates sin/cos pairs for each period-harmonic combination, producing a feature matrix suitable for regression-based seasonal modelling (e.g., linear regression, Prophet-style models, or as exogenous regressors for ARIMA).
- For a period P and harmonic k, the features are:
sin(2 * pi * k * t / P)andcos(2 * pi * k * t / P)
This captures up to k-th order seasonal variation within each period.
- Parameters:
index (
DatetimeIndex) – Datetime index of the time series.periods (
list[int]) – List of seasonal periods (in the same units as the index). For example,[7, 365]for daily data with weekly and yearly seasonality.n_harmonics (
int|list[int], default:3) – Number of harmonics per period. Can be a single int (applied to all periods) or a list of ints (one per period). Default 3.
- Return type:
- Returns:
DataFrame with columns named
sin_P{period}_H{harmonic}andcos_P{period}_H{harmonic}for each period-harmonic pair. Shape is(len(index), 2 * sum(harmonics)).
Example
>>> import pandas as pd >>> idx = pd.date_range("2020-01-01", periods=365, freq="D") >>> df = multi_fourier_features(idx, periods=[7, 365], n_harmonics=3) >>> df.shape[1] 12
- seasonal_strength(data, period=None)[source]¶
Quantify the strength of seasonality in a time series.
- Uses the Wang, Smith, and Hyndman (2006) measure:
F_s = max(0, 1 - Var(R_t) / Var(S_t + R_t))
where S_t is the seasonal component and R_t is the remainder from an STL decomposition.
- Returns a float in [0, 1]:
0.0: no detectable seasonality (remainder dominates).
1.0: perfectly seasonal (no noise).
> 0.9 is typically considered “strong” seasonality.
< 0.4 is typically “weak”.
- Parameters:
- Return type:
- Returns:
Strength of seasonality as a float in [0, 1].
Example
>>> import numpy as np, pandas as pd >>> t = np.arange(200, dtype=float) >>> pure_seasonal = pd.Series(10 * np.sin(2 * np.pi * t / 20)) >>> strength = seasonal_strength(pure_seasonal, period=20) >>> strength > 0.9 True
References
Wang, X., Smith, K. & Hyndman, R. (2006), “Characteristic- Based Clustering for Time Series Data”, Data Mining and Knowledge Discovery.
- multi_seasonal_decompose(data, periods)[source]¶
Decompose a time series with multiple seasonal periods.
Handles complex seasonality (e.g., daily data with both weekly and yearly patterns) by using MSTL (Multiple Seasonal-Trend decomposition using Loess) from statsmodels when available, or an iterative STL approach as fallback.
- Parameters:
- Returns:
trend: pd.Series of the trend component.seasonal: dict mapping each period to its pd.Series seasonal component.residual: pd.Series of the residual after removing trend and all seasonal components.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> t = np.arange(730, dtype=float) >>> weekly = 3 * np.sin(2 * np.pi * t / 7) >>> yearly = 5 * np.sin(2 * np.pi * t / 365) >>> trend = 0.01 * t >>> data = pd.Series(trend + weekly + yearly) >>> result = multi_seasonal_decompose(data, periods=[7, 365]) >>> sorted(result['seasonal'].keys()) [7, 365]
References
Bandara, K. et al. (2021), “MSTL: A Seasonal-Trend Decomposition Algorithm for Time Series with Multiple Seasonal Patterns”, arXiv:2107.13462.
Feature Extraction¶
Time series feature extraction.
Provides functions to compute summary features from time series data for use in classification, clustering, or exploratory analysis. Features fall into three categories:
Autocorrelation features: ACF, PACF, Ljung-Box test for serial dependence structure.
Spectral features: power spectral density, dominant frequency, spectral entropy, spectral flatness.
Complexity features: sample entropy, approximate entropy, permutation entropy for measuring signal regularity/predictability.
- autocorrelation_features(data, n_lags=40, significance=0.05)[source]¶
Compute autocorrelation and partial autocorrelation features.
Extracts the ACF and PACF values up to
n_lags, identifies statistically significant lags, and runs the Ljung-Box test for overall serial correlation.- Interpretation:
ACF measures linear dependence between y_t and y_{t-k}. For AR(p) processes, ACF decays exponentially. For MA(q), ACF cuts off after lag q.
PACF measures the correlation between y_t and y_{t-k} after removing the effect of intermediate lags. For AR(p), PACF cuts off after lag p.
Ljung-Box: tests H0 that the first m autocorrelations are jointly zero. A significant result indicates the series has exploitable temporal structure.
- Parameters:
- Returns:
acf: 1-D numpy array of ACF values (lags 0 to n_lags).pacf: 1-D numpy array of PACF values (lags 0 to n_lags).significant_acf_lags: list of lag indices where ACF exceeds the Bartlett confidence band.significant_pacf_lags: list of lag indices where PACF exceeds the confidence band.ljung_box: dict withstatistic,p_value, andis_significantfor the Ljung-Box test at the maximum lag.first_significant_lag: int or None, the first lag with significant ACF (excluding lag 0).
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> # AR(1) process >>> x = np.zeros(500) >>> for i in range(1, 500): ... x[i] = 0.7 * x[i-1] + rng.normal() >>> result = autocorrelation_features(pd.Series(x), n_lags=20) >>> result['acf'][1] > 0.5 # strong lag-1 autocorrelation True
- spectral_features(data, fs=1.0)[source]¶
Compute power spectral density features.
Extracts summary statistics from the periodogram including the dominant frequency, spectral entropy (a measure of spectral complexity), and spectral flatness (how noise-like the signal is).
- Parameters:
- Returns:
dominant_frequency: float, the frequency with the highest spectral power (excluding DC component).dominant_period: float, the period corresponding to the dominant frequency (1 / dominant_frequency).spectral_entropy: float, Shannon entropy of the normalised power spectral density. Higher values indicate a more complex, broadband signal; lower values indicate a few dominant frequencies. Range: [0, log2(N/2)].spectral_flatness: float in [0, 1]. Ratio of geometric mean to arithmetic mean of the PSD. A value near 1 indicates white noise; near 0 indicates a tonal/periodic signal. Also known as Wiener entropy.spectral_centroid: float, the weighted mean frequency (centre of mass of the spectrum).psd: 1-D numpy array of power spectral density values.frequencies: 1-D numpy array of corresponding frequencies.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> t = np.arange(500, dtype=float) >>> pure_tone = pd.Series(np.sin(2 * np.pi * t / 20)) # period=20 >>> result = spectral_features(pure_tone) >>> abs(result['dominant_period'] - 20) < 2 True >>> result['spectral_flatness'] < 0.3 # tonal signal True
- complexity_features(data, m=2, r=None, order=3)[source]¶
Compute entropy-based complexity measures for a time series.
Extracts three entropy measures that quantify the regularity and predictability of the signal:
Sample entropy (SampEn): probability that patterns similar for m points remain similar for m+1 points. Lower values indicate more regularity (self-similarity); higher values indicate more complexity. Defined as -ln(A/B) where A is the count of m+1 length template matches and B is the count of m length matches, within tolerance r.
Approximate entropy (ApEn): similar to SampEn but includes self-matches, making it slightly biased toward regularity. Historically introduced first (Pincus 1991).
Permutation entropy (PeEn): based on the frequency of ordinal patterns of length
order. Robust to noise and monotonic transformations. Normalised to [0, 1].
- Parameters:
data (
Series) – Time series. NaN values are dropped.m (
int, default:2) – Embedding dimension for SampEn and ApEn (default 2). Typical values: 2 or 3.r (
float|None, default:None) – Tolerance threshold for SampEn and ApEn. IfNone, defaults to0.2 * std(data)(standard choice).order (
int, default:3) – Ordinal pattern length for permutation entropy (default 3). Typical values: 3-7.
- Returns:
sample_entropy: float, SampEn(m, r). Values typically range from 0 (perfectly regular) to ~2.5 (random).approximate_entropy: float, ApEn(m, r).permutation_entropy: float, normalised PeEn in [0, 1]. 0 = perfectly predictable, 1 = maximally complex/random.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> regular = pd.Series(np.sin(np.arange(500) * 0.1)) >>> random = pd.Series(rng.normal(0, 1, 500)) >>> reg_feat = complexity_features(regular) >>> rnd_feat = complexity_features(random) >>> reg_feat['sample_entropy'] < rnd_feat['sample_entropy'] True
References
Richman, J.S. & Moorman, J.R. (2000), “Physiological time-series analysis using approximate entropy and sample entropy”, AJP.
Pincus, S.M. (1991), “Approximate entropy as a measure of system complexity”, PNAS.
Bandt, C. & Pompe, B. (2002), “Permutation Entropy: A Natural Complexity Measure for Time Series”, PRL.
Anomaly Detection¶
Time series anomaly detection.
Provides methods for detecting anomalous observations in time series data using statistical and machine learning approaches:
Isolation Forest on rolling features – detects contextual anomalies by featurising the local window (mean, std, skew) and running an isolation forest on the feature space.
Forecast-based anomaly detection – fits a time series model and flags observations that deviate from the forecast by more than k standard deviations. Does NOT require fbprophet.
Rolling Grubbs test – applies the classical Grubbs outlier test in a rolling window to detect local outliers.
- isolation_forest_ts(data, window=20, contamination=0.05, features=None, random_state=42)[source]¶
Anomaly detection using Isolation Forest on rolling features.
Rather than applying Isolation Forest directly to the raw values, this function first computes rolling window features (mean, std, skew, min, max) to capture the context of each observation. This approach detects contextual anomalies – values that are unusual given their local context, even if they are not globally extreme.
- Parameters:
data (
Series) – Time series. NaN values are dropped.window (
int, default:20) – Rolling window size for feature computation (default 20).contamination (
float, default:0.05) – Expected proportion of anomalies in the data (default 0.05). Controls the threshold for the Isolation Forest decision function.features (
list[str] |None, default:None) – List of rolling features to compute. IfNone, uses["mean", "std", "skew", "min", "max"]. Supported values:"mean","std","skew","min","max","median","range".random_state (
int, default:42) – Random seed for reproducibility (default 42).
- Returns:
anomaly_scores: pd.Series of anomaly scores from the Isolation Forest decision function (lower = more anomalous).anomaly_labels: pd.Series of int labels (1 = normal, -1 = anomaly).threshold: float, the decision function threshold.anomaly_indices: list of index values flagged as anomalies.n_anomalies: int, number of detected anomalies.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> x = rng.normal(0, 1, 500) >>> x[100] = 20.0 # inject anomaly >>> x[300] = -15.0 # inject anomaly >>> data = pd.Series(x) >>> result = isolation_forest_ts(data, window=20) >>> result['n_anomalies'] > 0 True
References
Liu, F.T. et al. (2008), “Isolation Forest”, ICDM.
- prophet_anomaly(data, k=3.0, seasonal_period=None, trend=True)[source]¶
Forecast-based anomaly detection using statsmodels.
Fits an Unobserved Components Model (local level + optional seasonal) and flags observations where the actual value deviates from the smoothed state by more than
kstandard deviations of the residuals.This approach does NOT require fbprophet or Prophet. It uses pure statsmodels for a fully open-source, lightweight solution.
- Parameters:
data (
Series) – Time series. NaN values are dropped.k (
float, default:3.0) – Number of standard deviations for the anomaly threshold (default 3.0). Lower values flag more anomalies.seasonal_period (
int|None, default:None) – Seasonal period for the model. IfNone, no seasonal component is included.trend (
bool, default:True) – Include a trend component (default True).
- Returns:
forecast: pd.Series of smoothed / fitted values.residuals: pd.Series of (actual - forecast).anomaly_mask: pd.Series of bool, True for anomalies.anomaly_indices: list of index values flagged as anomalies.upper_bound: pd.Series, forecast + k * sigma.lower_bound: pd.Series, forecast - k * sigma.sigma: float, standard deviation of residuals.n_anomalies: int, number of detected anomalies.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> x = rng.normal(0, 1, 300) >>> x[50] = 15.0 # anomaly >>> x[200] = -12.0 # anomaly >>> data = pd.Series(x) >>> result = prophet_anomaly(data, k=3.0) >>> result['n_anomalies'] >= 2 True
References
Harvey, A.C. (1989), Forecasting, Structural Time Series Models and the Kalman Filter. Cambridge University Press.
- grubbs_test_ts(data, window=50, significance=0.05)[source]¶
Rolling Grubbs test for outlier detection in time series.
Applies the Grubbs test (maximum normed residual test) within a rolling window to detect observations that are statistically unlikely given the local distribution.
- The Grubbs test statistic for a sample of size n is:
G = max|x_i - mean| / std- and the critical value is derived from the t-distribution:
G_crit = ((n-1) / sqrt(n)) * sqrt(t^2 / (n - 2 + t^2))
where
tis the critical value of the t-distribution withn-2degrees of freedom at significancealpha / (2n).- Parameters:
- Returns:
outlier_mask: pd.Series of bool, True for detected outliers.test_statistics: pd.Series of Grubbs G statistics at each position.outlier_indices: list of index values flagged as outliers.n_outliers: int, number of detected outliers.
- Return type:
Example
>>> import numpy as np, pandas as pd >>> rng = np.random.default_rng(42) >>> x = rng.normal(0, 1, 200) >>> x[75] = 20.0 # obvious outlier >>> data = pd.Series(x) >>> result = grubbs_test_ts(data, window=50) >>> 75 in result['outlier_indices'] True
References
Grubbs, F.E. (1950), “Sample Criteria for Testing Outlying Observations”, Annals of Mathematical Statistics.
Stochastic Processes¶
Stochastic process forecasting for financial time series.
Provides parameter estimation and forecasting for continuous-time stochastic processes commonly used in quantitative finance:
Ornstein-Uhlenbeck (OU): mean-reverting diffusion for spreads, rates, and volatility.
Merton Jump-Diffusion: GBM with Poisson jumps for fat-tailed asset returns.
Regime-Switching: separate models per hidden regime, blended by regime probabilities.
Vector Autoregression (VAR): multi-asset linear forecasting with impulse response and variance decomposition.
- ornstein_uhlenbeck_forecast(data, h=10, dt=None, n_simulations=1000, confidence_level=0.95)[source]¶
Forecast using an Ornstein-Uhlenbeck (OU) mean-reverting process.
The OU process is the continuous-time analogue of an AR(1) process and is the workhorse model for mean-reverting financial quantities.
Use for mean-reverting assets like spreads, interest rates, volatility (VIX), pairs-trading residuals, or any series that fluctuates around a long-run equilibrium.
- SDE:
dX_t = theta * (mu - X_t) * dt + sigma * dW_t- where:
theta> 0: speed of mean reversion (higher = faster).mu: long-run mean level.sigma: volatility of the diffusion.W_t: standard Brownian motion.
The half-life of mean reversion is
ln(2) / theta, giving the expected time for a deviation to halve.- Parameter estimation:
Parameters are estimated via Maximum Likelihood on the discrete-time transition density, which is Gaussian:
X_{t+dt} | X_t ~ N(mu + (X_t - mu) * exp(-theta*dt), sigma^2 / (2*theta) * (1 - exp(-2*theta*dt)))
- Parameters:
data (
Series) – Time series of observed values.h (
int, default:10) – Number of steps to forecast (default 10).dt (
float|None, default:None) – Time step between observations. IfNone, inferred from the index (1/252 for business days, 1.0 otherwise).n_simulations (
int, default:1000) – Number of Monte Carlo paths for confidence bands (default 1000).confidence_level (
float, default:0.95) – Confidence level for intervals (default 0.95).
- Returns:
params: dict withtheta,mu,sigma.half_life: half-life of mean reversion (in observation units).forecast: pd.Series of expected path (conditional mean).confidence_intervals: dict withlowerandupperpd.Series.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> rng = np.random.default_rng(42) >>> # Simulate OU process >>> n, theta, mu, sigma = 500, 5.0, 100.0, 2.0 >>> x = np.zeros(n); x[0] = 100.0 >>> dt = 1 / 252 >>> for i in range(1, n): ... x[i] = x[i-1] + theta * (mu - x[i-1]) * dt + sigma * np.sqrt(dt) * rng.normal() >>> data = pd.Series(x) >>> result = ornstein_uhlenbeck_forecast(data, h=20) >>> result['half_life'] > 0 True
References
Uhlenbeck, G.E. & Ornstein, L.S. (1930). On the theory of Brownian motion. Physical Review, 36(5), 823.
- jump_diffusion_forecast(data, h=10, dt=None, n_paths=1000, confidence_level=0.95, seed=None)[source]¶
Merton jump-diffusion model forecast via Monte Carlo simulation.
Extends geometric Brownian motion (GBM) by adding Poisson-driven jumps, capturing the fat tails and sudden moves observed in real asset returns.
- SDE:
dS/S = (mu - lambda*k) * dt + sigma * dW + J * dN- where:
mu: drift of the diffusion component.sigma: diffusion volatility.lambda: jump intensity (expected jumps per unit time).J: jump size,ln(1+J) ~ N(mu_j, sigma_j^2).N_t: Poisson process with intensitylambda.k = exp(mu_j + sigma_j^2/2) - 1.
- Parameter estimation:
A moment-matching approach is used on log-returns: - Jump intensity estimated from excess kurtosis. - Jump mean and variance from the non-Gaussian tail structure.
- Parameters:
data (
Series) – Price series (positive values).h (
int, default:10) – Forecast horizon in steps (default 10).dt (
float|None, default:None) – Time step between observations (default 1/252 for daily).n_paths (
int, default:1000) – Number of Monte Carlo simulation paths (default 1000).confidence_level (
float, default:0.95) – Confidence level for intervals (default 0.95).seed (
int|None, default:None) – Random seed for reproducibility.
- Returns:
params: dict withmu,sigma,lambda_,mu_j,sigma_j.forecast_paths: np.ndarray of shape(n_paths, h)with simulated price paths.mean_forecast: pd.Series of mean across paths.confidence_intervals: dict withlowerandupperpd.Series.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> prices = pd.Series(100 * np.exp(np.cumsum(np.random.randn(200) * 0.01))) >>> result = jump_diffusion_forecast(prices, h=10, n_paths=500, seed=42) >>> result['forecast_paths'].shape (500, 10)
References
Merton, R.C. (1976). Option pricing when underlying stock returns are discontinuous. Journal of Financial Economics, 3(1-2), 125-144.
- regime_switching_forecast(data, n_regimes=2, h=10)[source]¶
Forecast using a Markov regime-switching model.
Fits a Markov-switching autoregression where the series parameters (mean, variance) switch between discrete hidden regimes. Forecasts are blended across regimes weighted by the predicted regime probabilities.
- When to use:
Series that alternate between distinct states (e.g., bull/bear markets, high/low volatility regimes).
When a single-model forecast is inadequate because the data generation process changes over time.
- Parameters:
- Returns:
forecast: pd.Series of blended point forecasts.regime_forecasts: dict mapping regime index to its pd.Series forecast.regime_probs: np.ndarray of shape(h, n_regimes)with predicted regime probabilities.blended: same asforecast(for clarity).
- Return type:
Example
>>> import pandas as pd, numpy as np >>> rng = np.random.default_rng(42) >>> # Bull/bear regime data >>> regimes = np.concatenate([rng.normal(0.1, 0.5, 150), ... rng.normal(-0.05, 1.0, 150)]) >>> data = pd.Series(np.cumsum(regimes)) >>> result = regime_switching_forecast(data, n_regimes=2, h=10) >>> result['regime_probs'].shape[1] 2
References
Hamilton, J.D. (1989). A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle. Econometrica, 57(2), 357-384.
- var_forecast(data, h=10, maxlags=None, ic='aic')[source]¶
Vector Autoregression (VAR) for multi-asset forecasting.
VAR models each variable as a linear function of its own lags and the lags of all other variables. This captures lead-lag relationships (e.g., one asset predicting another) and provides impulse response functions (IRF) and forecast error variance decomposition (FEVD).
- When to use:
Forecasting multiple related time series simultaneously (e.g., a portfolio of assets, yield curve factors).
Analysing dynamic interactions: which variables Granger-cause which.
Computing impulse responses: how a shock to one variable propagates to others.
- Math:
Y_t = c + A_1 * Y_{t-1} + ... + A_p * Y_{t-p} + e_twhere
Y_tis a (k x 1) vector,A_iare (k x k) coefficient matrices, ande_t ~ N(0, Sigma).
- Parameters:
data (
DataFrame) – DataFrame with multiple columns (one per variable). Should be stationary (difference if needed).h (
int, default:10) – Forecast horizon (default 10).maxlags (
int|None, default:None) – Maximum lag order to consider. IfNone, uses12 * (nobs/100)^(1/4)rule of thumb.ic (
str, default:'aic') – Information criterion for lag selection:"aic","bic","hqic","fpe"(default"aic").
- Returns:
forecast: pd.DataFrame of point forecasts.irf: impulse response function results (dict of DataFrames keyed by shocked variable).fevd: forecast error variance decomposition (dict of DataFrames keyed by target variable).granger_causality: dict of p-values for Granger causality tests.lag_order: selected lag order.
- Return type:
Example
>>> import pandas as pd, numpy as np >>> rng = np.random.default_rng(42) >>> n = 200 >>> x1 = np.cumsum(rng.normal(0, 1, n)) >>> x2 = 0.3 * np.roll(x1, 1) + rng.normal(0, 1, n) >>> data = pd.DataFrame({'x1': np.diff(x1), 'x2': np.diff(x2)}) >>> result = var_forecast(data, h=5) >>> result['forecast'].shape[0] 5
References
Lutkepohl, H. (2005). New Introduction to Multiple Time Series Analysis. Springer.
Advanced Methods¶
Advanced time series integrations using optional packages.
Provides wrappers around tsfresh, stumpy, pywavelets, sktime, statsforecast, and tslearn for feature extraction, matrix profiles, wavelet analysis, forecasting, and time series clustering.
- tsfresh_features(df, column_id='id', column_sort='time')[source]¶
Extract time series features using tsfresh.
Wraps
tsfresh.extract_featureswith efficient defaults and returns a cleaned DataFrame with no NaN/infinite columns.- Parameters:
df (
DataFrame) – Long-format DataFrame containing time series data. Must include the columns specified by column_id and column_sort, plus one or more value columns.column_id (
str, default:'id') – Column identifying distinct time series.column_sort (
str, default:'time') – Column used for temporal ordering.
- Returns:
Extracted features indexed by column_id values. Columns with NaN or infinite values are dropped.
- Return type:
- stumpy_matrix_profile(ts, m=50)[source]¶
Compute the matrix profile of a time series using STUMPY.
The matrix profile identifies the nearest-neighbour distance for every subsequence of length m, enabling motif discovery and discord (anomaly) detection.
- Parameters:
- Returns:
Dictionary containing:
matrix_profile – 1-D array of nearest-neighbour distances.
profile_index – 1-D array of indices of the nearest neighbour.
motif_idx – index of the top motif (lowest MP value).
discord_idx – index of the top discord (highest MP value).
- Return type:
- wavelet_transform(data, wavelet='db4', level=None)[source]¶
Perform discrete wavelet transform (DWT) using PyWavelets.
- Parameters:
- Returns:
Dictionary containing:
coeffs – list of coefficient arrays
[cA_n, cD_n, ..., cD_1].wavelet – wavelet name used.
level – decomposition level used.
- Return type:
- wavelet_denoise(data, wavelet='db4', level=None, threshold='soft')[source]¶
Denoise a signal using wavelet thresholding.
Applies universal (VisuShrink) thresholding to the detail coefficients after a DWT decomposition.
- Parameters:
- Returns:
Denoised signal with the same length as the input.
- Return type:
- sktime_forecast(y, model='naive', horizon=10)[source]¶
Forecast a time series using sktime’s unified interface.
- Parameters:
y (
Series) – Historical observations indexed by a pandas PeriodIndex or integer index.model (
str, default:'naive') –Forecasting model. Supported values:
'naive'– last-value forecast'theta'– Theta method'ets'– exponential smoothing (AutoETS)
horizon (
int, default:10) – Number of steps ahead to forecast.
- Returns:
DataFrame with columns
forecastandindexcovering the forecast horizon.- Return type:
- statsforecast_auto(y, season_length=1, horizon=10)[source]¶
Automatic forecasting using Nixtla’s StatsForecast.
Runs AutoARIMA and returns point forecasts.
- Parameters:
- Returns:
DataFrame with column
forecastindexed over the forecast horizon.- Return type:
- tslearn_kmeans(dataset, n_clusters=3)[source]¶
Cluster time series using DTW-based K-means from tslearn.
- Parameters:
- Returns:
Dictionary containing:
labels – cluster assignments (1-D int array).
inertia – sum of distances to nearest cluster centre.
cluster_centers – array of cluster centre time series.
n_clusters – number of clusters used.
- Return type:
- darts_forecast(data, model='nbeats', horizon=10, **kwargs)[source]¶
Forecast using Darts deep learning models.
Darts provides production-quality implementations of N-BEATS, N-HiTS, Temporal Fusion Transformer, and other deep forecasting models. Use when you need state-of-the-art accuracy and have sufficient data (>1000 observations).
The model is trained on the provided data and produces a forecast of the specified horizon. Default training parameters are tuned for quick experimentation; pass additional keyword arguments to override them (e.g.,
n_epochs=200).- Parameters:
data (
ndarray|Series) – Price or return series. Must be univariate.model (
str, default:'nbeats') –Deep learning model to use:
'nbeats'– N-BEATS (Neural Basis Expansion Analysis).'nhits'– N-HiTS (Neural Hierarchical Interpolation).'tcn'– Temporal Convolutional Network.'rnn'– Simple RNN (LSTM-based).'transformer'– Vanilla Transformer.
horizon (
int, default:10) – Number of steps ahead to forecast.**kwargs (
Any) – Additional keyword arguments passed to the model constructor (e.g.,n_epochs,input_chunk_length).
- Returns:
Dictionary containing:
forecast – 1-D numpy array of forecasted values.
model_name – name of the model used.
training_loss – final training loss (when available).
- Return type:
Example
>>> import numpy as np >>> from wraquant.ts.advanced import darts_forecast >>> data = np.cumsum(np.random.default_rng(0).normal(0, 1, 500)) >>> result = darts_forecast(data, model="nbeats", horizon=5, n_epochs=5) >>> len(result["forecast"]) 5
Notes
Reference: Oreshkin et al. (2020). “N-BEATS: Neural basis expansion analysis for interpretable time series forecasting.” ICLR 2020.
See also
sktime_forecastSimpler statistical forecasting models.
statsforecast_autoAutoARIMA and friends.
auto_forecastwraquant’s built-in automatic forecasting.