Forecasting with Exponential Smoothing
This page documents the exponential smoothing methods and functions provided by the package, including detailed explanations, docstrings, and usage examples.
MCHammer.ExponentialSmoothingMethod — TypeAbstract type for exponential smoothing methods.
MCHammer.SimpleES — TypeSimple exponential smoothing model.
Fields
alpha::Float64: Smoothing constant.
MCHammer.DoubleES — TypeDouble exponential smoothing model.
Fields
alpha::Float64: Level smoothing constant.beta::Float64: Trend smoothing factor.
MCHammer.TripleES — TypeTriple exponential smoothing (Holt–Winters) model.
Fields
season_length::Int: Number of periods in a season (e.g., 12 for monthly data).alpha::Float64: Level smoothing constant.beta::Float64: Trend smoothing factor.gamma::Float64: Seasonal smoothing factor.
Method Functions
Simple Exponential Smoothing
Simple exponential smoothing model. Fields:
alpha::Float64: Smoothing constant.
Example:
model = MCHammer.SimpleES(0.2)SimpleES(0.2)Double Exponential Smoothing
Double exponential smoothing model with trend. Fields:
alpha::Float64: Level smoothing constant.beta::Float64: Trend smoothing factor.
Example:
model = DoubleES(0.3, 0.1)DoubleES(0.3, 0.1)Triple Exponential Smoothing
Triple exponential smoothing model (Holt–Winters method) with trend and seasonality.
Fields:
season_length::Int: Number of periods in a season (e.g., 12 for monthly).alpha::Float64: Level smoothing constant.beta::Float64: Trend smoothing factor.gamma::Float64: Seasonal smoothing factor.
Example:
model = TripleES(12, 0.3, 0.2, 0.1)TripleES(12, 0.3, 0.2, 0.1)Smoothing Functions
Perform smoothing on historical data.
MCHammer.es_smooth — FunctionPerform simple exponential smoothing on the historical series.
es_smooth(m::SimpleES, HistoricalSeries::Vector{<:Real}; forecast_only::Bool=false)Arguments
m::SimpleES: A SimpleES model containing the smoothing constantalpha.HistoricalSeries: A vector of historical data.forecast_only(optional): If true, only the smoothed forecast is returned.
Returns
A dataframe of smoothed values or the original and smoothed values.
Perform double exponential smoothing on the historical series.
es_smooth(m::DoubleES, HistoricalSeries::Vector{<:Real})Arguments
m::DoubleES: A DoubleES model withalphaandbeta.HistoricalSeries: A vector of historical data.
Returns
A DataFrame where:
level: The smoothed level values.trend: The estimated trend values.smoothed: The sum of level and trend.
Smoothing SimpleES:
data = [100, 102, 104, 108, 110]
simple_model = SimpleES(0.2)
smoothed_simple = es_smooth(simple_model, data; forecast_only=true)| Row | forecast |
|---|---|
| Float64 | |
| 1 | 100.0 |
| 2 | 100.4 |
| 3 | 101.12 |
| 4 | 102.496 |
| 5 | 103.997 |
Smoothing DoubleES:
# DoubleES example
double_model = DoubleES(0.2, 0.1)
smoothed_double = es_smooth(double_model, data)| Row | level | trend | smoothed |
|---|---|---|---|
| Float64 | Float64 | Float64 | |
| 1 | 100.0 | 0.0 | 100.0 |
| 2 | 100.4 | 0.04 | 100.44 |
| 3 | 101.12 | 0.108 | 101.228 |
| 4 | 102.496 | 0.2348 | 102.731 |
| 5 | 103.997 | 0.3614 | 104.358 |
Forecasting out
Generate forecasts based on smoothing models.
MCHammer.es_forecast — FunctionProduce a forecast using double exponential smoothing.
es_forecast(m::DoubleES, HistoricalSeries::Vector{<:Real}, periods::Int)Arguments
m::DoubleES: A DoubleES model withalphaandbeta.HistoricalSeries: A vector of historical data.periods: Number of periods to forecast beyond the historical data.
Returns
A DataFrame with columns: Historical, Level, and Forecast.
Produce a forecast using triple exponential smoothing (Holt–Winters method) with seasonal adjustment.
es_forecast(m::TripleES, HistoricalSeries::Vector{<:Real}, periods_out::Int; forecast_only::Bool=false)Arguments
m::TripleES: A TripleES model withseason_length,alpha,beta, andgamma.HistoricalSeries: A vector of historical data.periods_out: Number of periods to forecast beyond the historical data.forecast_only(optional): If true, returns only the forecasted values.
Returns
Either a vector of forecasted values (if forecast_only=true) or the complete in-sample forecast.
Forecasting DoubleES:
data = [100, 102, 104, 108, 110]
# DoubleES forecast
double_model = DoubleES(0.2, 0.1)
df_forecast = es_forecast(double_model, data, 3)| Row | Historical | Level | Forecast |
|---|---|---|---|
| Int64 | Float64 | Float64 | |
| 1 | 100 | 100.0 | 100.0 |
| 2 | 102 | 100.4 | 100.4 |
| 3 | 104 | 101.12 | 101.12 |
| 4 | 108 | 102.496 | 102.496 |
| 5 | 110 | 103.997 | 103.997 |
| 6 | 0 | 0.0 | 104.358 |
| 7 | 0 | 0.0 | 104.72 |
| 8 | 0 | 0.0 | 105.081 |
<br> Let's visualize this as a line plot comparing historical data and the forecast <br>
using Plots
# Create an index vector corresponding to the rows.
x_all = 1:nrow(df_forecast)
# Identify the indices where Historical is nonzero.
nonzero_idx = findall(x -> x != 0, df_forecast.Historical)
# Plot forecast over all rows.
plot(x_all, df_forecast.Forecast, label="Forecast", xlabel="Time", ylabel="Value", lw=2)
# Plot Historical and Level only on nonzero indices.
plot!(nonzero_idx, df_forecast.Historical[nonzero_idx], label="Historical", marker=:circle, lw=2)
plot!(nonzero_idx, df_forecast.Level[nonzero_idx], label="Level", marker=:square, lw=2)Forecasting TripleES:
# TripleES forecast
seasonal_data = [120,130,140,130,125,135,145,150,160,155,165,170]
triple_model = MCHammer.TripleES(12, 0.3, 0.2, 0.1)
df_forecast = es_forecast(triple_model, seasonal_data, 6; forecast_only=false)| Row | Historical | Forecast |
|---|---|---|
| Int64 | Float64 | |
| 1 | 120 | 120.0 |
| 2 | 130 | 123.599 |
| 3 | 140 | 141.283 |
| 4 | 130 | 136.585 |
| 5 | 125 | 134.901 |
| 6 | 135 | 146.628 |
| 7 | 145 | 157.139 |
| 8 | 150 | 161.769 |
| 9 | 160 | 170.804 |
| 10 | 155 | 164.48 |
| 11 | 165 | 172.984 |
| 12 | 170 | 176.458 |
| 13 | 0 | 127.067 |
| 14 | 0 | 139.386 |
| 15 | 0 | 149.558 |
| 16 | 0 | 139.938 |
| 17 | 0 | 135.499 |
| 18 | 0 | 146.212 |
using Plots
theme(:ggplot2)
# Create an index vector corresponding to the rows.
x_all = 1:nrow(df_forecast)
# Identify the indices where Historical is nonzero.
nonzero_idx = findall(x -> x != 0, df_forecast.Historical)
# Plot forecast over all rows.
plot(x_all, df_forecast.Forecast, label="Forecast", xlabel="Time", ylabel="Value", lw=2)
# Plot Historical and Level only on nonzero indices.
plot!(nonzero_idx, df_forecast.Historical[nonzero_idx], label="Historical", marker=:circle, lw=2)Forecast Standard Error
Calculate standard error between historical and forecasted data.
MCHammer.FrctStdError — FunctionCalculate the fractional standard error between the historical series and forecast series.
FrctStdError(HistoricalSeries::Vector{<:Real}, ForecastSeries::Vector{<:Real})Arguments
HistoricalSeries: A vector of historical data.ForecastSeries: A vector of forecasted data.
Returns
The standard error as a Float64.
Example:
data = [100, 102, 104, 108, 110]
double_model = DoubleES(0.3, 0.2)
# Generate in-sample forecast; since periods = 0, the forecast equals the smoothed level
df_forecast = es_forecast(double_model, data, 0)
forecasted = df_forecast.Forecast # Access the forecast column from the returned DataFrame
se = FrctStdError(data, forecasted)
println("Forecast Standard Error: ", se)Forecast Standard Error: 4.951604689391114Fitting historical data
Automatically optimize parameters for TripleES.
MCHammer.es_fit — FunctionAutomatically fit optimal parameters for triple exponential smoothing by random trials.
es_fit(::Type{TripleES}, HistoricalSeries::Vector{<:Real}, season_length::Int, trials::Int)Arguments
HistoricalSeries: A vector of historical data.season_length: Number of periods in a season.trials: Number of random trials to perform.
Returns
A TripleES instance with the best parameters (lowest forecast error).
data = [120,130,140,130,125,135,145,150,160,155,165,170]
best_model = es_fit(TripleES, data, 12, 1000)TripleES(12, 0.007262037419813128, 0.24140746716674133, 0.9133680978504877)Simulating forecasts (Monte-Carlo)
Simulate forecast uncertainty using historical returns.
MCHammer.forecast_uncertainty — Functionforecast_uncertainty(HistoricalData::Vector{<:Real}, PeriodsToForecast::Int) -> Vector{Float64}Calculate forecast uncertainty multipliers based on historical data volatility.
This function computes the percentage returns from the historical data, estimates the standard deviation (σ) of those returns, and then generates a vector of forecast multipliers. Each multiplier is calculated as:
$u = 1 + \sigma \cdot \epsilon$
where ($\epsilon$) is a random sample drawn from a standard normal distribution $\epsilon \sim \mathcal{N}(0,1)$ and $\sigma$ is the standard deviation of the historical percentage returns.
Arguments
HistoricalData::Vector{<:Real}: A vector of historical observations (e.g., prices, values).PeriodsToForecast::Int: The number of forecast periods for which to generate uncertainty multipliers.
Returns
- A vector of length
PeriodsToForecastcontaining the forecast uncertainty multipliers. These multipliers can be used to perturb a base forecast to simulate forecast variability.
Practical Example:
#The historical data is used to assess the volatility of the series automatically.
data = [100, 105, 102, 108, 110]
uncertainty = forecast_uncertainty(data, 4)4-element Vector{Float64}:
1.0757128905163262
0.9968106686862175
1.0044588140446982
1.009764197257352Now let's assume we want to run 1000 Monte-Carlo Trials, this is the approach we would take.
function simulate_ESTS()
#Simulation Model Inputs and Parameters
n_trials = 1000
n_periods = 5
historical_data = [100, 105, 102, 108, 110, 115, 120, 118, 122, 125, 130, 128]
seasonality = 12
# 1. Fit optimal TripleES parameters using 1,000 optimization trials. Seasonality is the number of periods to test for seasonal patterns.
optimal_triple = es_fit(TripleES, historical_data, seasonality, 1000)
# 2. Generate the base forecast (for n_periods = 5) with the fitted model
base_forecast = es_forecast(optimal_triple, historical_data, n_periods; forecast_only=true)
# 3. Run simulation: for 1,000 trials, apply forecast uncertainty to the base forecast
# Prepare a DataFrame to store simulation results in long format.
# Columns: Trial (simulation number), Period (forecast period), Forecast (simulated forecast value)
sim_results = DataFrame(Trial=Int[], Period=Int[], Forecast=Float64[])
for trial in 1:n_trials
# Compute forecast uncertainty multipliers for the next n_periods
uncertainty = forecast_uncertainty(historical_data, n_periods)
# Apply the uncertainty multipliers to the base forecast to simulate forecast variability.
# This returns a DataFrame with columns "Historical", "Level", and "Forecast".
simulated_forecast = base_forecast .* uncertainty
# Now iterate over the rows using eachrow() and enumerate to get an index.
for (i, row) in enumerate(eachrow(simulated_forecast))
push!(sim_results, (Trial = trial, Period = i, Forecast = row.Forecast))
end
end
return sim_results = unstack(sim_results, :Period, :Forecast)
end
#Genreate simulation trials
sim_results_to_chart = simulate_ESTS()
#Here are the first 10 trials
first(sim_results_to_chart,10)| Row | Trial | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| Int64 | Float64? | Float64? | Float64? | Float64? | Float64? | |
| 1 | 1 | 103.682 | 104.658 | 103.037 | 112.308 | 115.862 |
| 2 | 2 | 100.75 | 106.704 | 108.044 | 113.159 | 121.32 |
| 3 | 3 | 101.081 | 102.861 | 103.074 | 113.724 | 116.99 |
| 4 | 4 | 102.421 | 107.713 | 106.305 | 109.303 | 114.863 |
| 5 | 5 | 98.6351 | 106.282 | 106.582 | 109.469 | 114.067 |
| 6 | 6 | 101.774 | 107.237 | 103.471 | 109.955 | 118.265 |
| 7 | 7 | 103.627 | 110.462 | 107.24 | 110.368 | 113.114 |
| 8 | 8 | 108.068 | 111.321 | 101.209 | 111.605 | 114.916 |
| 9 | 9 | 100.45 | 106.996 | 103.937 | 116.764 | 113.986 |
| 10 | 10 | 101.327 | 107.732 | 105.662 | 118.642 | 107.231 |
When plotted, we can see the uncertainty is applied using historical volatility. Remember to remove the trials column for better charting.
trend_chrt(sim_results_to_chart[:,2:6])Sources & References
- Eric Torkia, Decision Superhero Vol. 3, chapter 5 : Predicting 1000 futures, Technics Publishing, 2025
- Available on Amazon : https://a.co/d/4YlJFzY . Volumes 2 and 3 to be released in Spring and Fall 2025.