Forecasting with Exponential Smoothing
This page documents the exponential smoothing methods and functions provided by the package, including detailed explanations, docstrings, and usage examples.
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 withalpha
andbeta
.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 withalpha
andbeta
.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.951604689391114
Fitting 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.03592609905071564, 0.22023858456162726, 0.8885711298535152)
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
PeriodsToForecast
containing 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.011876340185373
1.012254908712964
0.9587948500115532
1.0354953933329445
Now 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.21 | 111.323 | 105.878 | 110.127 | 120.755 |
2 | 2 | 100.894 | 104.252 | 105.863 | 119.184 | 112.218 |
3 | 3 | 96.0384 | 112.571 | 108.383 | 109.232 | 110.863 |
4 | 4 | 99.8376 | 105.885 | 108.98 | 111.684 | 110.869 |
5 | 5 | 104.315 | 115.385 | 107.618 | 111.613 | 117.21 |
6 | 6 | 102.666 | 105.887 | 100.43 | 113.962 | 116.073 |
7 | 7 | 101.027 | 108.202 | 107.008 | 110.316 | 119.041 |
8 | 8 | 97.7293 | 105.462 | 101.295 | 112.037 | 120.382 |
9 | 9 | 102.1 | 103.141 | 100.514 | 115.858 | 112.529 |
10 | 10 | 104.209 | 109.836 | 101.908 | 111.903 | 113.437 |
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.