D09-F02-A05 / Complete engineering topic

Holt-Winters

A production-minded guide to Holt-Winters.

D09 · STATISTICAL TIME SERIES
D09-F02-A05Canonical / Tested / Open
D09 / D09-F02

The useful outcome

Holt-Winters represents forecast structure directly as evolving level, trend, and seasonal states. It is often easier to explain operationally than lag-polynomial models. The practical goal is a path that another person can reproduce from a declared cutoff—not a line whose origin, state, or future assumptions are hidden.

The question this tutorial answers is: How do level, trend, and seasonal states update after every observation and produce a multi-season forecast?

What you will build

You will calculate the model by hand, implement it in Python and TypeScript, inspect eight long synthetic scenarios, and learn where correct recursion ends and empirical forecast validation begins.

Simple exponential smoothing explains a weighted level. Seasonal period and additive versus multiplicative scale must be understood before choosing this variant.

Freeze the variant before fitting anything

Additive trend and additive seasonality, fixed alpha/beta/gamma-star in [0,1], a supplied known initial state with published provenance, no damping, and deterministic recursive updates.

VariantDefinitionBest useMain limitation
Selected additive-additiveconstant-magnitude seasonality and additive trendstable seasonal amplitudecan forecast negative values
Multiplicative seasonalityseasonal factors scale the levelamplitude grows with levelrequires positive compatible data
Damped trendfuture trend contribution decays by philong-horizon restraintadds another state convention
ETS state spaceexplicit error/trend/seasonal modellikelihood and intervalsbroader than classical recursions

This matters because forecast libraries often share a model-family name while differing in constants, trend representations, signs, initialization, missing data, or optimizer behavior.

Initialization and gamma-star audit

The lab publishes the two-cycle initialization information set separately from the supplied state. Its gamma input is gamma-star in the updated-level seasonal equation. If another system uses the prior-level-and-trend seasonal equation, reconcile the parameterization before comparing values.

Intuition before notation

Each observation is split into a seasonally adjusted level update, a trend update, and an update to the seasonal position that just occurred. The hardest part is state timing: use the previous seasonal state for prediction before overwriting it. The forecast origin is therefore a visible data boundary, not just the point where a chart changes color.

Forecast origin and recursive path

Formula and symbols

t=α(ytstm)+(1α)(t1+bt1); bt=β(tt1)+(1β)bt1; st=γ(ytt)+(1γ)stm\ell_t=\alpha(y_t-s_{t-m})+(1-\alpha)(\ell_{t-1}+b_{t-1});\ b_t=\beta(\ell_t-\ell_{t-1})+(1-\beta)b_{t-1};\ s_t=\gamma(y_t-\ell_t)+(1-\gamma)s_{t-m}

SymbolMeaningUnit or range
ell_tlevel stateseries units
b_ttrend state per observationseries units/step
s_tadditive seasonal stateseries units
alpha,beta,gamma-starlevel, trend, and selected updated-level seasonal weights[0,1]
mseasonal periodobservations/cycle

The formula is a conditional point forecast. Future random innovations use zero because zero is their conditional expectation—not because the future is known to contain no shocks.

Data that the formula is allowed to see

A finalized equally spaced series with at least two seasons, alpha/beta/gamma-star, period m, known initial level/trend/m seasonal states, initialization information set, and horizon. Observations must be equally spaced, finite, ordered, and on one consistent scale. The model version and cutoff are frozen before the holdout. Revisions require a new forecast version rather than silent history replacement.

Algorithm

  1. Validate smoothing weights, period, two seasons of data, and all known initial states.
  2. Before updating, calculate the one-step fitted value from previous level, trend, and matching seasonal state.
  3. Update level using the seasonally adjusted observation.
  4. Update trend from the level change and seasonal state from observation minus new level.
  5. Forecast h steps as final level plus h times trend plus the aligned seasonal state.

Recursion anatomy for the selected forecast model

Hand calculation

The following is author-derived arithmetic over synthetic values:

JSON
{
  "values": [
    10,
    12,
    11,
    13
  ],
  "config": {
    "alpha": 0.5,
    "beta": 0.5,
    "gamma": 0.5,
    "period": 2,
    "horizon": 2,
    "initial_level": 9,
    "initial_trend": 1,
    "initial_seasonals": [
      -1,
      1
    ]
  }
}
  • After four updates: level=12.5859375, trend=.69140625, seasonal states=[-.921875,.61328125].
  • h=1: 12.5859375+.69140625-.921875=12.35546875.
  • h=2: 12.5859375+2(.69140625)+.61328125=14.58203125.

The expected path is [12.35546875, 14.58203125]. Independent tests pin these values before the larger fixtures are generated.

Calculation flow

Rendering system map…

The order matters. Revealed actuals may score the path, but they may not alter the version that was issued at the cutoff.

Guided experiment

The standalone lab contains eight scenarios and 1,440 calendar-month-end observations. It trains on 144 values and forecasts a 36-value holdout. Start at horizon one, predict what state should persist, then Step through twelve model-specific checkpoints. Change the principal parameter and watch the complete path recompute from the frozen training data.

The scenarios isolate persistence, mean reversion, linear trend, seasonal structure, a late level shift, one gross outlier, a variance-regime change, and seasonal phase/amplitude drift. They are not selected trades or provider histories.

The state an expert audits

For this model, keep the final level, accumulated undamped trend, aligned seasonal index, initialization provenance, and gamma-star convention visible.

Use additive Holt-Winters when seasonal amplitude is roughly constant in series units and explicit evolving states aid operations. Compare multiplicative seasonality and damped trend when amplitude scales with level or long-horizon trend is implausible.

Decompose the path into level, h-times-trend, and seasonal contribution. A phase error points to period or state alignment; explosive long-horizon movement points to undamped trend; early instability points to initialization.

When implementations disagree, use this order: Verify whether gamma multiplies y minus the updated level or y minus the prior level-and-trend forecast, then check initial-state timestamp, seasonal ordering, damping, and additive versus multiplicative form.

Model-specific state and benchmark workbench

The workbench is deliberately quantitative. It connects the model's unique state to the declared benchmark rather than asking the learner to judge whether the purple line “looks right.”

Failure diagnosis

  • Initialization can materially change the first several seasons.
  • A wrong period rotates seasonal states onto the wrong timestamps.
  • Additive seasonality is inappropriate when amplitude scales with level.
  • Undamped trends can become implausible at long horizons.
  • A level shift can be absorbed slowly depending on alpha and beta.

Boundary and failure comparison

Calling smoothing weights probabilities or assuming automatic initialization is universal. Correct arithmetic answers “did we implement the selected model?” It does not answer “is this a good model for this series?”

Implementation contract

The Python reference favors readable state transitions. TypeScript implements the same contract independently. Shared fixtures and focused tests require matching numeric paths within 1e-9 and exact agreement on lengths, nulls, states, and invalid-input behavior.

Estimation is intentionally outside the public function. In a real workflow, fit or select parameters using only training data, record the method and version, pass the frozen result into this recursion engine, and evaluate using rolling origins against simple benchmarks.

Practical evaluation

Use a loss function that matches the decision, report by horizon, and include baseline forecasts. Preserve the refit schedule and origin timestamps. If prediction intervals are needed, choose and validate an uncertainty model; do not manufacture intervals from point forecasts alone.

The lab's declared baseline is seasonal naïve forecast. At active horizon k, it calculates errors only for the first k revealed holdout values:

  • MAE = mean(abs(actual - forecast));
  • RMSE = sqrt(mean((actual - forecast)^2));
  • bias = mean(actual - forecast);
  • MAE skill = 1 - model_MAE / benchmark_MAE.

Positive skill means the model's revealed-prefix MAE is lower than the frozen benchmark's. Negative skill is useful evidence: the sophisticated recursion is losing to a simple rule on this synthetic path. When benchmark MAE is zero, skill is undefined rather than infinite. One origin still cannot establish general performance; repeat the complete procedure over rolling origins before making an empirical claim.

Related methods

Forecast Models family handoff

AutoReg introduces lag memory. ARMA adds innovation memory. ARIMA changes the modeled scale through differencing. SARIMA/SARIMAX adds seasonal polynomials and future driver scenarios. Holt-Winters maintains level, trend, and seasonal states directly. Theta combines a smoothed level with constrained drift.

Next: Theta Forecast.

Historical and evidence boundary

A historical example is not useful for this mechanism lesson. A defensible case requires licensed point-in-time observations, identity, frequency, adjustment and revision basis, cutoff, parameters frozen before inspection, benchmark, holdout, and publication rights.

This tutorial establishes definition fidelity. It does not establish stationarity, model adequacy, forecast calibration, historical association, causation, profitability, or investment suitability.

Primary references

Holt-Winters Calculation Flow

Purpose: separate validated historical state, fixed model parameters, recursive forecast state, and holdout evaluation.

Rendering system map…

Takeaway: evaluation data follows publication; it never repairs the already issued forecast.

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

FPP_HW — Forecasting: Principles and Practice — Holt-Winters

  • Organization or authors: Rob J. Hyndman and George Athanasopoulos
  • Source type: Authoritative open forecasting textbook
  • Publication or effective date: 2021 third edition; online version updated 2026-03-09
  • Version: 3rd edition
  • URL or DOI: https://otexts.com/fpp3/holt-winters.html
  • Accessed: 2026-07-27
  • Jurisdiction: Technical method; no legal jurisdiction.
  • Supports: Level, trend, and seasonal recursions plus additive versus multiplicative interpretation.
  • Limitations: State-space error models and automatic initialization are not reproduced.
  • Evidence role: Definition/variant comparison only; not provider data or an empirical result for the synthetic fixtures.
  • Publication boundary: Link and paraphrase are public; no source dataset or copyrighted table is reproduced.

STATSMODELS_HW — Exponential smoothing examples

  • Organization or authors: statsmodels developers
  • Source type: Maintained official library documentation
  • Publication or effective date: 2025-12-05 documentation build
  • Version: statsmodels 0.14.6
  • URL or DOI: https://www.statsmodels.org/stable/examples/notebooks/generated/exponential_smoothing.html
  • Accessed: 2026-07-27
  • Jurisdiction: Technical method; no legal jurisdiction.
  • Supports: Holt and Holt-Winters combinations, fixed smoothing parameters, damping, and additive versus multiplicative components.
  • Limitations: The package selects additive trend and additive seasonality with known initialization and no damping.
  • Evidence role: Definition/variant comparison only; not provider data or an empirical result for the synthetic fixtures.
  • Publication boundary: Link and paraphrase are public; no source dataset or copyrighted table is reproduced.

FPP_ACCURACY — Forecasting: Principles and Practice — Evaluating point forecast accuracy

  • Organization or authors: Rob J. Hyndman and George Athanasopoulos
  • Source type: Authoritative open forecasting textbook
  • Publication or effective date: 2021 third edition; online version updated 2026-03-09
  • Version: 3rd edition
  • URL or DOI: https://otexts.com/fpp3/accuracy.html
  • Accessed: 2026-07-27
  • Jurisdiction: Technical method; no legal jurisdiction.
  • Supports: Forecast accuracy must be measured on genuine forecasts outside the training sample; MAE, RMSE, scaled errors, and benchmarks answer different evaluation questions.
  • Limitations: A single synthetic holdout is a teaching comparison, not an empirical model-selection study.
  • Evidence role: Definition/variant comparison only; not provider data or an empirical result for the synthetic fixtures.
  • Publication boundary: Link and paraphrase are public; no source dataset or copyrighted table is reproduced.

FPP_TSCV — Forecasting: Principles and Practice — Time series cross-validation

  • Organization or authors: Rob J. Hyndman and George Athanasopoulos
  • Source type: Authoritative open forecasting textbook
  • Publication or effective date: 2021 third edition; online version updated 2026-03-09
  • Version: 3rd edition
  • URL or DOI: https://otexts.com/fpp3/tscv.html
  • Accessed: 2026-07-27
  • Jurisdiction: Technical method; no legal jurisdiction.
  • Supports: Rolling forecasting origins preserve temporal order and can evaluate horizon-specific errors without using future observations.
  • Limitations: This family demonstrates one frozen origin; production claims require many origins and a declared refit schedule.
  • Evidence role: Definition/variant comparison only; not provider data or an empirical result for the synthetic fixtures.
  • Publication boundary: Link and paraphrase are public; no source dataset or copyrighted table is reproduced.

FPP_RESIDUALS — Forecasting: Principles and Practice — Evaluating regression and residual behavior

  • Organization or authors: Rob J. Hyndman and George Athanasopoulos
  • Source type: Authoritative open forecasting textbook
  • Publication or effective date: 2021 third edition; online version updated 2026-03-09
  • Version: 3rd edition
  • URL or DOI: https://otexts.com/fpp3/regression-evaluation.html
  • Accessed: 2026-07-27
  • Jurisdiction: Technical method; no legal jurisdiction.
  • Supports: Residual plots and residual autocorrelation diagnose information left in the fitted model, but residual size is not a substitute for genuine forecast error.
  • Limitations: Residual diagnostics do not prove unbiasedness, calibrated intervals, or out-of-sample usefulness for these fixed teaching configurations.
  • Evidence role: Definition/variant comparison only; not provider data or an empirical result for the synthetic fixtures.
  • Publication boundary: Link and paraphrase are public; no source dataset or copyrighted table is reproduced.
holt_winters.ts

type ForecastResult = {
  forecast:number[];
  fitted:(number|null)[];
  residuals?:number[];
  state:Record<string,unknown>;
  [key:string]:unknown;
};
const finite=(value:number,name:string)=>{
  if(typeof value!=="number"||!Number.isFinite(value))throw new Error(`${name} must be finite`);
  return value;
};
const positiveInt=(value:number,name:string,min=1)=>{
  if(!Number.isInteger(value)||value<min)throw new Error(`${name} must be an integer >= ${min}`);
  return value;
};
const series=(values:number[],minimum=2)=>{
  if(!Array.isArray(values)||values.length<minimum)throw new Error(`values must contain at least ${minimum} observations`);
  return values.map((value,index)=>finite(value,`values[${index}]`));
};
const coefficients=(values:number[],name:string,allowEmpty=true)=>{
  if(!Array.isArray(values)||(!allowEmpty&&values.length===0))throw new Error(`${name} must be a coefficient list`);
  return values.map((value,index)=>finite(value,`${name}[${index}]`));
};

export function forecastHoltWintersAdditive(values:number[],alpha:number,beta:number,gamma:number,period:number,horizon:number,initialLevel:number,initialTrend:number,initialSeasonals:number[]):ForecastResult{
  const m=positiveInt(period,"period",2),y=series(values,m*2),H=positiveInt(horizon,"horizon"),a=finite(alpha,"alpha"),b=finite(beta,"beta"),g=finite(gamma,"gamma");
  for(const [name,value] of [["alpha",a],["beta",b],["gamma",g]] as const)if(value<0||value>1)throw new Error(`${name} must be in [0, 1]`);
  let level=finite(initialLevel,"initial_level"),trend=finite(initialTrend,"initial_trend");
  const seasonals=coefficients(initialSeasonals,"initial_seasonals",false);if(seasonals.length!==m)throw new Error("initial_seasonals must contain exactly period values");
  const fitted:number[]=[],residuals:number[]=[],trace:Record<string,number>[]=[];
  y.forEach((observation,t)=>{
    const position=t%m,oldLevel=level,oldTrend=trend,oldSeason=seasonals[position],prediction=oldLevel+oldTrend+oldSeason;
    level=a*(observation-oldSeason)+(1-a)*(oldLevel+oldTrend);
    trend=b*(level-oldLevel)+(1-b)*oldTrend;
    seasonals[position]=g*(observation-level)+(1-g)*oldSeason;
    fitted.push(prediction);residuals.push(observation-prediction);trace.push({index:t,level,trend,seasonal:seasonals[position]});
  });
  const forecast=Array.from({length:H},(_,index)=>level+(index+1)*trend+seasonals[(y.length+index)%m]);
  return {forecast,fitted,residuals,trace,state:{level,trend,seasonals}};
}
Full-height labholt winters labOpen full screen