# Python Holt-Winters Demand Forecast Script Generator (pandas and statsmodels)

> Source: https://netray.co/tools/python-holt-winters-forecast-script-generator
> Publisher: Netray (netray.co), AI and integration engineering for ERP and manufacturing systems.
> Section: Free Tools

## Short answer

Generate a short Python script that fits Holt-Winters to monthly demand, tests it on a holdout against a naive baseline and writes the forecast to CSV.

## Category

developer

## Audience

- Demand planner
- Data analyst
- ERP developer

## Tool

**Kind:** template

**Output:** code

**Language:** python

**Fields:**

#### Input CSV path

**Key:** csv_in

**Type:** text

**Default:** demand_history.csv

#### Date column

**Key:** date_col

**Type:** text

**Default:** month_start

#### Quantity column

**Key:** qty_col

**Type:** text

**Default:** qty

#### Season length (periods)

**Key:** season

**Type:** number

**Default:** 12

#### Forecast horizon (periods)

**Key:** horizon

**Type:** number

**Default:** 6

#### Holdout periods for testing

**Key:** holdout

**Type:** number

**Default:** 6

#### Trend component

**Key:** trend

**Type:** select

**Options:**

- add
- mul

**Default:** add

#### Seasonal component

**Key:** seasonal

**Type:** select

**Options:**

- add
- mul

**Default:** add

#### Output CSV path

**Key:** csv_out

**Type:** text

**Default:** forecast_output.csv

**Blocks:**

#### Install

**Body:** pip install pandas numpy statsmodels

#### Script

**Body:** import numpy as np
import pandas as pd
from statsmodels.tsa.holtwinters import ExponentialSmoothing

SEASON = {{season}}
HORIZON = {{horizon}}
HOLDOUT = {{holdout}}

df = pd.read_csv("{{csv_in}}", parse_dates=["{{date_col}}"])
series = df.set_index("{{date_col}}")["{{qty_col}}"].sort_index().asfreq("MS").fillna(0.0)

if len(series) < 2 * SEASON + HOLDOUT:
    raise SystemExit("Need at least two full seasons plus the holdout of history")

def fit(train):
    model = ExponentialSmoothing(
        train,
        trend="{{trend}}",
        seasonal="{{seasonal}}",
        seasonal_periods=SEASON,
    )
    return model.fit()

def wmape(actual, forecast):
    a = np.asarray(actual, dtype=float)
    f = np.asarray(forecast, dtype=float)
    return np.abs(a - f).sum() / a.sum() if a.sum() else float("nan")

train = series.iloc[:-HOLDOUT]
test = series.iloc[-HOLDOUT:]
hw_test = fit(train).forecast(HOLDOUT)
naive_test = series.shift(SEASON).iloc[-HOLDOUT:]

print("Holt-Winters WMAPE:", round(wmape(test, hw_test), 3))
print("Seasonal naive WMAPE:", round(wmape(test, naive_test), 3))

final = fit(series).forecast(HORIZON)
out = final.rename("forecast").round(0).clip(lower=0).to_frame()
out.index.name = "{{date_col}}"
out.to_csv("{{csv_out}}")
print(out)

**Notes:** Multiplicative components require strictly positive history, so use additive when the series has zero months. For intermittent items do not use this script, use Croston style methods. Check the statsmodels version you have installed, as argument names can change between releases. Only adopt the model if its holdout error beats the seasonal naive baseline.

## Content

**Intro:** A Holt-Winters model is a good first statistical forecast for items with trend and seasonality, and it is a few lines in Python. This generator writes a complete script that loads your monthly history, tests the model on a holdout against a seasonal naive baseline and writes the forecast to CSV.

**Sections:**

#### How to use the generator

**Body:** Export monthly demand for one item with a date column and a quantity column, for example from the SQL generator on this site. Enter the file and column names, the season length, the horizon and the holdout. Copy the script, install the packages and run it. It prints the holdout error of Holt-Winters and of the seasonal naive baseline, then saves the forecast. Run it once per item or wrap it in a loop.

#### The method behind it

**Body:** Holt-Winters keeps three smoothed components: level, trend and season. Additive seasonality suits a swing of constant size, multiplicative suits a swing that grows with the level. The script holds out the most recent periods, fits on the rest and scores both the model and a seasonal naive forecast with WMAPE. A model that does not beat that simple baseline should not replace it.

**Bullets:**

- Needs two full seasons plus the holdout
- Zero-fills missing months first
- Compares to seasonal naive
- Clips negative forecasts to zero

#### Reading the result

**Body:** If Holt-Winters shows a WMAPE of 0.18 and the seasonal naive shows 0.25 on the same holdout, the model adds value and you can use its forecast as the statistical baseline. If the two are close, keep the simpler method. Always plot the history and forecast before sending numbers to sales, and review items with a recent level shift.

#### Common mistakes

**Body:** Choosing multiplicative seasonality on a series that contains zeros makes the fit fail. Leaving gaps in the dates shifts the seasonal pattern. Evaluating only on the data used to fit the model overstates accuracy. Fitting every item with the same settings ignores that some are trending and some are flat, so consider classifying items first.

**Cta:** If you want model selection, holdout testing and write-back approval handled per item on your own servers, Netray can build that over your ERP data.

**Faq:**

#### How much data does Holt-Winters need?

**Answer:** At least two full seasons to estimate the seasonal shape, so 24 months for monthly seasonality. The script also reserves a holdout, so aim for more than 30 months. With less history use a moving average or seasonal naive.

#### Should I choose additive or multiplicative seasonality?

**Answer:** Choose additive if the seasonal swing stays about the same size as demand grows, and multiplicative if it grows with demand. Multiplicative cannot handle zeros. When unsure, test both on the holdout and keep the lower error.

#### Can I use this for slow moving spare parts?

**Answer:** No. Intermittent items with many zero months need Croston, SBA or TSB. Holt-Winters will forecast a smooth small number each month that does not match real order timing. Classify items with ADI and CV2 first.

#### Does this run inside my ERP?

**Answer:** No, it is a standalone script that works on exported CSV data. Any forecast you load back into an ERP should go through your normal planner review and import process, not be written automatically.

## Search terms

python holt winters forecast, statsmodels exponential smoothing example, python demand forecasting script, holt winters seasonal_periods 12, forecast holdout python mape, demand planning python, pandas monthly forecast csv, seasonal naive baseline python

---
Talk to an engineer: https://netray.co/contact?intent=erp_ai | Machine catalog: https://netray.co/agent/catalog.json
