Connect any forecasting model¶
Stockcast works with any forecasting library: statistical models, machine learning, deep learning, foundation models, or your own code. The connection is always the same three steps:
- Forecast the next \(H\) periods from data up to the decision.
- Summarise the forecast into one target per SKU for the whole window.
- Fit a policy with that target and its dates.
This page shows the recipe with smooth, which the notebooks use, and with a generic model that produces sample paths.
What Stockcast needs from your model¶
For each SKU and decision, one of:
| Your model gives | Turn it into a target by |
|---|---|
| sample paths (simulations, bootstrap, probabilistic networks) | summing each path over the window, then taking the quantile |
| a quantile of the cumulative forecast | using it directly |
| per-period means and standard deviations | the independent-normal mode, or your own covariance formula |
and three dates: the forecast origin (last observed demand), the frequency, and the end date \(= \text{origin} + H\Delta\). Forecast targets explains the theory.
With smooth¶
smooth can forecast the cumulative
demand over a horizon directly. For a daily series history ending at
origin:
from smooth import ES
H = lead_time + review_period
model = ES(model="ANN")
model.fit(history["y"])
total = model.predict(h=H, interval="prediction", level=0.95,
side="upper", cumulative=True)
target = pd.DataFrame({
"unique_id": [sku],
"target": [float(total.upper.iloc[-1, 0])],
"target_end_date": [origin + pd.Timedelta(days=H)],
})
Then fit the policy on it as in
Walkthrough step 4, with forecast_origin=origin. Notebooks
04,
04c,
04e, and
04f run this end to
end.
With any model that samples paths¶
Many libraries can draw future sample paths. The helper below turns a
(n_paths, H) array per SKU into a target table; plug in whatever produces
the paths:
import numpy as np
import pandas as pd
def target_from_paths(paths_by_sku, *, probability, origin, horizon, freq="D"):
"""One cumulative target per SKU from arrays of shape (n_paths, horizon)."""
offset = pd.tseries.frequencies.to_offset(freq)
for sku, p in paths_by_sku.items():
if p.ndim != 2 or p.shape[1] < horizon:
raise ValueError(f"{sku}: paths need shape (n_paths, {horizon}), got {p.shape}")
return pd.DataFrame({
"unique_id": list(paths_by_sku),
"target": [float(np.quantile(p[:, :horizon].sum(axis=1), probability))
for p in paths_by_sku.values()],
"target_end_date": origin + horizon * offset,
})
# Stand-in for your model: 5,000 simulated futures for two SKUs.
rng = np.random.default_rng(1)
paths = {
"tea_250g": rng.poisson(6.0, size=(5_000, 6)),
"coffee_1kg": rng.negative_binomial(3, 0.4, size=(5_000, 6)),
}
origin = pd.Timestamp("2026-01-05")
targets = target_from_paths(paths, probability=0.95, origin=origin, horizon=6)
targets
from stockcast.policies import OrderUpToPolicy
policy = OrderUpToPolicy(lead_time=2, review_period=4, freq="D", service_level=0.95,
allow_backorders=False, date_column="target_end_date").fit(
targets, target_column="target",
forecast_origin=origin,
)
policy.get_target_levels()
Notes¶
- Long format fits. Most forecasting libraries (Nixtla's, sktime's,
skforecast's multi-series tools) use the same long
unique_id/ date / value layout as Stockcast, so reshaping is usually agroupby. - Forecast the window you need. Ask your model for exactly \(H\) steps from the decision's origin. For irregular schedules, \(H\) differs per decision.
- Forecast demand. A target protects against demand, so fit the model on demand. Sales are demand on days that had stock left; on days that ran out they are a floor. Sales and demand shows how to forecast from such a history.
- Use the correct origin. Fit on data up to the origin only. For a decision at demand period \(t\), the origin is the date of period \(t - 1\) (the opening date for \(t = 0\)). Stockcast checks the dates you declare.
- Quantile forecasts per step are not enough on their own. They are marginal: combine them through paths or a cumulative forecast, not by adding them (why).
- Record how the target was made. Keep the model, its settings, and the seed next to the target table. The run manifest holds the target's metadata and a fingerprint that identifies the table, not a copy of it, so keep the table too.