Skip to content

Models

All built-in models live under rt.models and inherit from ForecastModel. Lags are supplied at fit/forecast time (y_lags/X_lags), not in the constructor.

Optional models are imported when their public attribute is first accessed. Install the extra for the model you use, for example pip install "forecast-realtime[ridge]".

Model Description
ForecastOLS, ForecastRidge, ForecastLasso, ForecastElasticNet Linear models (shared LinearRegression base; support scale, recursive/direct strategy, formula)
RandomForest, XGBoost Tree models (shared TreeRegression base; support standardise, formula, recursive/direct strategy)
ForecastBVAR Bayesian VAR with conditional forecasting
ForecastMIDAS, ForecastMultiMIDAS, ForecastMIDASCombo Mixed-frequency (MIDAS) models
ForecastRlm Wrapper for an R-based lm() model
import forecast_evaluation as fe
import forecast_realtime as rt

sample_data = rt.generate_synthetic_data(
    N=2,
    first_period="2015-01-31",
    endpoint="2024-12-31",
)
forecast_data = fe.NowcastData(outturns_data=sample_data)
ols = rt.models.ForecastOLS(
    label="ols",
    formula="quarterly_1 ~ quarterly_2",
)
ridge = rt.models.ForecastRidge(cv=5, scale=True)

# Pass several models at once — each is run across every vintage
rt_model = rt.RealTimeModel(data=forecast_data, models=[ols, ridge])

Example with a built-in model

import forecast_evaluation as fe
import forecast_realtime as rt

sample_data = rt.generate_synthetic_data(
    N=2,
    first_period="2015-01-31",
    endpoint="2024-12-31",
)
forecast_data = fe.NowcastData(outturns_data=sample_data)

ridge_model = rt.models.ForecastRidge(label="Ridge", cv=5, scale=True)
lasso_model = rt.models.ForecastLasso(label="LASSO", cv=5, scale=True)

rt_model = rt.RealTimeModel(data=forecast_data, models=[ridge_model, lasso_model])

rt_model.forecast(
    y_variables=["quarterly_1"],
    X_variables=["quarterly_2"],
    data_transformation={"quarterly_1": "pop", "quarterly_2": "pop"},
    steps=2,
    y_lags=4,
    first_vintage="2024-01-31",
    last_vintage="2024-06-30",
    X_imputation="last",
)

# Optional interactive dashboard:
# rt_model.data.run_dashboard()

Autoregressive penalisation

ForecastRidge, ForecastLasso, and ForecastElasticNet default to penalise_ar=False, so retained target lags generated by y_lags are unpenalised. Set penalise_ar=True to shrink those lags as well. Outlier dummies remain unpenalised in either mode; ForecastOLS does not use this option.

Cross-validation for regularised models

Set cv to an integer or a scikit-learn-compatible splitter. An integer uses unshuffled K-fold cross-validation; a splitter, such as TimeSeriesSplit, is used as supplied. Choose a splitter that matches the time ordering and information available at the forecast origin. Ordinary K-fold CV can be valid for autoregressive prediction when the fitted model captures the dependence and its errors are uncorrelated (Bergmeir, Hyndman and Koo, 2018).

CV supports unpenalised AR terms and dummies. These fits minimise mean validation squared error in the target's units, with FWL projection and scaling fitted on each training fold. Validation scores use held-out design rows, not recursive multi-step forecasts. With penalise_ar=False, AR-only designs use least squares and skip CV; alpha_ (Ridge) or best_alpha (Lasso and ElasticNet) is then None.

When alphas is omitted, Ridge uses 100 log-spaced values from 1e-4 to 1e2 in alpha_scaling units in both CV paths. Explicit alphas override this grid.

Lasso and ElasticNet use 100 data-dependent, log-spaced candidates. With unpenalised terms, CV compares relative strengths from each training fold's grid; best_alpha reports the final full-sample penalty. Supply alphas to compare the same absolute penalties across folds. ElasticNet also accepts a sequence of l1_ratio values; include alphas when using l1_ratio=0.

For a time-ordered validation scheme, pass a scikit-learn-compatible splitter explicitly:

from sklearn.model_selection import TimeSeriesSplit

ridge = rt.models.ForecastRidge(cv=TimeSeriesSplit(n_splits=5), scale=True)

Reference: Bergmeir, C., Hyndman, R. J. and Koo, B. (2018), "A note on the validity of cross-validation for evaluating autoregressive time series prediction", Computational Statistics & Data Analysis, 120, 70-83. https://doi.org/10.1016/j.csda.2017.11.003

Models in other languages

Models written in R, MATLAB or Julia can be wrapped with RModel, MATLABModel and JuliaModel. You provide fit and forecast functions in the target language; the wrapper handles data exchange (Parquet), parameters and the subprocess.

The supplied R, MATLAB or Julia script is executed by the corresponding runtime and must therefore be trusted code. The wrapper protects generated command and path literals, but it does not sandbox the external script.

from pathlib import Path

import forecast_realtime as rt
from forecast_realtime import RModel, MATLABModel, JuliaModel

# Resolve the script relative to this file so it works from any directory
model = RModel(str(Path(__file__).parent / "my_model.R"), p=4, shrinkage=0.5)

ets = rt.models.RFableETS(error="A", trend="A", season="N")
arima = rt.models.RFableARIMA(p=1, d=0, q=0)

See adding_a_model.md for the expected function signatures.

Density forecasts

Built-in density support is available for ForecastOLS and ForecastBVAR. Penalised regressions (ForecastRidge, ForecastLasso, and ForecastElasticNet), ForecastBridgeOLS, RandomForest, and XGBoost do not support quantile forecasts.

ForecastOLS

OLS quantiles are the textbook prediction interval, the one returned by R's predict.lm(interval = "prediction") and statsmodels' get_prediction().summary_frame(). For n retained observations, design rank r, and a forecast row x0 (intercept included):

\[ \hat{y}_0 + s\sqrt{1 + x_0^\top (X^\top X)^{+} x_0}\; t^{-1}_{n - r}(q), \qquad s^2 = \frac{\text{RSS}}{n - r} \]

The interval accounts for residual noise, coefficient uncertainty, and uncertainty in s. The pseudo-inverse keeps rank-deficient designs valid. The median equals the point forecast, and quantiles require positive residual degrees of freedom.

Both forecast strategies are supported:

  • Recursive: one regression; each future row has its own leverage. Target lags are not supported, because later rows would contain earlier forecasts and no closed form exists.
  • Direct: one regression per horizon h, of y[t + h] on the origin row. Target lags are supported, because at the origin they are observed.

Future regressor rows must be complete. Supplied or imputed regressor paths are treated as known and add no regressor uncertainty.

ForecastBVAR

ForecastBVAR density forecasts require posterior draws. Keep mode_only=False, the default; mode_only=True rejects quantile requests. Leave N_burn=None unless you need to set it explicitly, so the backend can derive burn-in from the effective draw count. Quantiles are returned in the fitted model's native metric. Realtime does not reconstruct levels or derive another metric from marginal quantile curves.

Regressor imputation

When X_imputation is requested, every regressor must have at least one observed value. An all-missing regressor is rejected with a ValueError rather than being silently removed from the fitted specification. Models that own their own missing-value handling are not sent through this generic imputation path.

Fable models

RFableModel, RFableETS and RFableARIMA wrap the R fable package. They require an R installation with the arrow, fable, fabletools and tsibble packages available; tests and fits invoke Rscript directly.

  • spec (RFableModel only): a generic R fable model expression, e.g. "ARIMA(value ~ 1 + pdq(1, 0, 0))". The response column is always named value; regressors keep their Python column names. spec is parsed and evaluated as trusted R code. RFableETS and RFableARIMA build this expression for you from their keyword arguments.
  • index: how the date index is converted to a tsibble index — one of "auto" (inferred from the data's spacing), "quarter", "month" or "date".
  • allow_xreg / xreg: RFableModel accepts an allow_xreg flag that controls whether regressor columns in X are permitted. RFableARIMA instead takes an xreg string naming the regressor term to add to the ARIMA formula (e.g. xreg="indicator"), which also sets allow_xreg=True. xreg is an R expression and must come from a trusted source.
ets_monthly = rt.models.RFableETS(error="A", trend="A", season="N", index="month")