<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="gfn2html.xsl"?>
<gretl-functions>
<gretl-function-package name="ETS" needs-time-series-data="true" minver="2024c" lives-in-subdir="true">
<author email="cottrell@wfu.edu">Allin Cottrell and Ignacio Diaz-Emparanza</author>
<version>1.0</version>
<date>2026-11-07</date>
<description>Exponential smoothing: simple, Holt and Holt-Winters</description>
<tags>C53</tags>
<label>ETS</label>
<menu-attachment>MAINWIN/Variable/Filter</menu-attachment>
<help>
pdfdoc:ETS.pdf
</help>
<gui-help filename="ets_gui_help.md">
*ETS graphical interface*

- The `model` selector chooses among simple exponential smoothing (level
only), Holt's method (level and trend), and Holt-Winters with an additive
or multiplicative seasonal component. The seasonal models require
data with periodicity greater than 1 (quarterly, monthly, etc.).

- The parameters α, β and γ are the smoothing coefficients for the level,
trend and seasonal components of the time series, respectively. Only
those relevant to the chosen model are used (α for simple smoothing, α
and β for Holt, all three for Holt-Winters). Setting these values
manually may be considered an &quot;expert&quot; (or pedagogical) option. If you
leave the `parms` selection at `automatic` gretl will estimate them for
you.

- In the multiplicative Holt-Winters case the seasonal effect is
proportional to the level-plus-trend component; in the additive case it's
simply added to level-plus-trend.

- The `ntest` selector allows you to reserve a certain number of
observations (at the end of the available data) for testing forecasts. If
the sample contains trailing observations beyond the range of the y data,
setting this to zero means that out-of-sample forecasts will be made
instead.

- To see more details of the procedure, increase the `verbose` level.
</gui-help>
<data-files count="1">
examples </data-files>
<gretl-function name="ETS" type="bundle">
 <params count="2">
  <param name="y" type="series" const="true"/>
  <param name="specs" type="bundle" optional="true" const="true"/>
 </params>
<code>scalar fixed_theta = 0
scalar fixed_init = 0
string yname = argname(y, &quot;y&quot;)
matrix init = {}

bundle args = ets_default_args()
if exists(specs)
  fixed_theta = inbundle(specs, &quot;theta&quot;)
  fixed_init = inbundle(specs, &quot;init&quot;)
  # a seasonal model implies a trend unless told otherwise
  if inbundle(specs, &quot;seasonal&quot;) &amp;&amp; !inbundle(specs, &quot;trend&quot;)
    args.trend = specs.seasonal &gt; 0
  endif
  errorif(bcheck(&amp;args, specs), &quot;aborting&quot;)
endif

ets_checks(args, fixed_theta, fixed_init)
scalar td = args.trend
scalar sf = args.seasonal
scalar m = sf &gt; 0 ? $pd : 0
scalar ntest = args.ntest

matrix limits = ets_ranges(y, ntest, m &gt; 0 ? 4 * m : 4)
scalar t1 = limits[1]
scalar t2 = limits[2]
scalar t3 = limits[3]
smpl t1 t3

scalar verbose = args.verbose

series level = NA
list L = y level
if td
  series trend = NA
  L += trend
endif
if sf &gt; 0
  series seasonal = NA
  L += seasonal
endif
series forecast = NA
L += forecast

if verbose &gt; 0
  printf &quot;%s\n&quot;, ets_model_name(td, sf)
  printf &quot;Training range: %s to %s\n&quot;, obslabel(t1), obslabel(t2)
  if fixed_theta
    printf &quot;Fixed parameters:\n&quot;
    ets_print_theta(args.theta)
  endif
  if fixed_init
    printf &quot;Fixed initializer:\n&quot;
    ets_print_init(args.init, td, sf)
  endif
  if ntest &gt; 0
    printf &quot;Testing forecast: %s to %s\n\n&quot;, obslabel(t2+1), obslabel(t3)
  elif t3 &gt; t2
    printf &quot;Out-of-sample forecast: %s to %s\n\n&quot;, obslabel(t2+1), obslabel(t3)
  else
    printf &quot;Number of forecasts: 0\n\n&quot;
  endif
endif

if fixed_init
  init = vec(args.init)
else
  init = ets_auto_init(y, td, sf)
  if verbose &gt; 1
    print init
  endif
endif

# training data as a column vector
smpl t1 t2
matrix my = {y}
smpl t1 t3

if fixed_theta
  matrix theta = vec(args.theta)
  if sf &gt; 0 &amp;&amp; !fixed_init
    err = ets_given_theta(theta, my, &amp;init, td, sf, m, verbose)
    if err
      funcerr &quot;Failed to optimize initializer&quot;
    endif
  endif
else
  matrix theta = ets_auto_parms(my, &amp;init, td, sf, m, verbose, fixed_init)
  if !ok(theta[1])
    funcerr &quot;Failed to estimate parameters&quot;
  endif
endif
theta = vec(theta)

# training: states and one-step-ahead forecasts
matrix Wtr = ets_filter(theta, init, my, td, sf, m)
scalar fcol = 3 + td + (m &gt; 0)
matrix u = Wtr[,1] - Wtr[,fcol]
scalar n = rows(u)
scalar SSE = sumc(u.^2)
scalar RMSE_train = sqrt(SSE / n)
scalar MAE_train = sumc(abs(u)) / n

# transcribe to series over t1..t2
smpl t1 t2
level = Wtr[,2]
if td
  trend = Wtr[,3]
endif
if sf &gt; 0
  seasonal = Wtr[,3+td]
endif
forecast = Wtr[,fcol]

# h-step-ahead forecasts over t2+1..t3 (test or out-of-sample)
if t3 &gt; t2
  scalar H = t3 - t2
  matrix hv = seq(1, H)'
  matrix fc = Wtr[n,2] * ones(H, 1)
  if td
    fc += hv * Wtr[n,3]
  endif
  if sf &gt; 0
    # last m seasonals, cycled out to H periods
    matrix sh = Wtr[n-m+1:n,3+td] * ones(1, ceil(H/m))
    sh = vec(sh)[1:H]
    if sf == 1
      fc += sh
    else
      fc = fc .* sh
    endif
  endif
  smpl t2+1 t3
  forecast = fc
endif
smpl t1 t3

if verbose &gt; 0
  if verbose &gt; 1
    printf &quot;\n&quot;
  endif
  printf &quot;training SSE = %g, RMSE = %g, MAE = %g\n&quot;, SSE, RMSE_train, MAE_train
  print L -o
endif

set skip_missing off
matrix S = {L}
nparms = ets_nparms(td, m, fixed_theta, fixed_init)
bundle ret = _(theta, init, trend=td, seasonal=sf, ntest, S, RMSE_train, MAE_train, yname, limits, nparms)

if ntest &gt; 0
  ets_add_eval_stats(&amp;ret)
  if verbose &gt; 0
    printf &quot;testing RMSE = %g, MAE = %g\n&quot;, ret.RMSE_test, ret.MAE_test
  endif
endif

if verbose &gt; 0
  printf &quot;\n&quot;
endif
if sf != 2 &amp;&amp; t3 &gt; t2
  ets_add_fcse(&amp;ret)
endif
if $version &gt;= 20262
  ets_add_virtual_series(&amp;ret)
endif

return ret
</code>
</gretl-function>
<gretl-function name="ETS_gui" type="bundle" pkg-role="gui-main">
 <params count="8">
  <param name="y" type="series"/>
  <param name="model" type="int" min="1" max="4" default="2">
<labels count="4">
"simple exponential smoothing" "Holt (trend)" "Holt-Winters, additive" "Holt-Winters, multiplicative" </labels>
  </param>
  <param name="parms" type="int" min="1" max="2" default="1">
<labels count="2">
"automatic" "pre-specified" </labels>
  </param>
  <param name="alpha" type="scalar" min="0" max="1" default="0.5" step="0.0001">
<description>level, α</description>
  </param>
  <param name="beta" type="scalar" min="0" max="1" default="0.5" step="0.0001">
<description>trend, β</description>
  </param>
  <param name="gamma" type="scalar" min="0" max="1" default="0.05" step="0.0001">
<description>season, γ</description>
  </param>
  <param name="ntest" type="int" min="0" default="0">
<description>ntest, see Help</description>
  </param>
  <param name="verbose" type="int" min="1" max="3" default="1"/>
 </params>
<code>string yname = argname(y, &quot;y&quot;)
if yname != &quot;y&quot;
  rename y @yname
endif
# decode the model selector
trend = model &gt; 1
seasonal = model &gt; 2 ? model - 2 : 0
bundle ret
if parms == 1
  ret = ETS(@yname, _(trend, seasonal, ntest, verbose))
else
  matrix th3 = {alpha, beta, gamma}
  matrix theta = th3[1:1+trend+(seasonal&gt;0)]'
  ret = ETS(@yname, _(theta, trend, seasonal, ntest, verbose))
endif
return ret
</code>
</gretl-function>
<gretl-function name="ETS_plot" type="void" pkg-role="bundle-plot">
 <params count="3">
  <param name="b" type="bundleref" const="true"/>
  <param name="ptype" type="int" min="1" max="2" default="1">
<labels count="2">
"actual and forecast" "components" </labels>
  </param>
  <param name="ci_levels" type="matrix" optional="true"/>
 </params>
<code>list L = mat2list(b.S)
if ptype == 1
  string yname = b.yname
  if yname != &quot;y&quot;
    rename y @yname
  endif
  if b.limits[3] &gt; b.limits[2]
    scalar c1 c2 hmax
    ets_get_ci_vals(b, ci_levels, &amp;c1, &amp;c2, &amp;hmax)
    df = rows(b.S) - hmax - b.nparms
    if b.seasonal != 2
      hbands = ets_ci_bands(df, c1, c2)
    else
      # simulation-based bands
      matrix Y = ets_mult_sim(&amp;b, 10000, df)
      scalar T = rows(b.S)
      matrix B1 = ets_sim_band(Y, T, c1)
      matrix B2 = ets_sim_band(Y, T, c2)
      series ctr1 = B1[,1]
      series hw1 = B1[,2]
      series ctr2 = B2[,1]
      series hw2 = B2[,2]
      bundles hbands = array(2)
      hbands[1] = _(center=&quot;ctr2&quot;, width=&quot;hw2&quot;, style=&quot;fill&quot;, color=&quot;#BBD9F5&quot;)
      hbands[2] = _(center=&quot;ctr1&quot;, width=&quot;hw1&quot;, style=&quot;fill&quot;, color=&quot;#6AADEC&quot;)
    endif
    list P = @yname forecast
    plot P
      options --time-series --with-lines --bands=hbands
      printf &quot;set title 'forecast with %d and %d percent intervals'&quot;, c1, c2
    end plot --output=display
  else
    gnuplot @yname forecast --time --with-lines --output=display
  endif
else
  series irreg = b.S[,1] - b.S[,ets_fcol(b)]
  string cs = &quot;level&quot;
  if b.trend
    cs ~= &quot; trend&quot;
  endif
  if b.seasonal &gt; 0
    cs ~= &quot; seasonal&quot;
  endif
  tsplots @cs irreg --output=display
endif
</code>
</gretl-function>
<gretl-function name="ETS_ci_matrix" type="matrix">
 <params count="2">
  <param name="b" type="bundle" const="true"/>
  <param name="ci_levels" type="matrix" optional="true"/>
 </params>
<code>scalar hmax = b.limits[3] - b.limits[2]
errorif(hmax &lt;= 0, &quot;No post-training forecasts are available&quot;)
if exists(ci_levels)
  ets_check_ci_levels(ci_levels)
else
  ci_levels = {80, 95}
endif
scalar df = rows(b.S) - hmax - b.nparms
scalar ci1 = min(ci_levels)
scalar ci2 = max(ci_levels)
scalar T = rows(b.S)
scalar fcol = ets_fcol(b)
matrix fc = b.S[T-hmax+1:T,fcol]
matrix CI = zeros(hmax, 4)
if b.seasonal == 2
  # simulation-based intervals
  matrix Y = ets_mult_sim(&amp;b, 10000, df)
  matrix B1 = ets_sim_band(Y, T, ci1)
  matrix B2 = ets_sim_band(Y, T, ci2)
  CI[,1] = B1[T-hmax+1:T,1] - B1[T-hmax+1:T,2]
  CI[,2] = B1[T-hmax+1:T,1] + B1[T-hmax+1:T,2]
  CI[,3] = B2[T-hmax+1:T,1] - B2[T-hmax+1:T,2]
  CI[,4] = B2[T-hmax+1:T,1] + B2[T-hmax+1:T,2]
else
  scalar f1 = critical(t, df, (100 - ci1) / 200)
  scalar f2 = critical(t, df, (100 - ci2) / 200)
  matrix me = b.S[T-hmax+1:T,fcol+1]
  CI[,1] = fc - f1 * me
  CI[,2] = fc + f1 * me
  CI[,3] = fc - f2 * me
  CI[,4] = fc + f2 * me
endif
strings cs = array(4)
cs[1] = sprintf(&quot;Lo%d&quot;, ci1)
cs[2] = sprintf(&quot;Hi%d&quot;, ci1)
cs[3] = sprintf(&quot;Lo%d&quot;, ci2)
cs[4] = sprintf(&quot;Hi%d&quot;, ci2)
cnameset(CI, cs)
return CI
</code>
</gretl-function>
<gretl-function name="ets_nparms" type="scalar" private="1">
 <params count="4">
  <param name="td" type="int"/>
  <param name="m" type="int"/>
  <param name="fixed_theta" type="int"/>
  <param name="fixed_init" type="int"/>
 </params>
<code># number of estimated parameters: smoothing parameters plus free
# initial-state elements (seasonals are constrained to sum to zero
# or to average 1, hence m-1 of them). With fixed theta, a
# non-seasonal initializer is not estimated either.
scalar np = fixed_theta ? 0 : 1 + td + (m &gt; 0)
scalar ni = 1 + td + (m &gt; 0 ? m - 1 : 0)
if fixed_init || (fixed_theta &amp;&amp; m == 0)
  ni = 0
endif
return np + ni
</code>
</gretl-function>
<gretl-function name="ets_fcol" type="scalar" private="1">
 <params count="1">
  <param name="b" type="bundle" const="true"/>
 </params>
<code># column of the forecast in b.S
return 3 + b.trend + (b.seasonal &gt; 0)
</code>
</gretl-function>
<gretl-function name="ets_print_theta" type="void" private="1">
 <params count="1">
  <param name="theta" type="matrix" const="true"/>
 </params>
<code>strings gk = defarray(&quot;α&quot;, &quot;β&quot;, &quot;γ&quot;)
scalar n = nelem(theta)
printf &quot;  &quot;
loop i=1..n
  printf &quot;%s = %g&quot;, gk[i], theta[i]
  if i &lt; n
    printf &quot;, &quot;
  endif
endloop
printf &quot;\n&quot;
</code>
</gretl-function>
<gretl-function name="ets_print_init" type="void" private="1">
 <params count="3">
  <param name="init" type="matrix" const="true"/>
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
 </params>
<code>printf &quot;  level = %g&quot;, init[1]
scalar i = 2
if td
  printf &quot;, trend = %g&quot;, init[i++]
endif
printf &quot;\n&quot;
if sf &amp;&amp; $pd &gt; 4
  scalar n = nelem(init)
  printf &quot;  seasonals:\n&quot;
  loop j=i..n
    printf &quot;  %+f\n&quot;, init[i++]
  endloop
elif sf
  scalar n = nelem(init)
  printf &quot;  seasonals = {%g, &quot;, init[i++]
  loop j=i..n
    printf &quot;%g%s&quot;, init[i++], j &lt; n ? &quot;, &quot; : &quot;}&quot;
  endloop
  printf &quot;\n&quot;
endif
printf &quot;\n&quot;
</code>
</gretl-function>
<gretl-function name="ets_ranges" type="matrix" private="1">
 <params count="3">
  <param name="y" type="series" const="true"/>
  <param name="ntest" type="int"/>
  <param name="minlen" type="int"/>
 </params>
<code>matrix ret = zeros(1,3)
catch smpl --contiguous y
if $error
  funcerr &quot;Missing values encountered&quot;
endif
ret[1] = $t1 # start of contiguous y
ret[2] = $t2 # end of contiguous y
scalar tail = $tmax - $t2
if ntest &gt; 0
  ret[2] = $t2 - ntest
  ret[3] = $t2
elif tail &gt; 0
  ret[3] = $tmax
else
  ret[3] = $t2
endif
ntrain = ret[2] - ret[1] + 1
errorif(ntrain &lt; minlen, &quot;Insufficient observations for training&quot;)
return ret
</code>
</gretl-function>
<gretl-function name="ets_checks" type="void" private="1">
 <params count="3">
  <param name="args" type="bundle" const="true"/>
  <param name="fixed_theta" type="int"/>
  <param name="fixed_init" type="int"/>
 </params>
<code>scalar td = args.trend
scalar sf = args.seasonal
if sf &gt; 0
  errorif($pd == 1, &quot;Periodicity = 1, no seasonality!&quot;)
  errorif(td == 0, &quot;seasonal models require a trend&quot;)
endif
if fixed_theta
  scalar np = 1 + td + (sf &gt; 0)
  errorif(nelem(args.theta) != np, sprintf(&quot;theta must be a %d-vector&quot;, np))
  loop i=1..np
    errorif(args.theta[i] &lt; 0 || args.theta[i] &gt; 1, sprintf(&quot;theta[%d] is out of bounds&quot;, i))
  endloop
endif
if fixed_init
  scalar ni = 1 + td + (sf &gt; 0 ? $pd : 0)
  errorif(nelem(args.init) != ni, sprintf(&quot;init must be a %d-vector&quot;, ni))
  errorif(sumc(ok(vec(args.init))) &lt; ni, &quot;init contains missing values&quot;)
  if sf == 2
    matrix iv = vec(args.init)
    errorif(iv[1] &lt;= 0 || minc(iv[3:ni]) &lt;= 0, &quot;multiplicative model: level and seasonals must be positive&quot;)
  endif
endif
</code>
</gretl-function>
<gretl-function name="ets_model_name" type="string" private="1">
 <params count="2">
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
 </params>
<code>if sf == 0
  return td ? &quot;Holt double exponential smoothing&quot; : &quot;Simple exponential smoothing&quot;
else
  return sprintf(&quot;Holt-Winters, using %s formula&quot;, sf == 1 ? &quot;additive&quot; : &quot;multiplicative&quot;)
endif
</code>
</gretl-function>
<gretl-function name="ets_auto_init" type="matrix" private="1">
 <params count="3">
  <param name="y" type="series">
<description>Target series</description>
  </param>
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
 </params>
<code># Rule-of-thumb initial state, for period 0. Non-seasonal: initial
# level is the first observation, initial trend the first
# difference. Seasonal: first-period average, trend from the
# first two major periods, and seasonal indices relative to the
# first-period average.
if sf == 0
  matrix ret = zeros(1 + td, 1)
  ret[1] = y[$t1]
  if td
    ret[2] = y[$t1+1] - y[$t1]
    rnameset(ret, &quot;level trend&quot;)
  else
    rnameset(ret, &quot;level&quot;)
  endif
  return ret
endif

scalar m = $pd
scalar t2 = $t1+m-1
scalar am1 = 0
scalar am2 = 0
matrix ret = zeros(m + 2, 1)

# sums of y over the first two major periods
loop t=$t1..t2
  am1 += y[t]
  am2 += y[t + m]
endloop

ret[1] = am1 / m
ret[2] = (am2 - am1) / (m * m)

# seasonal indices
i = 3
loop t=$t1..t2
  if sf == 1
    ret[i] = y[t] - ret[1]
  else
    ret[i] = y[t] / ret[1]
  endif
  i++
endloop

strings S = array(2 + m)
S[1] = &quot;level&quot;
S[2] = &quot;trend&quot;
loop i=1..m
  S[i+2] = sprintf(&quot;seas%d&quot;, i)
endloop
rnameset(ret, S)

return ret
</code>
</gretl-function>
<gretl-function name="ets_A" type="matrix" private="1">
 <params count="3">
  <param name="theta" type="matrix" const="true"/>
  <param name="td" type="int"/>
  <param name="m" type="int"/>
 </params>
<code>scalar α = theta[1]
scalar β = td ? theta[2] : 0
scalar γ = m &gt; 0 ? theta[2+td] : 0
scalar n = 1 + td + m
matrix A = zeros(n, n)
A[1,1] = 1-α
if td
  A[1,2] = 1-α
  A[2,1] = -α*β
  A[2,2] = 1-α*β
endif
if m &gt; 0
  A[1,n] = -α
  if td
    A[2,n] = -α*β
  endif
  scalar sr = 2 + td # row of the newest seasonal
  A[sr,1] = -γ*(1-α)
  if td
    A[sr,2] = -γ*(1-α)
  endif
  A[sr,n] = 1-γ*(1-α)
  loop j=sr+1..n
    # shift the older seasonals down one slot
    A[j,j-1] = 1
  endloop
endif
return A
</code>
</gretl-function>
<gretl-function name="ets_d" type="matrix" private="1">
 <params count="3">
  <param name="theta" type="matrix" const="true"/>
  <param name="td" type="int"/>
  <param name="m" type="int"/>
 </params>
<code># loading of y_t on the state (row vector)
scalar α = theta[1]
matrix d = {α}
if td
  d ~= α * theta[2]
endif
if m &gt; 0
  d ~= theta[2+td] * (1-α) ~ zeros(1, m-1)
endif
return d
</code>
</gretl-function>
<gretl-function name="ets_c" type="matrix" private="1">
 <params count="2">
  <param name="td" type="int"/>
  <param name="m" type="int"/>
 </params>
<code># forecast selection vector
matrix c = ones(1 + td, 1)
if m &gt; 0
  c |= zeros(m-1, 1) | 1
endif
return c
</code>
</gretl-function>
<gretl-function name="ets_filter_add" type="matrix" private="1">
 <params count="5">
  <param name="theta" type="matrix" const="true"/>
  <param name="init" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="td" type="int"/>
  <param name="m" type="int"/>
 </params>
<code># returns T x (3 + td + (m&gt;0)) matrix
#   {y, level, [trend], [seasonal], forecast}
scalar T = rows(my)
scalar k = 1 + td
# state order is newest seasonal first; @init is oldest first
matrix z0 = init[1:k]
if m &gt; 0
  z0 |= mreverse(init[k+1:k+m])
endif
matrix Z = varsimul(ets_A(theta, td, m), my * ets_d(theta, td, m), z0')
matrix ret = my ~ Z[2:T+1,1:k]
if m &gt; 0
  ret ~= Z[2:T+1,k+1]
endif
ret ~= Z[1:T,] * ets_c(td, m)
return ret
</code>
</gretl-function>
<gretl-function name="ets_crit_add" type="scalar" private="1">
 <params count="5">
  <param name="theta" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="init" type="matrixref"/>
  <param name="td" type="int"/>
  <param name="m" type="int"/>
 </params>
<code># BFGS callback, additive case: SSE given @theta, with the initial
# state concentrated out. Forecasts are linear in the initial
# state, f = f0 + G z0, where f0 is the forecast path from a zero
# state and row t of G is c'A^(t-1). Note that G can be had from
# one varsimul() call with transition A' and initial value c.
#
# If seasonal, the seasonals are constrained to sum to zero: the
# initial level and the seasonal mean are not separately identified
# (adding k to the level and subtracting k from every seasonal
# leaves all forecasts unchanged), so without this the regressors
# would be collinear.
scalar T = rows(my)
scalar k = 1 + td
scalar n = k + m
matrix A = ets_A(theta, td, m)
matrix c = ets_c(td, m)
matrix Z = varsimul(A, my * ets_d(theta, td, m), zeros(1, n))
matrix f0 = Z[1:T,] * c
matrix G = varsimul(A', zeros(T-1, n), c')
matrix e, q
if m &gt; 0
  # put seasonal columns in @init order (oldest first)
  matrix X = G[,1:k] ~ mreverse(G[,k+1:n]')'
  # impose seas_m = -(seas_1 + ... + seas_{m-1})
  matrix Xs = X[,1:n-1] - X[,n] * (zeros(1, k) ~ ones(1, m-1))
  q = mols(my - f0, Xs, &amp;e)
  init[1:n-1] = q
  init[n] = -sumc(q[k+1:n-1])
else
  q = mols(my - f0, G, &amp;e)
  init[1:k] = q
endif
return sumc(e.^2)
</code>
</gretl-function>
<gretl-function name="ets_filter_mult" type="matrix" private="1">
 <params count="3">
  <param name="theta" type="matrix" const="true"/>
  <param name="init" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
 </params>
<code># returns T x 5 matrix {y, level, trend, seasonal, forecast}
scalar α = theta[1]
scalar β = theta[2]
scalar γ = theta[3]
scalar m = nelem(init) - 2
scalar T = rows(my)
# s[t] holds s_{t-m}; s[t+m] receives s_t
matrix s = init[3:m+2] | zeros(T, 1)
matrix ret = my ~ zeros(T, 4)
scalar a_ = init[1]
scalar b_ = init[2]
scalar a = 0
scalar b = 0
scalar sm = 0
loop t=1..T
  sm = s[t]
  ret[t,5] = (a_ + b_) * sm
  a = α * my[t] / sm + (1-α) * (a_ + b_)
  b = β * (a - a_) + (1-β) * b_
  s[t+m] = γ * my[t] / a + (1-γ) * sm
  ret[t,2] = a
  ret[t,3] = b
  a_ = a
  b_ = b
endloop
ret[,4] = s[m+1:m+T]
return ret
</code>
</gretl-function>
<gretl-function name="ets_mult_init" type="matrix" private="1">
 <params count="2">
  <param name="par" type="matrix" const="true"/>
  <param name="m" type="int"/>
 </params>
<code># map (α, β, γ, level, trend, seas_1..seas_{m-1}) to the full
# initializer, with seasonals normalized to average 1: scaling
# level and trend by k and the seasonals by 1/k leaves all
# forecasts unchanged, so one seasonal is redundant
matrix sv = par[6:m+4]
return par[4:5] | sv | (m - sumc(sv))
</code>
</gretl-function>
<gretl-function name="ets_crit_mult" type="scalar" private="1">
 <params count="3">
  <param name="par" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="m" type="int"/>
 </params>
<code># BFGS callback, multiplicative case
matrix W = ets_filter_mult(par[1:3], ets_mult_init(par, m), my)
return sumc((W[,1] - W[,5]).^2)
</code>
</gretl-function>
<gretl-function name="ets_filter" type="matrix" private="1">
 <params count="6">
  <param name="theta" type="matrix" const="true"/>
  <param name="init" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
  <param name="m" type="int"/>
 </params>
<code>if sf == 2
  return ets_filter_mult(theta, init, my)
else
  return ets_filter_add(theta, init, my, td, m)
endif
</code>
</gretl-function>
<gretl-function name="ets_crit_add_scaled" type="scalar" private="1">
 <params count="6">
  <param name="theta" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="init" type="matrixref"/>
  <param name="td" type="int"/>
  <param name="m" type="int"/>
  <param name="f0" type="scalar"/>
 </params>
<code>return ets_crit_add(theta, my, &amp;init, td, m) / f0
</code>
</gretl-function>
<gretl-function name="ets_crit_fixed" type="scalar" private="1">
 <params count="6">
  <param name="theta" type="matrix" const="true"/>
  <param name="init" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
  <param name="m" type="int"/>
 </params>
<code># criterion (SSE) given both @theta and @init
matrix W = ets_filter(theta, init, my, td, sf, m)
return sumc((W[,1] - W[,cols(W)]).^2)
</code>
</gretl-function>
<gretl-function name="ets_crit_fixed_scaled" type="scalar" private="1">
 <params count="7">
  <param name="theta" type="matrix" const="true"/>
  <param name="init" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
  <param name="m" type="int"/>
  <param name="f0" type="scalar"/>
 </params>
<code>return ets_crit_fixed(theta, init, my, td, sf, m) / f0
</code>
</gretl-function>
<gretl-function name="ets_crit_mult_scaled" type="scalar" private="1">
 <params count="4">
  <param name="par" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="m" type="int"/>
  <param name="f0" type="scalar"/>
 </params>
<code>return ets_crit_mult(par, my, m) / f0
</code>
</gretl-function>
<gretl-function name="ets_start_values" type="matrix" private="1">
 <params count="7">
  <param name="my" type="matrix" const="true"/>
  <param name="init" type="matrix" const="true"/>
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
  <param name="m" type="int"/>
  <param name="fixed_init" type="int"/>
  <param name="f0" type="scalarref"/>
 </params>
<code># Starting values for the smoothing parameters, by coarse grid
# search over the criterion. The criterion can be quite irregular
# (and the first L-BFGS-B step is a full gradient step, which for
# a gradient of large magnitude can throw us onto a corner of the
# box from which we cannot escape), so a fixed start such as
# (0.5, 0.5) is not safe. On return @f0 holds the criterion value
# at the chosen point, for use in scaling the criterion.
scalar np = 1 + td + (m &gt; 0)
matrix P = {0.05; 0.1; 0.2; 0.4; 0.6; 0.8}
if np &gt; 1
  matrix bg = {0.01; 0.05; 0.1; 0.3; 0.6}
  P = (P ** ones(rows(bg), 1)) ~ (ones(rows(P), 1) ** bg)
endif
if np &gt; 2
  matrix gg = {0.01; 0.1; 0.3}
  P = (P ** ones(rows(gg), 1)) ~ (ones(rows(P), 1) ** gg)
endif
scalar n = rows(P)
matrix fv = zeros(n, 1)
matrix tmp = init
loop i=1..n
  if fixed_init
    fv[i] = ets_crit_fixed(P[i,]', init, my, td, sf, m)
  elif sf == 2
    fv[i] = ets_crit_mult(P[i,]' | init[1:m+1], my, m)
  else
    fv[i] = ets_crit_add(P[i,]', my, &amp;tmp, td, m)
  endif
endloop
scalar imin = iminr(fv')
f0 = fv[imin]
return P[imin,]'
</code>
</gretl-function>
<gretl-function name="ets_auto_parms" type="matrix" private="1">
 <params count="7">
  <param name="my" type="matrix" const="true"/>
  <param name="init" type="matrixref"/>
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
  <param name="m" type="int"/>
  <param name="verbose" type="int"/>
  <param name="fixed_init" type="int"/>
 </params>
<code># driver for automatic estimation of parameters; if @fixed_init
# is non-zero, @init is held constant and only theta is estimated
scalar np = 1 + td + (m &gt; 0)
scalar f0
matrix b = ets_start_values(my, init, td, sf, m, fixed_init, &amp;f0)
if sf == 2 &amp;&amp; !fixed_init
  # the additive cases solve for the initializer internally
  b |= init[1:m+1]
endif
scalar len = nelem(b)
matrix bounds = zeros(len, 3)
loop i=1..len
  if i &lt;= np
    bounds[i,] = {i, 0, 1}
  else
    bounds[i,] = {i, -$huge, $huge}
  endif
endloop

if verbose == 3
  set max_verbose full
elif verbose == 2
  set max_verbose on
  set bfgs_verbskip 5
endif
if fixed_init
  catch scalar SSE = BFGScmin(&amp;b, bounds, ets_crit_fixed_scaled(b, init, my, td, sf, m, f0))
elif sf == 2
  catch scalar SSE = BFGScmin(&amp;b, bounds, ets_crit_mult_scaled(b, my, m, f0))
else
  catch scalar SSE = BFGScmin(&amp;b, bounds, ets_crit_add_scaled(b, my, &amp;init, td, m, f0))
endif
err = $error
if err
  printf &quot;Got error %d from BFGScmin\n&quot;, err
  b[1] = NA
else
  # make sure @init corresponds to the final parameters
  if fixed_init
    # nothing to do
  elif sf == 2
    init[1:m+2] = ets_mult_init(b, m)
  else
    SSE = ets_crit_add(b, my, &amp;init, td, m)
  endif
  if verbose &gt; 0
    if verbose &gt; 1
      printf &quot;\n&quot;
    endif
    printf &quot;estimated parameter values:\n&quot;
    ets_print_theta(b[1:np])
    if !fixed_init
      printf &quot;\nestimated initializer:\n&quot;
      ets_print_init(init, td, sf)
    endif
  endif
endif
return b[1:np]
</code>
</gretl-function>
<gretl-function name="ets_given_theta" type="scalar" private="1">
 <params count="7">
  <param name="theta" type="matrix" const="true"/>
  <param name="my" type="matrix" const="true"/>
  <param name="init" type="matrixref"/>
  <param name="td" type="int"/>
  <param name="sf" type="int"/>
  <param name="m" type="int"/>
  <param name="verbose" type="int"/>
 </params>
<code># optimize the (seasonal) initializer conditional on fixed @theta
if sf == 2
  matrix b = init[1:m+1]
  if verbose == 3
    set max_verbose full
  elif verbose == 2
    set max_verbose on
    set bfgs_verbskip 5
  endif
  catch scalar SSE = BFGSmin(&amp;b, ets_crit_mult(theta | b, my, m))
  err = $error
  if err
    printf &quot;Got error %d from BFGSmin\n&quot;, err
    return err
  endif
  init[1:m+2] = ets_mult_init(theta | b, m)
else
  # the initializer is concentrated out by OLS
  ets_crit_add(theta, my, &amp;init, td, m)
endif
if verbose &gt; 0
  printf &quot;estimated initializer, given θ:\n&quot;
  ets_print_init(init, td, sf)
endif
return 0
</code>
</gretl-function>
<gretl-function name="ets_add_virtual_series" type="void" private="1">
 <params count="1">
  <param name="b" type="bundleref"/>
 </params>
<code>scalar td = b.trend
scalar sf = b.seasonal
scalar fc = ets_fcol(b)
string s = sprintf(&quot;%s_level&quot;, b.yname)
b[s] = &quot;virtual_series:S[,2]&quot;
if td
  s = sprintf(&quot;%s_trend&quot;, b.yname)
  b[s] = &quot;virtual_series:S[,3]&quot;
endif
if sf &gt; 0
  s = sprintf(&quot;%s_season&quot;, b.yname)
  b[s] = sprintf(&quot;virtual_series:S[,%d]&quot;, 3 + td)
endif
s = sprintf(&quot;%s_fcast&quot;, b.yname)
b[s] = sprintf(&quot;virtual_series:S[,%d]&quot;, fc)
s = sprintf(&quot;%s_irreg&quot;, b.yname)
b[s] = sprintf(&quot;virtual_series:S[,1]-S[,%d]&quot;, fc)
</code>
</gretl-function>
<gretl-function name="ets_add_eval_stats" type="void" private="1">
 <params count="1">
  <param name="b" type="bundleref"/>
 </params>
<code>scalar T = rows(b.S)
scalar t0 = T - b.ntest + 1
scalar fc = ets_fcol(b)
matrix e = b.S[t0:T,1] - b.S[t0:T,fc]
b.RMSE_test = sqrt(sumc(e.^2) / b.ntest)
b.MAE_test = sumc(abs(e)) / b.ntest
</code>
</gretl-function>
<gretl-function name="ets_add_fcse" type="void" private="1">
 <params count="1">
  <param name="b" type="bundleref"/>
 </params>
<code># Forecast standard errors for the additive models (SES, Holt and
# additive Holt-Winters), via the equivalent ETS(A,*,*) model: see
# https://otexts.com/fpp3/ets-forecasting.html, section 8.7 and
# Table 8.8. The h-step variance is
#   σ² [1 + Σ_{j=1}^{h-1} c_j²],
# where c_j = α + β_e j + γ_e I(j mod m = 0). The ETS smoothing
# parameters are related to ours by β_e = αβ and γ_e = (1-α)γ (see
# fpp3 sections 8.3 and 8.5).
scalar td = b.trend
scalar m = b.seasonal &gt; 0 ? rows(b.init) - 2 : 0
scalar α = b.theta[1]
scalar β = td ? α * b.theta[2] : 0
scalar γ = m &gt; 0 ? (1-α) * b.theta[3] : 0
scalar s2 = b.RMSE_train^2
scalar hmax = b.limits[3] - b.limits[2]
matrix j = seq(1, hmax-1)'
matrix cj = α + β * j
if m &gt; 0
  cj += γ * ((j - m * floor(j/m)) .= 0)
endif
matrix vh = ones(rows(b.S), 1) * NA
scalar T = rows(vh)
matrix cs = hmax &gt; 1 ? 0 | cum(cj.^2) : 0
vh[T-hmax+1:T] = s2 * (1 + cs)
strings ss = cnameget(b.S)
b.S ~= sqrt(vh)
cnameset(b.S, ss + &quot;stderr&quot;)
</code>
</gretl-function>
<gretl-function name="ets_mult_sim" type="matrix" private="1">
 <params count="3">
  <param name="b" type="bundleref" const="true"/>
  <param name="N" type="int"/>
  <param name="df" type="int"/>
 </params>
<code># Simulated paths of y over the forecast range, multiplicative case,
# for which there's no closed form for the forecast variance. Each
# path runs the recursion forward from the final training state,
# feeding in y = forecast + error, with the error drawn as
# RMSE_train times Student t(df), so the interval at h = 1 matches
# the analytic additive one. Returns an N x H matrix.
scalar m = nelem(b.init) - 2
scalar H = b.limits[3] - b.limits[2]
scalar n = rows(b.S) - H
scalar α = b.theta[1]
scalar β = b.theta[2]
scalar γ = b.theta[3]
matrix E = b.RMSE_train * mrandgen(t, df, N, H)
matrix ι = ones(N, 1)
matrix a_ = ι * b.S[n,2]
matrix b_ = ι * b.S[n,3]
# S[,h] holds s_{n+h-m}; S[,h+m] receives s_{n+h}
matrix S = (ι * b.S[n-m+1:n,4]') ~ zeros(N, H)
matrix Y = zeros(N, H)
matrix a y sm
loop h=1..H
  sm = S[,h]
  y = (a_ + b_) .* sm + E[,h]
  a = α * y ./ sm + (1-α) * (a_ + b_)
  b_ = β * (a - a_) + (1-β) * b_
  S[,h+m] = γ * y ./ a + (1-γ) * sm
  a_ = a
  Y[,h] = y
endloop
return Y
</code>
</gretl-function>
<gretl-function name="ets_sim_band" type="matrix" private="1">
 <params count="3">
  <param name="Y" type="matrix" const="true"/>
  <param name="T" type="int"/>
  <param name="c" type="scalar"/>
 </params>
<code># For a c percent interval from the empirical quantiles of the
# simulated paths @Y, return a T x 2 matrix holding the center and
# half-width of the band, NA outside the forecast range (the last
# cols(Y) of T rows). The band need not be symmetric about the
# forecast, hence the representation via its midpoint.
scalar H = cols(Y)
scalar p = (100 - c) / 200
matrix Q = quantile(Y, {p, 1-p})
matrix ret = mshape(NA, T, 2)
ret[T-H+1:T,1] = (Q[1,] + Q[2,])' / 2
ret[T-H+1:T,2] = (Q[2,] - Q[1,])' / 2
return ret
</code>
</gretl-function>
<gretl-function name="ets_check_ci_levels" type="void" private="1">
 <params count="1">
  <param name="pc" type="matrix"/>
 </params>
<code>scalar err = 0
if nelem(pc) != 2
  err = 1
elif min(pc) &lt; 50 || max(pc) &gt; 99
  err = 2
elif pc[1] == pc[2]
  err = 3
endif
errorif(err, &quot;ci_levels: invalid argument&quot;)
</code>
</gretl-function>
<gretl-function name="ets_get_ci_vals" type="void" private="1">
 <params count="5">
  <param name="b" type="bundle" const="true"/>
  <param name="ci_levels" type="matrix" optional="true"/>
  <param name="c1" type="scalarref"/>
  <param name="c2" type="scalarref"/>
  <param name="hmax" type="scalarref"/>
 </params>
<code>if exists(ci_levels)
  ets_check_ci_levels(ci_levels)
else
  ci_levels = {80, 95}
endif
hmax = b.limits[3] - b.limits[2]
c1 = min(ci_levels)
c2 = max(ci_levels)
</code>
</gretl-function>
<gretl-function name="ets_ci_bands" type="bundles" private="1">
 <params count="3">
  <param name="df" type="int"/>
  <param name="c1" type="scalar"/>
  <param name="c2" type="scalar"/>
 </params>
<code>bundles bb = array(2)
# for plotting, show the wider band first
f1 = critical(t, df, (100 - c2) / 200)
f2 = critical(t, df, (100 - c1) / 200)
bb[1] = _(center=&quot;forecast&quot;, width=&quot;stderr&quot;, factor=f1, style=&quot;fill&quot;, color=&quot;#BBD9F5&quot;)
bb[2] = _(center=&quot;forecast&quot;, width=&quot;stderr&quot;, factor=f2, style=&quot;fill&quot;, color=&quot;#6AADEC&quot;)
return bb
</code>
</gretl-function>
<gretl-function name="ets_default_args" type="bundle" private="1">
<code>bundle b
b.trend = 0
b.seasonal = 0
b.theta = {0.5}' # placeholder, length checked only if supplied
b.init = {1.0}   # ditto
b.ntest = 0
b.verbose = 1
b.bounds = _(trend={0,1}, seasonal={0,2}, verbose={0,3}, ntest={0,NA})
return b
</code>
</gretl-function>
<gretl-function name="ets_precheck" type="scalar" private="1" pkg-role="gui-precheck">
<code># return non-zero if dataset is not suitable: we require
# sufficient observations (seasonal models need more, and a
# periodicity &gt; 1, which is checked at run time)
return $nobs &lt; 10
</code>
</gretl-function>
<gretl-function name="ets_ui_maker" type="bundle" private="1" pkg-role="ui-maker">
<code>bundle b
b.alpha = _(depends=&quot;parms=2&quot;)
b.beta  = _(depends=&quot;parms=2&quot;)
b.gamma = _(depends=&quot;parms=2&quot;)
b.model = _(default=&quot;$pd&gt;1 &amp;&amp; $pd!=52 ? 4 : 2&quot;, maximum=&quot;$pd&gt;1 &amp;&amp; $pd&lt;52 ? 4 : 2&quot;)
return b
</code>
</gretl-function>
<sample-script>
include ETS.gfn

# simple exponential smoothing: no trend, no seasonality
open alg_exports.gdt --frompkg=ETS
dataset addobs 5
bundle sb = ETS(Exports)
ETS_plot(&amp;sb, 1)

# Holt: linear trend
open auspop.gdt --frompkg=ETS
dataset addobs 10
bundle hb = ETS(pop, _(trend=1))
ETS_plot(&amp;hb, 1)
ETS_plot(&amp;hb, 2)

# Holt-Winters, with 12 out-of-sample forecasts
open aus_holidays.gdt --frompkg=ETS
dataset addobs 12
bundle wb = ETS(Trips, _(seasonal=1))
ETS_plot(&amp;wb, 1)
ETS_plot(&amp;wb, 2)
</sample-script>
</gretl-function-package>
</gretl-functions>
