Repository: statsmodels/statsmodels

Issue:
Holt-Winters/SES AICc formula ???
aicc = aic + (2 * (k + 2) * (k + 3)) / (self.nobs - k - 3)  #line 735 @holtwinters.py

VS 

return -2. * llf + 2. * df_modelwc * nobs / (nobs - df_modelwc - 1.) #from eval measures


Discussion:
I meet the same problems. the formula of aicc leads to the "ZeroDivisionError: division by zero " error.
I looked a bit at the definition
The aicc definition for holt-winters is in one of the writings by Hyndman, but I didn't see a reference that justifies it.
The `eval_measures` definition is a reformulation of the standard aicc formula e.g. as shown in Wikipedia.

If `(self.nobs - k - 3)` is not > zero, then the degrees of freedom is not large enough to get a meaningful aicc in this definition. I haven't checked how `k` is determined in this case. Hyndman just mentions that it includes all parameters, which would mean that the model is overparameterized to get reliable estimates of the parameters, i.e. not enough degrees of freedom.


Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -369,8 +369,11 @@ def run(self):
              "sources" : []},
     _smoothers_lowess = {"name" : "statsmodels/nonparametric/_smoothers_lowess.c",
              "depends" : [],
-             "sources" : []}
-    )
+             "sources" : []},
+    _exponential_smoothers = {"name": "statsmodels/tsa/_exponential_smoothers.c",
+                              "depends": [],
+                              "sources": []}
+)
 
 statespace_ext_data = dict(
     _initialization = {"name" : "statsmodels/tsa/statespace/_initialization.c",
diff --git a/statsmodels/tsa/holtwinters.py b/statsmodels/tsa/holtwinters.py
--- a/statsmodels/tsa/holtwinters.py
+++ b/statsmodels/tsa/holtwinters.py
@@ -1,6 +1,4 @@
 """
-Created on Wed Jul 12 09:35:35 2017
-
 Notes
 -----
 Code written using below textbook as a reference.
@@ -10,27 +8,26 @@
 Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice. OTexts, 2014.
 
 Author: Terence L van Zyl
-
+Modified: Kevin Sheppard
 """
-import numpy as np
-
-from statsmodels.base.model import Results
-from statsmodels.base.wrapper import populate_wrapper, union_dicts, ResultsWrapper
-from statsmodels.tsa.base.tsa_model import TimeSeriesModel
+from statsmodels.compat.python import string_types
 
+import numpy as np
+import pandas as pd
 from scipy.optimize import basinhopping, brute, minimize
 from scipy.spatial.distance import sqeuclidean
-try:
-    from scipy.special import inv_boxcox
-except ImportError:
-    def inv_boxcox(x, lmbda):
-        return np.exp(np.log1p(lmbda * x) / lmbda) if lmbda != 0 else np.exp(x)
+from scipy.special import inv_boxcox
 from scipy.stats import boxcox
 
+from statsmodels.base.model import Results
+from statsmodels.base.wrapper import populate_wrapper, union_dicts, ResultsWrapper
+from statsmodels.tsa.base.tsa_model import TimeSeriesModel
+from statsmodels.tsa.tsatools import freq_to_period
+import statsmodels.tsa._exponential_smoothers as smoothers
 
 def _holt_init(x, xi, p, y, l, b):
     """Initialization for the Holt Models"""
-    p[xi] = x
+    p[xi.astype(np.bool)] = x
     alpha, beta, _, l0, b0, phi = p[:6]
     alphac = 1 - alpha
     betac = 1 - beta
@@ -56,7 +53,7 @@ def _holt__(x, xi, p, y, l, b, s, m, n, max_seen):
 
 def _holt_mul_dam(x, xi, p, y, l, b, s, m, n, max_seen):
     """
-    Multiplicative and Multiplicative Damped 
+    Multiplicative and Multiplicative Damped
     Minimization Function
     (M,) & (Md,)
     """
@@ -73,7 +70,7 @@ def _holt_mul_dam(x, xi, p, y, l, b, s, m, n, max_seen):
 
 def _holt_add_dam(x, xi, p, y, l, b, s, m, n, max_seen):
     """
-    Additive and Additive Damped 
+    Additive and Additive Damped
     Minimization Function
     (A,) & (Ad,)
     """
@@ -90,7 +87,7 @@ def _holt_add_dam(x, xi, p, y, l, b, s, m, n, max_seen):
 
 def _holt_win_init(x, xi, p, y, l, b, s, m):
     """Initialization for the Holt Winters Seasonal Models"""
-    p[xi] = x
+    p[xi.astype(np.bool)] = x
     alpha, beta, gamma, l0, b0, phi = p[:6]
     s0 = p[6:]
     alphac = 1 - alpha
@@ -109,7 +106,7 @@ def _holt_win_init(x, xi, p, y, l, b, s, m):
 
 def _holt_win__mul(x, xi, p, y, l, b, s, m, n, max_seen):
     """
-    Multiplicative Seasonal 
+    Multiplicative Seasonal
     Minimization Function
     (,M)
     """
@@ -127,7 +124,7 @@ def _holt_win__mul(x, xi, p, y, l, b, s, m, n, max_seen):
 
 def _holt_win__add(x, xi, p, y, l, b, s, m, n, max_seen):
     """
-    Additive Seasonal 
+    Additive Seasonal
     Minimization Function
     (,A)
     """
@@ -139,14 +136,13 @@ def _holt_win__add(x, xi, p, y, l, b, s, m, n, max_seen):
         return max_seen
     for i in range(1, n):
         l[i] = (y_alpha[i - 1]) - (alpha * s[i - 1]) + (alphac * (l[i - 1]))
-        s[i + m - 1] = y_gamma[i - 1] - \
-            (gamma * (l[i - 1])) + (gammac * s[i - 1])
+        s[i + m - 1] = y_gamma[i - 1] - (gamma * (l[i - 1])) + (gammac * s[i - 1])
     return sqeuclidean(l + s[:-(m - 1)], y)
 
 
 def _holt_win_add_mul_dam(x, xi, p, y, l, b, s, m, n, max_seen):
     """
-    Additive and Additive Damped with Multiplicative Seasonal 
+    Additive and Additive Damped with Multiplicative Seasonal
     Minimization Function
     (A,M) & (Ad,M)
     """
@@ -158,7 +154,7 @@ def _holt_win_add_mul_dam(x, xi, p, y, l, b, s, m, n, max_seen):
         return max_seen
     for i in range(1, n):
         l[i] = (y_alpha[i - 1] / s[i - 1]) + \
-            (alphac * (l[i - 1] + phi * b[i - 1]))
+               (alphac * (l[i - 1] + phi * b[i - 1]))
         b[i] = (beta * (l[i] - l[i - 1])) + (betac * phi * b[i - 1])
         s[i + m - 1] = (y_gamma[i - 1] / (l[i - 1] + phi *
                                           b[i - 1])) + (gammac * s[i - 1])
@@ -167,7 +163,7 @@ def _holt_win_add_mul_dam(x, xi, p, y, l, b, s, m, n, max_seen):
 
 def _holt_win_mul_mul_dam(x, xi, p, y, l, b, s, m, n, max_seen):
     """
-    Multiplicative and Multiplicative Damped with Multiplicative Seasonal 
+    Multiplicative and Multiplicative Damped with Multiplicative Seasonal
     Minimization Function
     (M,M) & (Md,M)
     """
@@ -179,7 +175,7 @@ def _holt_win_mul_mul_dam(x, xi, p, y, l, b, s, m, n, max_seen):
         return max_seen
     for i in range(1, n):
         l[i] = (y_alpha[i - 1] / s[i - 1]) + \
-            (alphac * (l[i - 1] * b[i - 1]**phi))
+               (alphac * (l[i - 1] * b[i - 1]**phi))
         b[i] = (beta * (l[i] / l[i - 1])) + (betac * b[i - 1]**phi)
         s[i + m - 1] = (y_gamma[i - 1] / (l[i - 1] *
                                           b[i - 1]**phi)) + (gammac * s[i - 1])
@@ -188,7 +184,7 @@ def _holt_win_mul_mul_dam(x, xi, p, y, l, b, s, m, n, max_seen):
 
 def _holt_win_add_add_dam(x, xi, p, y, l, b, s, m, n, max_seen):
     """
-    Additive and Additive Damped with Additive Seasonal 
+    Additive and Additive Damped with Additive Seasonal
     Minimization Function
     (A,A) & (Ad,A)
     """
@@ -200,16 +196,15 @@ def _holt_win_add_add_dam(x, xi, p, y, l, b, s, m, n, max_seen):
         return max_seen
     for i in range(1, n):
         l[i] = (y_alpha[i - 1]) - (alpha * s[i - 1]) + \
-            (alphac * (l[i - 1] + phi * b[i - 1]))
+               (alphac * (l[i - 1] + phi * b[i - 1]))
         b[i] = (beta * (l[i] - l[i - 1])) + (betac * phi * b[i - 1])
-        s[i + m - 1] = y_gamma[i - 1] - \
-            (gamma * (l[i - 1] + phi * b[i - 1])) + (gammac * s[i - 1])
+        s[i + m - 1] = y_gamma[i - 1] - (gamma * (l[i - 1] + phi * b[i - 1])) + (gammac * s[i - 1])
     return sqeuclidean((l + phi * b) + s[:-(m - 1)], y)
 
 
 def _holt_win_mul_add_dam(x, xi, p, y, l, b, s, m, n, max_seen):
     """
-    Multiplicative and Multiplicative Damped with Additive Seasonal 
+    Multiplicative and Multiplicative Damped with Additive Seasonal
     Minimization Function
     (M,A) & (M,Ad)
     """
@@ -221,13 +216,34 @@ def _holt_win_mul_add_dam(x, xi, p, y, l, b, s, m, n, max_seen):
         return max_seen
     for i in range(1, n):
         l[i] = (y_alpha[i - 1]) - (alpha * s[i - 1]) + \
-            (alphac * (l[i - 1] * b[i - 1]**phi))
+               (alphac * (l[i - 1] * b[i - 1]**phi))
         b[i] = (beta * (l[i] / l[i - 1])) + (betac * b[i - 1]**phi)
         s[i + m - 1] = y_gamma[i - 1] - \
             (gamma * (l[i - 1] * b[i - 1]**phi)) + (gammac * s[i - 1])
     return sqeuclidean((l * phi * b) + s[:-(m - 1)], y)
 
 
+SMOOTHERS = {('mul', 'add'): smoothers._holt_win_add_mul_dam,
+             ('mul', 'mul'): smoothers._holt_win_mul_mul_dam,
+             ('mul', None): smoothers._holt_win__mul,
+             ('add', 'add'): smoothers._holt_win_add_add_dam,
+             ('add', 'mul'): smoothers._holt_win_mul_add_dam,
+             ('add', None): smoothers._holt_win__add,
+             (None, 'add'): smoothers._holt_add_dam,
+             (None, 'mul'): smoothers._holt_mul_dam,
+             (None, None): smoothers._holt__}
+
+PY_SMOOTHERS = {('mul', 'add'): _holt_win_add_mul_dam,
+                ('mul', 'mul'): _holt_win_mul_mul_dam,
+                ('mul', None): _holt_win__mul,
+                ('add', 'add'): _holt_win_add_add_dam,
+                ('add', 'mul'): _holt_win_mul_add_dam,
+                ('add', None): _holt_win__add,
+                (None, 'add'): _holt_add_dam,
+                (None, 'mul'): _holt_mul_dam,
+                (None, None): _holt__}
+
+
 class HoltWintersResults(Results):
     """
     Holt Winter's Exponential Smoothing Results
@@ -236,21 +252,22 @@ class HoltWintersResults(Results):
     ----------
     model : ExponentialSmoothing instance
         The fitted model instance
-    params : dictionary
+    params : dict
         All the parameters for the Exponential Smoothing model.
 
     Attributes
     ----------
-    specification : dictionary
-        Dictionary including all attributes from the VARMAX model instance.
-    params: dictionary
+    params: dict
         All the parameters for the Exponential Smoothing model.
+    params_formatted: pd.DataFrame
+        DataFrame containing all parameters, their short names and a flag
+        indicating whether the parameter's value was optimized to fit the data.
     fittedfcast: array
         An array of both the fitted values and forecast values.
     fittedvalues: array
-        An array of the fitted values. Fitted by the Exponential Smoothing 
+        An array of the fitted values. Fitted by the Exponential Smoothing
         model.
-    fcast: array
+    fcastvalues: array
         An array of the forecast values forecast by the Exponential Smoothing
         model.
     sse: float
@@ -260,7 +277,7 @@ class HoltWintersResults(Results):
     slope: array
         An array of the slope values that make up the fitted values.
     season: array
-        An array of the seaonal values that make up the fitted values.
+        An array of the seasonal values that make up the fitted values.
     aic: float
         The Akaike information criterion.
     bic: float
@@ -271,12 +288,21 @@ class HoltWintersResults(Results):
         An array of the residuals of the fittedvalues and actual values.
     k: int
         the k parameter used to remove the bias in AIC, BIC etc.
+    optimized: bool
+        Flag indicating whether the model parameters were optimized to fit the data.
+    mle_retvals:  {None, scipy.optimize.optimize.OptimizeResult}
+        Optimization results if the parameters were optimized to fit the data.
 
+    Methods
+    -------
+    forecast
+    predict
+    summary
     """
 
-    def __init__(self, model, params, **kwds):
+    def __init__(self, model, params, **kwargs):
         self.data = model.data
-        super(HoltWintersResults, self).__init__(model, params, **kwds)
+        super(HoltWintersResults, self).__init__(model, params, **kwargs)
 
     def predict(self, start=None, end=None):
         """
@@ -294,7 +320,7 @@ def predict(self, start=None, end=None):
             parse or a datetime type. However, if the dates index does not
             have a fixed frequency, end must be an integer index if you
             want out of sample prediction. Default is the last observation in
-            the sample.        
+            the sample.
 
         Returns
         -------
@@ -326,6 +352,84 @@ def forecast(self, steps=1):
             # May occur when the index doesn't have a freq
             return self.model._predict(h=steps, **self.params).fcastvalues
 
+    def summary(self):
+        """Summarize the Model
+
+        Returns
+        -------
+        smry : Summary instance
+            This holds the summary table and text, which can be printed or
+            converted to various output formats.
+
+        See Also
+        --------
+        statsmodels.iolib.summary.Summary
+        """
+        from statsmodels.iolib.summary import Summary
+        from statsmodels.iolib.table import SimpleTable
+        model = self.model
+        title = model.__class__.__name__ + ' Model Results'
+
+        dep_variable = 'endog'
+        if isinstance(self.model.endog, pd.DataFrame):
+            dep_variable = self.model.endog.columns[0]
+        elif isinstance(self.model.endog, pd.Series):
+            dep_variable = self.model.endog.name
+        seasonal_periods = None if self.model.seasonal is None else self.model.seasonal_periods
+        lookup = {'add': 'Additive', 'additive': 'Additive',
+                  'mul': 'Multiplicative', 'multiplicative': 'Multiplicative', None: 'None'}
+        transform = self.params['use_boxcox']
+        box_cox_transform = True if transform else False
+        box_cox_coeff = transform if isinstance(transform, string_types) else self.params['lamda']
+        if isinstance(box_cox_coeff, float):
+            box_cox_coeff = '{:>10.5f}'.format(box_cox_coeff)
+        top_left = [('Dep. Variable:', [dep_variable]),
+                    ('Model:', [model.__class__.__name__]),
+                    ('Optimized:', [str(np.any(self.optimized))]),
+                    ('Trend:', [lookup[self.model.trend]]),
+                    ('Seasonal:', [lookup[self.model.seasonal]]),
+                    ('Seasonal Periods:', [str(seasonal_periods)]),
+                    ('Box-Cox:', [str(box_cox_transform)]),
+                    ('Box-Cox Coeff.:', [str(box_cox_coeff)])]
+
+        top_right = [
+            ('No. Observations:', [str(len(self.model.endog))]),
+            ('SSE', ['{:5.3f}'.format(self.sse)]),
+            ('AIC', ['{:5.3f}'.format(self.aic)]),
+            ('BIC', ['{:5.3f}'.format(self.bic)]),
+            ('AICC', ['{:5.3f}'.format(self.aicc)]),
+            ('Date:', None),
+            ('Time:', None)]
+
+        smry = Summary()
+        smry.add_table_2cols(self, gleft=top_left, gright=top_right,
+                             title=title)
+        formatted = self.params_formatted  # type: pd.DataFrame
+
+        def _fmt(x):
+            abs_x = np.abs(x)
+            scale = 1
+            if abs_x != 0:
+                scale = int(np.log10(abs_x))
+            if scale > 4 or scale < -3:
+                return '{:>20.5g}'.format(x)
+            dec = min(7 - scale, 7)
+            fmt = '{{:>20.{0}f}}'.format(dec)
+            return fmt.format(x)
+
+        tab = []
+        for _, vals in formatted.iterrows():
+            tab.append([_fmt(vals.iloc[1]),
+                        '{0:>20}'.format(vals.iloc[0]),
+                        '{0:>20}'.format(str(bool(vals.iloc[2])))])
+        params_table = SimpleTable(tab, headers=['coeff', 'code', 'optimized'],
+                                   title="",
+                                   stubs=list(formatted.index))
+
+        smry.tables.append(params_table)
+
+        return smry
+
 
 class HoltWintersResultsWrapper(ResultsWrapper):
     _attrs = {'fittedvalues': 'rows',
@@ -361,18 +465,19 @@ class ExponentialSmoothing(TimeSeriesModel):
 
     Returns
     -------
-    results : ExponentialSmoothing class        
+    results : ExponentialSmoothing class
 
     Notes
     -----
     This is a full implementation of the holt winters exponential smoothing as
     per [1]. This includes all the unstable methods as well as the stable methods.
-    The implementation of the library covers the functionality of the R 
+    The implementation of the library covers the functionality of the R
     library as much as possible whilst still being pythonic.
 
     References
     ----------
-    [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice. OTexts, 2014.
+    [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice.
+        OTexts, 2014.
     """
 
     def __init__(self, endog, trend=None, damped=False, seasonal=None,
@@ -388,16 +493,17 @@ def __init__(self, endog, trend=None, damped=False, seasonal=None,
         self.seasonal = seasonal
         self.trending = trend in ['mul', 'add']
         self.seasoning = seasonal in ['mul', 'add']
-        if (self.trend == 'mul' or self.seasonal == 'mul') and (endog <= 0.0).any():
-            raise NotImplementedError(
-                'Unable to correct for negative or zero values')
+        if (self.trend == 'mul' or self.seasonal == 'mul') and np.any(endog <= 0.0):
+            raise ValueError('endog must be strictly positive when using multiplicative '
+                             'trend or seasonal components.')
         if self.damped and not self.trending:
-            raise NotImplementedError('Can only dampen the trend component')
+            raise ValueError('Can only dampen the trend component')
         if self.seasoning:
-            if (seasonal_periods is None or seasonal_periods == 0):
-                raise NotImplementedError(
-                    'Unable to detect season automatically')
             self.seasonal_periods = seasonal_periods
+            if seasonal_periods is None:
+                self.seasonal_periods = freq_to_period(self._index_freq)
+            if self.seasonal_periods <= 1:
+                raise ValueError('seasonal_periods must be larger than 1.')
         else:
             self.seasonal_periods = 0
         self.nobs = len(self.endog)
@@ -435,7 +541,8 @@ def predict(self, params, start=None, end=None):
 
     def fit(self, smoothing_level=None, smoothing_slope=None, smoothing_seasonal=None,
             damping_slope=None, optimized=True, use_boxcox=False, remove_bias=False,
-            use_basinhopping=False):
+            use_basinhopping=False, start_params=None, initial_level=None, initial_slope=None,
+            use_brute=True):
         """
         fit Holt Winter's Exponential Smoothing
 
@@ -445,7 +552,7 @@ def fit(self, smoothing_level=None, smoothing_slope=None, smoothing_seasonal=Non
             The alpha value of the simple exponential smoothing, if the value is
             set then this value will be used as the value.
         smoothing_slope :  float, optional
-            The beta value of the holts trend method, if the value is
+            The beta value of the Holt's trend method, if the value is
             set then this value will be used as the value.
         smoothing_seasonal : float, optional
             The gamma value of the holt winters seasonal method, if the value is
@@ -454,18 +561,28 @@ def fit(self, smoothing_level=None, smoothing_slope=None, smoothing_seasonal=Non
             The phi value of the damped method, if the value is
             set then this value will be used as the value.
         optimized : bool, optional
-            Should the values that have not been set above be optimized 
+            Should the values that have not been set above be optimized
             automatically?
         use_boxcox : {True, False, 'log', float}, optional
-            Should the boxcox tranform be applied to the data first? If 'log' 
+            Should the Box-Cox transform be applied to the data first? If 'log'
             then apply the log. If float then use lambda equal to float.
         remove_bias : bool, optional
-            Should the bias be removed from the forecast values and fitted values 
-            before being returned? Does this by enforcing average residuals equal 
+            Should the bias be removed from the forecast values and fitted values
+            before being returned? Does this by enforcing average residuals equal
             to zero.
         use_basinhopping : bool, optional
-            Should the opptimser try harder using basinhopping to find optimal 
-            values?
+            Using Basin Hopping optimizer to find optimal values
+        start_params: array, optional
+            Starting values to used when optimizing the fit.  If not provided,
+            starting values are determined using a combination of grid search
+            and reasonable values based on the initial values of the data
+        initial_level: float, optional
+            Value to use when initializing the fitted level.
+        initial_slope: float, optional
+            Value to use when initializing the fitted slope.
+        use_brute: bool, optional
+            Search for good starting values using a brute force (grid) optimizer.
+            If False, a naive set of starting values is used.
 
         Returns
         -------
@@ -476,12 +593,13 @@ def fit(self, smoothing_level=None, smoothing_slope=None, smoothing_seasonal=Non
         -----
         This is a full implementation of the holt winters exponential smoothing as
         per [1]. This includes all the unstable methods as well as the stable methods.
-        The implementation of the library covers the functionality of the R 
+        The implementation of the library covers the functionality of the R
         library as much as possible whilst still being pythonic.
 
         References
         ----------
-        [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice. OTexts, 2014.
+        [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice.
+            OTexts, 2014.
         """
         # Variable renames to alpha,beta, etc as this helps with following the
         # mathematical notation in general
@@ -489,6 +607,8 @@ def fit(self, smoothing_level=None, smoothing_slope=None, smoothing_seasonal=Non
         beta = smoothing_slope
         gamma = smoothing_seasonal
         phi = damping_slope
+        l0 = self._l0 = initial_level
+        b0 = self._b0 = initial_slope
 
         data = self.endog
         damped = self.damped
@@ -511,93 +631,162 @@ def fit(self, smoothing_level=None, smoothing_slope=None, smoothing_seasonal=Non
             lamda = None
             y = data.squeeze()
         if np.ndim(y) != 1:
-            raise NotImplementedError('Only 1 dimensional data supported')
-        l = np.zeros((self.nobs,))
-        b = np.zeros((self.nobs,))
-        s = np.zeros((self.nobs + m - 1,))
+            raise ValueError('Only 1 dimensional data supported')
+        self._y = y
+        l = np.zeros(self.nobs)
+        b = np.zeros(self.nobs)
+        s = np.zeros(self.nobs + m - 1)
         p = np.zeros(6 + m)
         max_seen = np.finfo(np.double).max
-        if seasoning:
-            l0 = y[np.arange(self.nobs) % m == 0].mean()
-            b0 = ((y[m:m + m] - y[:m]) / m).mean() if trending else None
-            s0 = list(y[:m] / l0) if seasonal == 'mul' else list(y[:m] - l0)
-        elif trending:
-            l0 = y[0]
-            b0 = y[1] / y[0] if trend == 'mul' else y[1] - y[0]
-            s0 = []
-        else:
-            l0 = y[0]
-            b0 = None
-            s0 = []
+        l0, b0, s0 = self.initial_values()
+
+        xi = np.zeros_like(p, dtype=np.bool)
         if optimized:
             init_alpha = alpha if alpha is not None else 0.5 / max(m, 1)
             init_beta = beta if beta is not None else 0.1 * init_alpha if trending else beta
             init_gamma = None
             init_phi = phi if phi is not None else 0.99
-            # Selection of functions to optimize for approporate parameters
-            func_dict = {('mul', 'add'): _holt_win_add_mul_dam,
-                         ('mul', 'mul'): _holt_win_mul_mul_dam,
-                         ('mul', None): _holt_win__mul,
-                         ('add', 'add'): _holt_win_add_add_dam,
-                         ('add', 'mul'): _holt_win_mul_add_dam,
-                         ('add', None): _holt_win__add,
-                         (None, 'add'): _holt_add_dam,
-                         (None, 'mul'): _holt_mul_dam,
-                         (None, None): _holt__}
+            # Selection of functions to optimize for appropriate parameters
             if seasoning:
                 init_gamma = gamma if gamma is not None else 0.05 * \
-                    (1 - init_alpha)
-                xi = np.array([alpha is None, beta is None, gamma is None,
-                               True, trending, phi is None and damped] + [True] * m)
-                func = func_dict[(seasonal, trend)]
+                                                             (1 - init_alpha)
+                xi = np.array([alpha is None, trending and beta is None, gamma is None,
+                               initial_level is None, trending and initial_slope is None,
+                               phi is None and damped] + [True] * m)
+                func = SMOOTHERS[(seasonal, trend)]
             elif trending:
                 xi = np.array([alpha is None, beta is None, False,
-                               True, True, phi is None and damped] + [False] * m)
-                func = func_dict[(None, trend)]
+                               initial_level is None, initial_slope is None,
+                               phi is None and damped] + [False] * m)
+                func = SMOOTHERS[(None, trend)]
             else:
                 xi = np.array([alpha is None, False, False,
-                               True, False, False] + [False] * m)
-                func = func_dict[(None, None)]
+                               initial_level is None, False, False] + [False] * m)
+                func = SMOOTHERS[(None, None)]
             p[:] = [init_alpha, init_beta, init_gamma, l0, b0, init_phi] + s0
-            
-            # txi [alpha, beta, gamma, l0, b0, phi, s0,..,s_(m-1)]
-            # Have a quick look in the region for a good starting place for alpha etc.
-            # using guestimates for the levels
-            txi = xi & np.array(
-                [True, True, True, False, False, True] + [False] * m)
-            bounds = np.array([(0.0, 1.0), (0.0, 1.0), (0.0, 1.0),
-                               (0.0, None), (0.0, None), (0.0, 1.0)] + [(None, None), ] * m)
-            res = brute(func, bounds[txi], (txi, p, y, l, b, s, m, self.nobs, max_seen),
-                        Ns=20, full_output=True, finish=None)
-            (p[txi], max_seen, grid, Jout) = res
-            [alpha, beta, gamma, l0, b0, phi] = p[:6]
-            s0 = p[6:]            
-            #bounds = np.array([(0.0,1.0),(0.0,1.0),(0.0,1.0),(0.0,None),(0.0,None),(0.8,1.0)] + [(None,None),]*m)
-            if use_basinhopping:
-                # Take a deeper look in the local minimum we are in to find the best
-                # solution to parameters, maybe hop around to try escape the local
-                # minimum we may be in.
-                res = basinhopping(func, p[xi], minimizer_kwargs={'args': (
-                    xi, p, y, l, b, s, m, self.nobs, max_seen), 'bounds': bounds[xi]}, stepsize=0.01)
+            if np.any(xi):
+                # txi [alpha, beta, gamma, l0, b0, phi, s0,..,s_(m-1)]
+                # Have a quick look in the region for a good starting place for alpha etc.
+                # using guesstimates for the levels
+                txi = xi & np.array([True, True, True, False, False, True] + [False] * m)
+                txi = txi.astype(np.bool)
+                bounds = np.array([(0.0, 1.0), (0.0, 1.0), (0.0, 1.0),
+                                   (0.0, None), (0.0, None), (0.0, 1.0)] + [(None, None), ] * m)
+                args = (txi.astype(np.uint8), p, y, l, b, s, m, self.nobs, max_seen)
+                if start_params is None and np.any(txi) and use_brute:
+                    res = brute(func, bounds[txi], args, Ns=20, full_output=True, finish=None)
+                    p[txi], max_seen, _, _ = res
+                else:
+                    if start_params is not None:
+                        start_params = np.atleast_1d(np.squeeze(start_params))
+                        if len(start_params) != xi.sum():
+                            raise ValueError('start_params must have {0} values but '
+                                             'has {1} instead'.format(len(xi), len(start_params)))
+                        p[xi] = start_params
+                    args = (xi.astype(np.uint8), p, y, l, b, s, m, self.nobs, max_seen)
+                    max_seen = func(np.ascontiguousarray(p[xi]), *args)
+                # alpha, beta, gamma, l0, b0, phi = p[:6]
+                # s0 = p[6:]
+                # bounds = np.array([(0.0,1.0),(0.0,1.0),(0.0,1.0),(0.0,None),
+                # (0.0,None),(0.8,1.0)] + [(None,None),]*m)
+                args = (xi.astype(np.uint8), p, y, l, b, s, m, self.nobs, max_seen)
+                if use_basinhopping:
+                    # Take a deeper look in the local minimum we are in to find the best
+                    # solution to parameters, maybe hop around to try escape the local
+                    # minimum we may be in.
+                    res = basinhopping(func, p[xi],
+                                       minimizer_kwargs={'args': args, 'bounds': bounds[xi]},
+                                       stepsize=0.01)
+                    success = res.lowest_optimization_result.success
+                else:
+                    # Take a deeper look in the local minimum we are in to find the best
+                    # solution to parameters
+                    res = minimize(func, p[xi], args=args, bounds=bounds[xi])
+                    success = res.success
+
+                if not success:
+                    from warnings import warn
+                    from statsmodels.tools.sm_exceptions import ConvergenceWarning
+                    warn("Optimization failed to converge. Check mle_retvals.",
+                         ConvergenceWarning)
+                p[xi] = res.x
+                opt = res
             else:
-                # Take a deeper look in the local minimum we are in to find the best
-                # solution to parameters
-                res = minimize(func, p[xi], args=(
-                    xi, p, y, l, b, s, m, self.nobs, max_seen), bounds=bounds[xi])                
-            p[xi] = res.x            
+                from warnings import warn
+                from statsmodels.tools.sm_exceptions import EstimationWarning
+                message = "Model has no free parameters to estimate. Set " \
+                          "optimized=False to suppress this warning"
+                warn(message, EstimationWarning)
+
             [alpha, beta, gamma, l0, b0, phi] = p[:6]
             s0 = p[6:]
-            opt = res
+
         hwfit = self._predict(h=0, smoothing_level=alpha, smoothing_slope=beta,
                               smoothing_seasonal=gamma, damping_slope=phi,
                               initial_level=l0, initial_slope=b0, initial_seasons=s0,
-                              use_boxcox=use_boxcox, lamda=lamda, remove_bias=remove_bias)
+                              use_boxcox=use_boxcox, remove_bias=remove_bias, is_optimized=xi)
         hwfit._results.mle_retvals = opt
         return hwfit
 
+    def initial_values(self):
+        """
+        Computes the initial values used in the exponential smoothing recursions.
+
+        Returns
+        -------
+        initial_level : float
+            The initial value used for the level component
+        initial_slope : {float, None}
+            The initial value used for the trend component
+        initial_seasons : list
+            The initial values used for the seasonal components
+
+        Notes
+        -----
+        Convenience function the exposes the values used to initialize the recursions.  When
+        optimizing parameters these are used as starting values.
+
+        Method used to compute the initial value depends on when components are included in the
+        model.  In a simple exponential smoothing model without trend or a seasonal components,
+        the initial value is set to the first observation. When a trend is added, the trend is
+        initialized either using y[1]/y[0], if multiplicative, or y[1]-y[0]. When the seasonal
+        component is added the initialization adapts to account for the modified structure.
+        """
+        y = self._y
+        trend = self.trend
+        seasonal = self.seasonal
+        seasoning = self.seasoning
+        trending = self.trending
+        m = self.seasonal_periods
+        l0 = self._l0
+        b0 = self._b0
+        if seasoning:
+            l0 = y[np.arange(self.nobs) % m == 0].mean() if l0 is None else l0
+            if b0 is None and trending:
+                lead, lag = y[m:m + m], y[:m]
+                if trend == 'mul':
+                    b0 = np.exp((np.log(lead.mean()) - np.log(lag.mean())) / m)
+                else:
+                    b0 = ((lead - lag) / m).mean()
+            s0 = list(y[:m] / l0) if seasonal == 'mul' else list(y[:m] - l0)
+        elif trending:
+            l0 = y[0] if l0 is None else l0
+            if b0 is None:
+                b0 = y[1] / y[0] if trend == 'mul' else y[1] - y[0]
+            s0 = []
+        else:
+            if l0 is None:
+                l0 = y[0]
+            b0 = None
+            s0 = []
+
+        return l0, b0, s0
+
+
     def _predict(self, h=None, smoothing_level=None, smoothing_slope=None,
                  smoothing_seasonal=None, initial_level=None, initial_slope=None,
-                 damping_slope=None, initial_seasons=None, use_boxcox=None, lamda=None, remove_bias=None):
+                 damping_slope=None, initial_seasons=None, use_boxcox=None, lamda=None,
+                 remove_bias=None, is_optimized=None):
         """
         Helper prediction function
 
@@ -606,7 +795,7 @@ def _predict(self, h=None, smoothing_level=None, smoothing_slope=None,
         h : int, optional
             The number of time steps to forecast ahead.
         """
-        # Variable renames to alpha,beta, etc as this helps with following the
+        # Variable renames to alpha, beta, etc as this helps with following the
         # mathematical notation in general
         alpha = smoothing_level
         beta = smoothing_slope
@@ -664,76 +853,78 @@ def _predict(self, h=None, smoothing_level=None, smoothing_slope=None,
                   'add': np.multiply,
                   None: lambda b, phi: 0
                   }[trend]
+        nobs = self.nobs
         if seasonal == 'mul':
-            for i in range(1, self.nobs + 1):
+            for i in range(1, nobs + 1):
                 l[i] = y_alpha[i - 1] / s[i - 1] + \
-                    (alphac * trended(l[i - 1], dampen(b[i - 1], phi)))
+                       (alphac * trended(l[i - 1], dampen(b[i - 1], phi)))
                 if trending:
                     b[i] = (beta * detrend(l[i], l[i - 1])) + \
-                        (betac * dampen(b[i - 1], phi))
-                s[i + m - 1] = y_gamma[i - 1] / \
-                    trended(l[i - 1], dampen(b[i - 1], phi)) + \
+                           (betac * dampen(b[i - 1], phi))
+                s[i + m - 1] = y_gamma[i - 1] / trended(l[i - 1], dampen(b[i - 1], phi)) + \
                     (gammac * s[i - 1])
-            slope = b[1:i + 1].copy()
-            season = s[m:i + m].copy()
-            l[i:] = l[i]
+            slope = b[1:nobs + 1].copy()
+            season = s[m:nobs + m].copy()
+            l[nobs:] = l[nobs]
             if trending:
-                b[:i] = dampen(b[:i], phi)
-                b[i:] = dampen(b[i], phi_h)
+                b[:nobs] = dampen(b[:nobs], phi)
+                b[nobs:] = dampen(b[nobs], phi_h)
             trend = trended(l, b)
-            s[i + m - 1:] = [s[(i - 1) + j % m] for j in range(h + 1 + 1)]            
+            s[nobs + m - 1:] = [s[(nobs - 1) + j % m] for j in range(h + 1 + 1)]
             fitted = trend * s[:-m]
         elif seasonal == 'add':
-            for i in range(1, self.nobs + 1):
+            for i in range(1, nobs + 1):
                 l[i] = y_alpha[i - 1] - (alpha * s[i - 1]) + \
-                    (alphac * trended(l[i - 1], dampen(b[i - 1], phi)))
+                       (alphac * trended(l[i - 1], dampen(b[i - 1], phi)))
                 if trending:
                     b[i] = (beta * detrend(l[i], l[i - 1])) + \
-                        (betac * dampen(b[i - 1], phi))
+                           (betac * dampen(b[i - 1], phi))
                 s[i + m - 1] = y_gamma[i - 1] - \
-                    (gamma * trended(l[i - 1],
-                                     dampen(b[i - 1], phi))) + (gammac * s[i - 1])
-            slope = b[1:i + 1].copy()
-            season = s[m:i + m].copy()
-            l[i:] = l[i]
+                    (gamma * trended(l[i - 1], dampen(b[i - 1], phi))) + \
+                    (gammac * s[i - 1])
+            slope = b[1:nobs + 1].copy()
+            season = s[m:nobs + m].copy()
+            l[nobs:] = l[nobs]
             if trending:
-                b[:i] = dampen(b[:i], phi)
-                b[i:] = dampen(b[i], phi_h)
+                b[:nobs] = dampen(b[:nobs], phi)
+                b[nobs:] = dampen(b[nobs], phi_h)
             trend = trended(l, b)
-            s[i + m - 1:] = [s[(i - 1) + j % m] for j in range(h + 1 + 1)]            
+            s[nobs + m - 1:] = [s[(nobs - 1) + j % m] for j in range(h + 1 + 1)]
             fitted = trend + s[:-m]
         else:
-            for i in range(1, self.nobs + 1):
+            for i in range(1, nobs + 1):
                 l[i] = y_alpha[i - 1] + \
-                    (alphac * trended(l[i - 1], dampen(b[i - 1], phi)))
+                       (alphac * trended(l[i - 1], dampen(b[i - 1], phi)))
                 if trending:
                     b[i] = (beta * detrend(l[i], l[i - 1])) + \
-                        (betac * dampen(b[i - 1], phi))
-            slope = b[1:i + 1].copy()
-            season = s[m:i + m].copy()
-            l[i:] = l[i]
+                           (betac * dampen(b[i - 1], phi))
+            slope = b[1:nobs + 1].copy()
+            season = s[m:nobs + m].copy()
+            l[nobs:] = l[nobs]
             if trending:
-                b[:i] = dampen(b[:i], phi)
-                b[i:] = dampen(b[i], phi_h)
+                b[:nobs] = dampen(b[:nobs], phi)
+                b[nobs:] = dampen(b[nobs], phi_h)
             trend = trended(l, b)
             fitted = trend
-        level = l[1:i + 1].copy()
+        level = l[1:nobs + 1].copy()
         if use_boxcox or use_boxcox == 'log' or isinstance(use_boxcox, float):
             fitted = inv_boxcox(fitted, lamda)
             level = inv_boxcox(level, lamda)
-            slope = detrend(trend[:i], level)
+            slope = detrend(trend[:nobs], level)
             if seasonal == 'add':
-                season = (fitted - inv_boxcox(trend, lamda))[:i]
-            elif seasonal == 'mul':
-                season = (fitted / inv_boxcox(trend, lamda))[:i]
-            else:
-                pass
+                season = (fitted - inv_boxcox(trend, lamda))[:nobs]
+            else:  # seasonal == 'mul':
+                season = (fitted / inv_boxcox(trend, lamda))[:nobs]
         sse = sqeuclidean(fitted[:-h - 1], data)
         # (s0 + gamma) + (b0 + beta) + (l0 + alpha) + phi
         k = m * seasoning + 2 * trending + 2 + 1 * damped
-        aic = self.nobs * np.log(sse / self.nobs) + (k) * 2
-        aicc = aic + (2 * (k + 2) * (k + 3)) / (self.nobs - k - 3)
-        bic = self.nobs * np.log(sse / self.nobs) + (k) * np.log(self.nobs)
+        aic = self.nobs * np.log(sse / self.nobs) + k * 2
+        if self.nobs - k - 3 > 0:
+            aicc_penalty = (2 * (k + 2) * (k + 3)) / (self.nobs - k - 3)
+        else:
+            aicc_penalty = np.inf
+        aicc = aic + aicc_penalty
+        bic = self.nobs * np.log(sse / self.nobs) + k * np.log(self.nobs)
         resid = data - fitted[:-h - 1]
         if remove_bias:
             fitted += resid.mean()
@@ -749,10 +940,34 @@ def _predict(self, h=None, smoothing_level=None, smoothing_slope=None,
                        'use_boxcox': use_boxcox,
                        'lamda': lamda,
                        'remove_bias': remove_bias}
-        hwfit = HoltWintersResults(self, self.params, fittedfcast=fitted, fittedvalues=fitted[:-h - 1],
-                                   fcastvalues=fitted[-h - 1:], sse=sse, level=level,
-                                   slope=slope, season=season, aic=aic, bic=bic,
-                                   aicc=aicc, resid=resid, k=k)
+
+        # Format parameters into a DataFrame
+        codes = ['alpha', 'beta', 'gamma', 'l.0', 'b.0', 'phi']
+        codes += ['s.{0}'.format(i) for i in range(m)]
+        idx = ['smoothing_level', 'smoothing_slope', 'smoothing_seasonal',
+               'initial_level', 'initial_slope', 'damping_slope']
+        idx += ['initial_seasons.{0}'.format(i) for i in range(m)]
+
+        formatted = [alpha, beta, gamma, l[0], b[0], phi]
+        formatted += s[:m].tolist()
+        formatted = list(map(lambda v: np.nan if v is None else v, formatted))
+        formatted = np.array(formatted)
+        if is_optimized is None:
+            optimized = np.zeros(len(codes), dtype=np.bool)
+        else:
+            optimized = is_optimized.astype(np.bool)
+        included = [True, trending, seasoning, True, trending, damped]
+        included += [True] * m
+        formatted = pd.DataFrame([[c, f, o] for c, f, o in zip(codes, formatted, optimized)],
+                                 columns=['name', 'param', 'optimized'],
+                                 index=idx)
+        formatted = formatted.loc[included]
+
+        hwfit = HoltWintersResults(self, self.params, fittedfcast=fitted,
+                                   fittedvalues=fitted[:-h - 1], fcastvalues=fitted[-h - 1:],
+                                   sse=sse, level=level, slope=slope, season=season, aic=aic,
+                                   bic=bic, aicc=aicc, resid=resid, k=k,
+                                   params_formatted=formatted, optimized=optimized)
         return HoltWintersResultsWrapper(hwfit)
 
 
@@ -781,13 +996,15 @@ class SimpleExpSmoothing(ExponentialSmoothing):
 
     References
     ----------
-    [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice. OTexts, 2014.
+    [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice.
+        OTexts, 2014.
     """
 
     def __init__(self, endog):
         super(SimpleExpSmoothing, self).__init__(endog)
 
-    def fit(self, smoothing_level=None, optimized=True):
+    def fit(self, smoothing_level=None, optimized=True, start_params=None,
+            initial_level=None, use_brute=True):
         """
         fit Simple Exponential Smoothing wrapper(...)
 
@@ -796,8 +1013,17 @@ def fit(self, smoothing_level=None, optimized=True):
         smoothing_level : float, optional
             The smoothing_level value of the simple exponential smoothing, if the value is
             set then this value will be used as the value.
-        optimized : bool
+        optimized : bool, optional
             Should the values that have not been set above be optimized automatically?
+        start_params: array, optional
+            Starting values to used when optimizing the fit.  If not provided,
+            starting values are determined using a combination of grid search
+            and reasonable values based on the initial values of the data
+        initial_level: float, optional
+            Value to use when initializing the fitted level.
+        use_brute: bool, optional
+            Search for good starting values using a brute force (grid) optimizer.
+            If False, a naive set of starting values is used.
 
         Returns
         -------
@@ -811,9 +1037,13 @@ def fit(self, smoothing_level=None, optimized=True):
 
         References
         ----------
-        [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice. OTexts, 2014.
+        [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice.
+            OTexts, 2014.
         """
-        return super(SimpleExpSmoothing, self).fit(smoothing_level=smoothing_level, optimized=optimized)
+        return super(SimpleExpSmoothing, self).fit(smoothing_level=smoothing_level,
+                                                   optimized=optimized, start_params=start_params,
+                                                   initial_level=initial_level,
+                                                   use_brute=use_brute)
 
 
 class Holt(ExponentialSmoothing):
@@ -824,18 +1054,18 @@ class Holt(ExponentialSmoothing):
     ----------
     endog : array-like
         Time series
-    expoential : bool, optional
+    exponential : bool, optional
         Type of trend component.
     damped : bool, optional
         Should the trend component be damped.
 
     Returns
     -------
-    results : Holt class        
+    results : Holt class
 
     Notes
     -----
-    This is a full implementation of the holts exponential smoothing as
+    This is a full implementation of the Holt's exponential smoothing as
     per [1].
 
     See Also
@@ -845,14 +1075,17 @@ class Holt(ExponentialSmoothing):
 
     References
     ----------
-    [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice. OTexts, 2014.
+    [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice.
+        OTexts, 2014.
     """
 
     def __init__(self, endog, exponential=False, damped=False):
         trend = 'mul' if exponential else 'add'
         super(Holt, self).__init__(endog, trend=trend, damped=damped)
 
-    def fit(self, smoothing_level=None, smoothing_slope=None, damping_slope=None, optimized=True):
+    def fit(self, smoothing_level=None, smoothing_slope=None, damping_slope=None,
+            optimized=True, start_params=None, initial_level=None,
+            initial_slope=None, use_brute=True):
         """
         fit Holt's Exponential Smoothing wrapper(...)
 
@@ -862,13 +1095,24 @@ def fit(self, smoothing_level=None, smoothing_slope=None, damping_slope=None, op
             The alpha value of the simple exponential smoothing, if the value is
             set then this value will be used as the value.
         smoothing_slope :  float, optional
-            The beta value of the holts trend method, if the value is
+            The beta value of the Holt's trend method, if the value is
             set then this value will be used as the value.
         damping_slope : float, optional
             The phi value of the damped method, if the value is
             set then this value will be used as the value.
         optimized : bool, optional
             Should the values that have not been set above be optimized automatically?
+        start_params: array, optional
+            Starting values to used when optimizing the fit.  If not provided,
+            starting values are determined using a combination of grid search
+            and reasonable values based on the initial values of the data
+        initial_level: float, optional
+            Value to use when initializing the fitted level.
+        initial_slope: float, optional
+            Value to use when initializing the fitted slope.
+        use_brute: bool, optional
+            Search for good starting values using a brute force (grid) optimizer.
+            If False, a naive set of starting values is used.
 
         Returns
         -------
@@ -877,13 +1121,15 @@ def fit(self, smoothing_level=None, smoothing_slope=None, damping_slope=None, op
 
         Notes
         -----
-        This is a full implementation of the holts exponential smoothing as
+        This is a full implementation of the Holt's exponential smoothing as
         per [1].
 
         References
         ----------
-        [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice. OTexts, 2014.
+        [1] Hyndman, Rob J., and George Athanasopoulos. Forecasting: principles and practice.
+            OTexts, 2014.
         """
         return super(Holt, self).fit(smoothing_level=smoothing_level,
                                      smoothing_slope=smoothing_slope, damping_slope=damping_slope,
-                                     optimized=optimized)
+                                     optimized=optimized, start_params=start_params,
+                                     initial_level=None, initial_slope=None, use_brute=use_brute)
diff --git a/statsmodels/tsa/x13.py b/statsmodels/tsa/x13.py
--- a/statsmodels/tsa/x13.py
+++ b/statsmodels/tsa/x13.py
@@ -33,7 +33,8 @@ def __getitem__(self, key):
             return 12
         elif key.startswith('Q'):
             return 4
-
+        elif key.startswith('W'):
+            return 52
 
 _freq_to_period = _freq_to_period()
 


Test patch:
diff --git a/statsmodels/tsa/tests/results/housing-data.csv b/statsmodels/tsa/tests/results/housing-data.csv
new file mode 100644
--- /dev/null
+++ b/statsmodels/tsa/tests/results/housing-data.csv
@@ -0,0 +1,715 @@
+DATE,HOUSTNSA
+1959-01-01,96.2
+1959-02-01,99.0
+1959-03-01,127.7
+1959-04-01,150.8
+1959-05-01,152.5
+1959-06-01,147.8
+1959-07-01,148.1
+1959-08-01,138.2
+1959-09-01,136.4
+1959-10-01,120.0
+1959-11-01,104.7
+1959-12-01,95.6
+1960-01-01,86.0
+1960-02-01,90.7
+1960-03-01,90.5
+1960-04-01,123.0
+1960-05-01,130.2
+1960-06-01,122.8
+1960-07-01,114.3
+1960-08-01,130.3
+1960-09-01,96.9
+1960-10-01,110.4
+1960-11-01,92.8
+1960-12-01,64.2
+1961-01-01,70.4
+1961-02-01,74.1
+1961-03-01,104.2
+1961-04-01,112.8
+1961-05-01,127.6
+1961-06-01,134.8
+1961-07-01,126.6
+1961-08-01,127.1
+1961-09-01,125.4
+1961-10-01,124.8
+1961-11-01,103.0
+1961-12-01,82.2
+1962-01-01,81.2
+1962-02-01,77.1
+1962-03-01,116.2
+1962-04-01,147.8
+1962-05-01,155.2
+1962-06-01,136.8
+1962-07-01,136.5
+1962-08-01,147.7
+1962-09-01,114.3
+1962-10-01,135.2
+1962-11-01,120.9
+1962-12-01,93.9
+1963-01-01,79.0
+1963-02-01,89.6
+1963-03-01,124.8
+1963-04-01,164.2
+1963-05-01,172.7
+1963-06-01,154.2
+1963-07-01,151.3
+1963-08-01,144.0
+1963-09-01,143.7
+1963-10-01,165.3
+1963-11-01,119.3
+1963-12-01,95.0
+1964-01-01,97.9
+1964-02-01,101.3
+1964-03-01,129.1
+1964-04-01,147.1
+1964-05-01,152.8
+1964-06-01,157.2
+1964-07-01,140.6
+1964-08-01,138.3
+1964-09-01,119.8
+1964-10-01,141.2
+1964-11-01,110.6
+1964-12-01,93.0
+1965-01-01,81.7
+1965-02-01,80.9
+1965-03-01,119.9
+1965-04-01,148.6
+1965-05-01,153.3
+1965-06-01,151.8
+1965-07-01,139.1
+1965-08-01,128.3
+1965-09-01,124.6
+1965-10-01,133.1
+1965-11-01,110.5
+1965-12-01,101.1
+1966-01-01,79.4
+1966-02-01,76.2
+1966-03-01,118.1
+1966-04-01,140.9
+1966-05-01,130.0
+1966-06-01,120.6
+1966-07-01,99.2
+1966-08-01,101.8
+1966-09-01,89.1
+1966-10-01,76.6
+1966-11-01,72.8
+1966-12-01,60.2
+1967-01-01,59.1
+1967-02-01,61.4
+1967-03-01,91.5
+1967-04-01,113.7
+1967-05-01,132.0
+1967-06-01,125.4
+1967-07-01,125.3
+1967-08-01,127.4
+1967-09-01,121.9
+1967-10-01,135.4
+1967-11-01,118.4
+1967-12-01,80.1
+1968-01-01,80.5
+1968-02-01,84.6
+1968-03-01,126.6
+1968-04-01,162.0
+1968-05-01,140.9
+1968-06-01,137.9
+1968-07-01,139.8
+1968-08-01,136.6
+1968-09-01,134.2
+1968-10-01,140.8
+1968-11-01,127.1
+1968-12-01,96.4
+1969-01-01,101.5
+1969-02-01,90.1
+1969-03-01,131.9
+1969-04-01,159.0
+1969-05-01,155.5
+1969-06-01,147.3
+1969-07-01,125.2
+1969-08-01,124.9
+1969-09-01,129.3
+1969-10-01,123.4
+1969-11-01,94.6
+1969-12-01,84.1
+1970-01-01,66.4
+1970-02-01,74.3
+1970-03-01,114.7
+1970-04-01,128.4
+1970-05-01,125.0
+1970-06-01,135.2
+1970-07-01,140.8
+1970-08-01,128.7
+1970-09-01,130.9
+1970-10-01,140.9
+1970-11-01,126.9
+1970-12-01,121.4
+1971-01-01,110.6
+1971-02-01,102.2
+1971-03-01,167.9
+1971-04-01,201.1
+1971-05-01,198.5
+1971-06-01,193.8
+1971-07-01,194.3
+1971-08-01,204.5
+1971-09-01,173.8
+1971-10-01,179.7
+1971-11-01,173.7
+1971-12-01,152.1
+1972-01-01,149.1
+1972-02-01,152.2
+1972-03-01,203.9
+1972-04-01,211.6
+1972-05-01,225.8
+1972-06-01,223.1
+1972-07-01,206.5
+1972-08-01,228.6
+1972-09-01,203.0
+1972-10-01,216.5
+1972-11-01,185.7
+1972-12-01,150.5
+1973-01-01,146.6
+1973-02-01,138.0
+1973-03-01,200.0
+1973-04-01,205.0
+1973-05-01,234.0
+1973-06-01,202.6
+1973-07-01,202.6
+1973-08-01,197.2
+1973-09-01,148.4
+1973-10-01,147.1
+1973-11-01,133.3
+1973-12-01,90.4
+1974-01-01,84.5
+1974-02-01,109.4
+1974-03-01,124.8
+1974-04-01,159.5
+1974-05-01,149.0
+1974-06-01,147.6
+1974-07-01,126.6
+1974-08-01,111.1
+1974-09-01,98.3
+1974-10-01,96.7
+1974-11-01,75.1
+1974-12-01,55.1
+1975-01-01,56.1
+1975-02-01,54.7
+1975-03-01,80.2
+1975-04-01,97.9
+1975-05-01,116.1
+1975-06-01,110.3
+1975-07-01,119.3
+1975-08-01,117.3
+1975-09-01,111.9
+1975-10-01,123.6
+1975-11-01,96.9
+1975-12-01,76.1
+1976-01-01,72.5
+1976-02-01,89.9
+1976-03-01,118.4
+1976-04-01,137.2
+1976-05-01,147.9
+1976-06-01,154.2
+1976-07-01,136.6
+1976-08-01,145.9
+1976-09-01,151.8
+1976-10-01,148.4
+1976-11-01,127.1
+1976-12-01,107.4
+1977-01-01,81.3
+1977-02-01,112.5
+1977-03-01,173.6
+1977-04-01,182.2
+1977-05-01,201.3
+1977-06-01,197.6
+1977-07-01,189.8
+1977-08-01,194.0
+1977-09-01,177.7
+1977-10-01,193.1
+1977-11-01,154.8
+1977-12-01,129.2
+1978-01-01,88.6
+1978-02-01,101.3
+1978-03-01,172.1
+1978-04-01,197.5
+1978-05-01,211.0
+1978-06-01,216.0
+1978-07-01,192.2
+1978-08-01,190.9
+1978-09-01,180.5
+1978-10-01,192.1
+1978-11-01,158.6
+1978-12-01,119.5
+1979-01-01,88.2
+1979-02-01,84.5
+1979-03-01,152.9
+1979-04-01,161.0
+1979-05-01,189.1
+1979-06-01,191.8
+1979-07-01,164.2
+1979-08-01,170.3
+1979-09-01,163.7
+1979-10-01,169.0
+1979-11-01,118.7
+1979-12-01,91.6
+1980-01-01,73.1
+1980-02-01,79.9
+1980-03-01,85.1
+1980-04-01,96.2
+1980-05-01,91.7
+1980-06-01,116.4
+1980-07-01,120.1
+1980-08-01,129.9
+1980-09-01,138.3
+1980-10-01,152.7
+1980-11-01,112.9
+1980-12-01,95.9
+1981-01-01,84.5
+1981-02-01,71.9
+1981-03-01,107.8
+1981-04-01,123.0
+1981-05-01,109.9
+1981-06-01,105.8
+1981-07-01,99.9
+1981-08-01,86.3
+1981-09-01,84.1
+1981-10-01,87.2
+1981-11-01,64.6
+1981-12-01,59.1
+1982-01-01,47.2
+1982-02-01,51.3
+1982-03-01,78.2
+1982-04-01,84.1
+1982-05-01,98.8
+1982-06-01,91.1
+1982-07-01,106.8
+1982-08-01,96.0
+1982-09-01,106.4
+1982-10-01,110.5
+1982-11-01,108.9
+1982-12-01,82.9
+1983-01-01,91.3
+1983-02-01,96.3
+1983-03-01,134.6
+1983-04-01,135.8
+1983-05-01,174.9
+1983-06-01,173.2
+1983-07-01,161.6
+1983-08-01,176.8
+1983-09-01,154.9
+1983-10-01,159.3
+1983-11-01,136.0
+1983-12-01,108.3
+1984-01-01,109.1
+1984-02-01,130.0
+1984-03-01,137.5
+1984-04-01,172.7
+1984-05-01,180.7
+1984-06-01,184.0
+1984-07-01,162.1
+1984-08-01,147.4
+1984-09-01,148.5
+1984-10-01,152.3
+1984-11-01,126.2
+1984-12-01,98.9
+1985-01-01,105.4
+1985-02-01,95.4
+1985-03-01,145.0
+1985-04-01,175.8
+1985-05-01,170.2
+1985-06-01,163.2
+1985-07-01,160.7
+1985-08-01,160.7
+1985-09-01,147.7
+1985-10-01,173.0
+1985-11-01,124.1
+1985-12-01,120.5
+1986-01-01,115.6
+1986-02-01,107.2
+1986-03-01,151.0
+1986-04-01,188.2
+1986-05-01,186.6
+1986-06-01,183.6
+1986-07-01,172.0
+1986-08-01,163.8
+1986-09-01,154.0
+1986-10-01,154.8
+1986-11-01,115.6
+1986-12-01,113.0
+1987-01-01,105.1
+1987-02-01,102.8
+1987-03-01,141.2
+1987-04-01,159.3
+1987-05-01,158.0
+1987-06-01,162.9
+1987-07-01,152.4
+1987-08-01,143.6
+1987-09-01,152.0
+1987-10-01,139.1
+1987-11-01,118.8
+1987-12-01,85.4
+1988-01-01,78.2
+1988-02-01,90.2
+1988-03-01,128.8
+1988-04-01,153.2
+1988-05-01,140.2
+1988-06-01,150.2
+1988-07-01,137.0
+1988-08-01,136.8
+1988-09-01,131.1
+1988-10-01,135.1
+1988-11-01,113.0
+1988-12-01,94.2
+1989-01-01,100.1
+1989-02-01,85.8
+1989-03-01,117.8
+1989-04-01,129.4
+1989-05-01,131.7
+1989-06-01,143.2
+1989-07-01,134.7
+1989-08-01,122.4
+1989-09-01,109.3
+1989-10-01,130.1
+1989-11-01,96.6
+1989-12-01,75.0
+1990-01-01,99.2
+1990-02-01,86.9
+1990-03-01,108.5
+1990-04-01,119.0
+1990-05-01,121.1
+1990-06-01,117.8
+1990-07-01,111.2
+1990-08-01,102.8
+1990-09-01,93.1
+1990-10-01,94.2
+1990-11-01,81.4
+1990-12-01,57.4
+1991-01-01,52.5
+1991-02-01,59.1
+1991-03-01,73.8
+1991-04-01,99.7
+1991-05-01,97.7
+1991-06-01,103.4
+1991-07-01,103.5
+1991-08-01,94.7
+1991-09-01,86.6
+1991-10-01,101.8
+1991-11-01,75.6
+1991-12-01,65.6
+1992-01-01,71.6
+1992-02-01,78.8
+1992-03-01,111.6
+1992-04-01,107.6
+1992-05-01,115.2
+1992-06-01,117.8
+1992-07-01,106.2
+1992-08-01,109.9
+1992-09-01,106.0
+1992-10-01,111.8
+1992-11-01,84.5
+1992-12-01,78.6
+1993-01-01,70.5
+1993-02-01,74.6
+1993-03-01,95.5
+1993-04-01,117.8
+1993-05-01,120.9
+1993-06-01,128.5
+1993-07-01,115.3
+1993-08-01,121.8
+1993-09-01,118.5
+1993-10-01,123.3
+1993-11-01,102.3
+1993-12-01,98.7
+1994-01-01,76.2
+1994-02-01,83.5
+1994-03-01,134.3
+1994-04-01,137.6
+1994-05-01,148.8
+1994-06-01,136.4
+1994-07-01,127.8
+1994-08-01,139.8
+1994-09-01,130.1
+1994-10-01,130.6
+1994-11-01,113.4
+1994-12-01,98.5
+1995-01-01,84.5
+1995-02-01,81.6
+1995-03-01,103.8
+1995-04-01,116.9
+1995-05-01,130.5
+1995-06-01,123.4
+1995-07-01,129.1
+1995-08-01,135.8
+1995-09-01,122.4
+1995-10-01,126.2
+1995-11-01,107.2
+1995-12-01,92.8
+1996-01-01,90.7
+1996-02-01,95.9
+1996-03-01,116.0
+1996-04-01,146.6
+1996-05-01,143.9
+1996-06-01,138.0
+1996-07-01,137.5
+1996-08-01,144.2
+1996-09-01,128.7
+1996-10-01,130.8
+1996-11-01,111.5
+1996-12-01,93.1
+1997-01-01,82.2
+1997-02-01,94.7
+1997-03-01,120.4
+1997-04-01,142.3
+1997-05-01,136.3
+1997-06-01,140.4
+1997-07-01,134.6
+1997-08-01,126.5
+1997-09-01,139.2
+1997-10-01,139.0
+1997-11-01,112.4
+1997-12-01,106.0
+1998-01-01,91.2
+1998-02-01,101.1
+1998-03-01,132.6
+1998-04-01,144.9
+1998-05-01,143.3
+1998-06-01,159.6
+1998-07-01,156.0
+1998-08-01,147.5
+1998-09-01,141.5
+1998-10-01,155.5
+1998-11-01,124.2
+1998-12-01,119.6
+1999-01-01,106.8
+1999-02-01,110.2
+1999-03-01,147.3
+1999-04-01,144.6
+1999-05-01,153.2
+1999-06-01,149.4
+1999-07-01,152.6
+1999-08-01,152.9
+1999-09-01,140.3
+1999-10-01,142.9
+1999-11-01,127.4
+1999-12-01,113.6
+2000-01-01,104.0
+2000-02-01,119.7
+2000-03-01,133.4
+2000-04-01,149.5
+2000-05-01,152.9
+2000-06-01,146.3
+2000-07-01,135.0
+2000-08-01,141.4
+2000-09-01,128.9
+2000-10-01,139.7
+2000-11-01,117.1
+2000-12-01,100.7
+2001-01-01,106.4
+2001-02-01,108.2
+2001-03-01,133.2
+2001-04-01,151.3
+2001-05-01,154.0
+2001-06-01,155.2
+2001-07-01,154.6
+2001-08-01,141.5
+2001-09-01,133.1
+2001-10-01,139.8
+2001-11-01,121.0
+2001-12-01,104.6
+2002-01-01,110.4
+2002-02-01,120.4
+2002-03-01,138.2
+2002-04-01,148.8
+2002-05-01,165.5
+2002-06-01,160.3
+2002-07-01,155.9
+2002-08-01,147.0
+2002-09-01,155.6
+2002-10-01,146.8
+2002-11-01,133.0
+2002-12-01,123.1
+2003-01-01,117.8
+2003-02-01,109.7
+2003-03-01,147.2
+2003-04-01,151.2
+2003-05-01,165.0
+2003-06-01,174.5
+2003-07-01,175.8
+2003-08-01,163.8
+2003-09-01,171.3
+2003-10-01,173.5
+2003-11-01,153.7
+2003-12-01,144.2
+2004-01-01,124.5
+2004-02-01,126.4
+2004-03-01,173.8
+2004-04-01,179.5
+2004-05-01,187.6
+2004-06-01,172.3
+2004-07-01,182.0
+2004-08-01,185.9
+2004-09-01,164.0
+2004-10-01,181.3
+2004-11-01,138.1
+2004-12-01,140.2
+2005-01-01,142.9
+2005-02-01,149.1
+2005-03-01,156.2
+2005-04-01,184.6
+2005-05-01,197.9
+2005-06-01,192.8
+2005-07-01,187.6
+2005-08-01,192.0
+2005-09-01,187.9
+2005-10-01,180.4
+2005-11-01,160.7
+2005-12-01,136.0
+2006-01-01,153.0
+2006-02-01,145.1
+2006-03-01,165.9
+2006-04-01,160.5
+2006-05-01,190.2
+2006-06-01,170.2
+2006-07-01,160.9
+2006-08-01,146.8
+2006-09-01,150.1
+2006-10-01,130.6
+2006-11-01,115.2
+2006-12-01,112.4
+2007-01-01,95.0
+2007-02-01,103.1
+2007-03-01,123.8
+2007-04-01,135.6
+2007-05-01,136.5
+2007-06-01,137.8
+2007-07-01,127.9
+2007-08-01,121.2
+2007-09-01,101.5
+2007-10-01,115.0
+2007-11-01,88.8
+2007-12-01,68.9
+2008-01-01,70.8
+2008-02-01,78.4
+2008-03-01,82.2
+2008-04-01,89.5
+2008-05-01,91.7
+2008-06-01,102.5
+2008-07-01,86.7
+2008-08-01,76.4
+2008-09-01,73.9
+2008-10-01,68.2
+2008-11-01,47.5
+2008-12-01,37.7
+2009-01-01,31.9
+2009-02-01,39.8
+2009-03-01,42.7
+2009-04-01,42.5
+2009-05-01,52.2
+2009-06-01,59.1
+2009-07-01,56.8
+2009-08-01,52.9
+2009-09-01,52.6
+2009-10-01,44.5
+2009-11-01,42.3
+2009-12-01,36.6
+2010-01-01,38.9
+2010-02-01,40.7
+2010-03-01,54.7
+2010-04-01,62.0
+2010-05-01,56.2
+2010-06-01,53.8
+2010-07-01,51.5
+2010-08-01,56.3
+2010-09-01,53.0
+2010-10-01,45.4
+2010-11-01,40.6
+2010-12-01,33.8
+2011-01-01,40.2
+2011-02-01,35.4
+2011-03-01,49.9
+2011-04-01,49.0
+2011-05-01,54.0
+2011-06-01,60.5
+2011-07-01,57.6
+2011-08-01,54.5
+2011-09-01,58.8
+2011-10-01,53.2
+2011-11-01,53.0
+2011-12-01,42.7
+2012-01-01,47.2
+2012-02-01,49.7
+2012-03-01,58.0
+2012-04-01,66.8
+2012-05-01,67.8
+2012-06-01,74.7
+2012-07-01,69.2
+2012-08-01,69.0
+2012-09-01,75.8
+2012-10-01,77.0
+2012-11-01,62.2
+2012-12-01,63.2
+2013-01-01,58.7
+2013-02-01,66.1
+2013-03-01,83.3
+2013-04-01,76.3
+2013-05-01,87.2
+2013-06-01,80.7
+2013-07-01,84.0
+2013-08-01,80.4
+2013-09-01,78.4
+2013-10-01,78.4
+2013-11-01,83.8
+2013-12-01,67.6
+2014-01-01,60.7
+2014-02-01,65.1
+2014-03-01,80.2
+2014-04-01,94.9
+2014-05-01,92.5
+2014-06-01,87.3
+2014-07-01,101.0
+2014-08-01,86.2
+2014-09-01,94.2
+2014-10-01,92.0
+2014-11-01,75.8
+2014-12-01,73.4
+2015-01-01,73.0
+2015-02-01,61.9
+2015-03-01,79.7
+2015-04-01,108.5
+2015-05-01,99.6
+2015-06-01,112.3
+2015-07-01,107.2
+2015-08-01,99.2
+2015-09-01,111.6
+2015-10-01,90.9
+2015-11-01,89.9
+2015-12-01,78.1
+2016-01-01,74.3
+2016-02-01,84.1
+2016-03-01,90.7
+2016-04-01,106.2
+2016-05-01,105.0
+2016-06-01,111.6
+2016-07-01,115.2
+2016-08-01,102.8
+2016-09-01,95.0
+2016-10-01,114.5
+2016-11-01,87.8
+2016-12-01,86.5
+2017-01-01,82.3
+2017-02-01,87.8
+2017-03-01,97.1
+2017-04-01,105.2
+2017-05-01,106.0
+2017-06-01,116.3
+2017-07-01,112.3
+2017-08-01,102.6
+2017-09-01,104.4
+2017-10-01,109.6
+2017-11-01,97.9
+2017-12-01,81.4
+2018-01-01,91.6
+2018-02-01,89.7
+2018-03-01,107.2
+2018-04-01,117.5
+2018-05-01,123.8
+2018-06-01,111.5
diff --git a/statsmodels/tsa/tests/test_holtwinters.py b/statsmodels/tsa/tests/test_holtwinters.py
--- a/statsmodels/tsa/tests/test_holtwinters.py
+++ b/statsmodels/tsa/tests/test_holtwinters.py
@@ -1,55 +1,93 @@
 """
-Created on Wed Jul 12 09:44:01 2017
-
-@author: tvzyl
+Author: Terence L van Zyl
+Modified: Kevin Sheppard
 """
+import os
+import warnings
 
 import numpy as np
 import pandas as pd
-from numpy.testing import assert_almost_equal, assert_equal
+import pytest
+from numpy.testing import assert_almost_equal, assert_allclose
+
+from statsmodels.tools.sm_exceptions import EstimationWarning
 from statsmodels.tsa.holtwinters import (ExponentialSmoothing,
-                                         SimpleExpSmoothing, Holt)
-from pandas import DataFrame, DatetimeIndex
+                                         SimpleExpSmoothing, Holt, SMOOTHERS, PY_SMOOTHERS)
+
+base, _ = os.path.split(os.path.abspath(__file__))
+housing_data = pd.read_csv(os.path.join(base, 'results', 'housing-data.csv'))
+housing_data = housing_data.set_index('DATE')
+housing_data = housing_data.asfreq('MS')
+
+SEASONALS = ('add', 'mul', None)
+TRENDS = ('add', 'mul', None)
+
+
+def _simple_dbl_exp_smoother(x, alpha, beta, l0, b0, nforecast=0):
+    """
+    Simple, slow, direct implementation of double exp smoothing for testing
+    """
+    n = x.shape[0]
+    l = np.zeros(n)
+    b = np.zeros(n)
+    xhat = np.zeros(n)
+    f = np.zeros(nforecast)
+    l[0] = l0
+    b[0] = b0
+    # Special case the 0 observations since index -1 is not available
+    xhat[0] = l0 + b0
+    l[0] = alpha * x[0] + (1 - alpha) * (l0 + b0)
+    b[0] = beta * (l[0] - l0) + (1 - beta) * b0
+    for t in range(1, n):
+        # Obs in index t is the time t forecast for t + 1
+        l[t] = alpha * x[t] + (1 - alpha) * (l[t - 1] + b[t - 1])
+        b[t] = beta * (l[t] - l[t - 1]) + (1 - beta) * b[t - 1]
+
+    xhat[1:] = l[0:-1] + b[0:-1]
+    f[:] = l[-1] + np.arange(1, nforecast + 1) * b[-1]
+    err = x - xhat
+    return l, b, f, err, xhat
+
 
 class TestHoltWinters(object):
     @classmethod
     def setup_class(cls):
-        #Changed for backwards compatability with pandas
+        # Changed for backwards compatibility with pandas
 
-        #oildata_oil_json = '{"851990400000":446.6565229,"883526400000":454.4733065,"915062400000":455.662974,"946598400000":423.6322388,"978220800000":456.2713279,"1009756800000":440.5880501,"1041292800000":425.3325201,"1072828800000":485.1494479,"1104451200000":506.0481621,"1135987200000":526.7919833,"1167523200000":514.268889,"1199059200000":494.2110193}'
-        #oildata_oil = pd.read_json(oildata_oil_json, typ='Series').sort_index()
+        # oildata_oil_json = '{"851990400000":446.6565229,"883526400000":454.4733065,"915062400000":455.662974,"946598400000":423.6322388,"978220800000":456.2713279,"1009756800000":440.5880501,"1041292800000":425.3325201,"1072828800000":485.1494479,"1104451200000":506.0481621,"1135987200000":526.7919833,"1167523200000":514.268889,"1199059200000":494.2110193}'
+        # oildata_oil = pd.read_json(oildata_oil_json, typ='Series').sort_index()
         data = [446.65652290000003, 454.47330649999998, 455.66297400000002,
                 423.63223879999998, 456.27132790000002, 440.58805009999998,
                 425.33252010000001, 485.14944789999998, 506.04816210000001,
                 526.79198329999997, 514.26888899999994, 494.21101929999998]
-        index= ['1996-12-31 00:00:00', '1997-12-31 00:00:00', '1998-12-31 00:00:00',
-                '1999-12-31 00:00:00', '2000-12-31 00:00:00', '2001-12-31 00:00:00',
-                '2002-12-31 00:00:00', '2003-12-31 00:00:00', '2004-12-31 00:00:00',
-                '2005-12-31 00:00:00', '2006-12-31 00:00:00', '2007-12-31 00:00:00']
+        index = ['1996-12-31 00:00:00', '1997-12-31 00:00:00', '1998-12-31 00:00:00',
+                 '1999-12-31 00:00:00', '2000-12-31 00:00:00', '2001-12-31 00:00:00',
+                 '2002-12-31 00:00:00', '2003-12-31 00:00:00', '2004-12-31 00:00:00',
+                 '2005-12-31 00:00:00', '2006-12-31 00:00:00', '2007-12-31 00:00:00']
         oildata_oil = pd.Series(data, index)
         oildata_oil.index = pd.DatetimeIndex(oildata_oil.index,
-                                freq=pd.infer_freq(oildata_oil.index))
+                                             freq=pd.infer_freq(oildata_oil.index))
         cls.oildata_oil = oildata_oil
 
-        #air_ausair_json = '{"662601600000":17.5534,"694137600000":21.8601,"725760000000":23.8866,"757296000000":26.9293,"788832000000":26.8885,"820368000000":28.8314,"851990400000":30.0751,"883526400000":30.9535,"915062400000":30.1857,"946598400000":31.5797,"978220800000":32.577569,"1009756800000":33.477398,"1041292800000":39.021581,"1072828800000":41.386432,"1104451200000":41.596552}'
-        #air_ausair = pd.read_json(air_ausair_json, typ='Series').sort_index()
+        # air_ausair_json = '{"662601600000":17.5534,"694137600000":21.8601,"725760000000":23.8866,"757296000000":26.9293,"788832000000":26.8885,"820368000000":28.8314,"851990400000":30.0751,"883526400000":30.9535,"915062400000":30.1857,"946598400000":31.5797,"978220800000":32.577569,"1009756800000":33.477398,"1041292800000":39.021581,"1072828800000":41.386432,"1104451200000":41.596552}'
+        # air_ausair = pd.read_json(air_ausair_json, typ='Series').sort_index()
         data = [17.5534, 21.860099999999999, 23.886600000000001,
                 26.929300000000001, 26.888500000000001, 28.831399999999999,
                 30.075099999999999, 30.953499999999998, 30.185700000000001,
                 31.579699999999999, 32.577568999999997, 33.477398000000001,
                 39.021580999999998, 41.386431999999999, 41.596552000000003]
-        index= ['1990-12-31 00:00:00', '1991-12-31 00:00:00', '1992-12-31 00:00:00',
-                '1993-12-31 00:00:00', '1994-12-31 00:00:00', '1995-12-31 00:00:00',
-                '1996-12-31 00:00:00', '1997-12-31 00:00:00', '1998-12-31 00:00:00',
-                '1999-12-31 00:00:00', '2000-12-31 00:00:00', '2001-12-31 00:00:00',
-                '2002-12-31 00:00:00', '2003-12-31 00:00:00', '2004-12-31 00:00:00']
+        index = ['1990-12-31 00:00:00', '1991-12-31 00:00:00', '1992-12-31 00:00:00',
+                 '1993-12-31 00:00:00', '1994-12-31 00:00:00', '1995-12-31 00:00:00',
+                 '1996-12-31 00:00:00', '1997-12-31 00:00:00', '1998-12-31 00:00:00',
+                 '1999-12-31 00:00:00', '2000-12-31 00:00:00', '2001-12-31 00:00:00',
+                 '2002-12-31 00:00:00', '2003-12-31 00:00:00', '2004-12-31 00:00:00']
         air_ausair = pd.Series(data, index)
         air_ausair.index = pd.DatetimeIndex(air_ausair.index,
                                             freq=pd.infer_freq(air_ausair.index))
         cls.air_ausair = air_ausair
 
-        #livestock2_livestock_json = '{"31449600000":263.917747,"62985600000":268.307222,"94608000000":260.662556,"126144000000":266.639419,"157680000000":277.515778,"189216000000":283.834045,"220838400000":290.309028,"252374400000":292.474198,"283910400000":300.830694,"315446400000":309.286657,"347068800000":318.331081,"378604800000":329.37239,"410140800000":338.883998,"441676800000":339.244126,"473299200000":328.600632,"504835200000":314.255385,"536371200000":314.459695,"567907200000":321.413779,"599529600000":329.789292,"631065600000":346.385165,"662601600000":352.297882,"694137600000":348.370515,"725760000000":417.562922,"757296000000":417.12357,"788832000000":417.749459,"820368000000":412.233904,"851990400000":411.946817,"883526400000":394.697075,"915062400000":401.49927,"946598400000":408.270468,"978220800000":414.2428}'
-        #livestock2_livestock = pd.read_json(livestock2_livestock_json, typ='Series').sort_index()
+        # livestock2_livestock_json = '{"31449600000":263.917747,"62985600000":268.307222,"94608000000":260.662556,"126144000000":266.639419,"157680000000":277.515778,"189216000000":283.834045,"220838400000":290.309028,"252374400000":292.474198,"283910400000":300.830694,"315446400000":309.286657,"347068800000":318.331081,"378604800000":329.37239,"410140800000":338.883998,"441676800000":339.244126,"473299200000":328.600632,"504835200000":314.255385,"536371200000":314.459695,"567907200000":321.413779,"599529600000":329.789292,"631065600000":346.385165,"662601600000":352.297882,"694137600000":348.370515,"725760000000":417.562922,"757296000000":417.12357,"788832000000":417.749459,"820368000000":412.233904,"851990400000":411.946817,"883526400000":394.697075,"915062400000":401.49927,"946598400000":408.270468,"978220800000":414.2428}'
+        # livestock2_livestock = pd.read_json(livestock2_livestock_json, typ='Series').sort_index()
         data = [263.91774700000002, 268.30722200000002, 260.662556,
                 266.63941899999998, 277.51577800000001, 283.834045,
                 290.30902800000001, 292.474198, 300.83069399999999,
@@ -60,25 +98,25 @@ def setup_class(cls):
                 348.37051500000001, 417.56292200000001, 417.12356999999997,
                 417.749459, 412.233904, 411.94681700000001, 394.69707499999998,
                 401.49927000000002, 408.27046799999999, 414.24279999999999]
-        index= ['1970-12-31 00:00:00', '1971-12-31 00:00:00', '1972-12-31 00:00:00',
-                '1973-12-31 00:00:00', '1974-12-31 00:00:00', '1975-12-31 00:00:00',
-                '1976-12-31 00:00:00', '1977-12-31 00:00:00', '1978-12-31 00:00:00',
-                '1979-12-31 00:00:00', '1980-12-31 00:00:00', '1981-12-31 00:00:00',
-                '1982-12-31 00:00:00', '1983-12-31 00:00:00', '1984-12-31 00:00:00',
-                '1985-12-31 00:00:00', '1986-12-31 00:00:00', '1987-12-31 00:00:00',
-                '1988-12-31 00:00:00', '1989-12-31 00:00:00', '1990-12-31 00:00:00',
-                '1991-12-31 00:00:00', '1992-12-31 00:00:00', '1993-12-31 00:00:00',
-                '1994-12-31 00:00:00', '1995-12-31 00:00:00', '1996-12-31 00:00:00',
-                '1997-12-31 00:00:00', '1998-12-31 00:00:00', '1999-12-31 00:00:00',
-                '2000-12-31 00:00:00']
+        index = ['1970-12-31 00:00:00', '1971-12-31 00:00:00', '1972-12-31 00:00:00',
+                 '1973-12-31 00:00:00', '1974-12-31 00:00:00', '1975-12-31 00:00:00',
+                 '1976-12-31 00:00:00', '1977-12-31 00:00:00', '1978-12-31 00:00:00',
+                 '1979-12-31 00:00:00', '1980-12-31 00:00:00', '1981-12-31 00:00:00',
+                 '1982-12-31 00:00:00', '1983-12-31 00:00:00', '1984-12-31 00:00:00',
+                 '1985-12-31 00:00:00', '1986-12-31 00:00:00', '1987-12-31 00:00:00',
+                 '1988-12-31 00:00:00', '1989-12-31 00:00:00', '1990-12-31 00:00:00',
+                 '1991-12-31 00:00:00', '1992-12-31 00:00:00', '1993-12-31 00:00:00',
+                 '1994-12-31 00:00:00', '1995-12-31 00:00:00', '1996-12-31 00:00:00',
+                 '1997-12-31 00:00:00', '1998-12-31 00:00:00', '1999-12-31 00:00:00',
+                 '2000-12-31 00:00:00']
         livestock2_livestock = pd.Series(data, index)
         livestock2_livestock.index = pd.DatetimeIndex(
-                livestock2_livestock.index,
-                freq=pd.infer_freq(livestock2_livestock.index))
+            livestock2_livestock.index,
+            freq=pd.infer_freq(livestock2_livestock.index))
         cls.livestock2_livestock = livestock2_livestock
 
-        #aust_json = '{"1104537600000":41.727458,"1112313600000":24.04185,"1120176000000":32.328103,"1128124800000":37.328708,"1136073600000":46.213153,"1143849600000":29.346326,"1151712000000":36.48291,"1159660800000":42.977719,"1167609600000":48.901525,"1175385600000":31.180221,"1183248000000":37.717881,"1191196800000":40.420211,"1199145600000":51.206863,"1207008000000":31.887228,"1214870400000":40.978263,"1222819200000":43.772491,"1230768000000":55.558567,"1238544000000":33.850915,"1246406400000":42.076383,"1254355200000":45.642292,"1262304000000":59.76678,"1270080000000":35.191877,"1277942400000":44.319737,"1285891200000":47.913736}'
-        #aust = pd.read_json(aust_json, typ='Series').sort_index()
+        # aust_json = '{"1104537600000":41.727458,"1112313600000":24.04185,"1120176000000":32.328103,"1128124800000":37.328708,"1136073600000":46.213153,"1143849600000":29.346326,"1151712000000":36.48291,"1159660800000":42.977719,"1167609600000":48.901525,"1175385600000":31.180221,"1183248000000":37.717881,"1191196800000":40.420211,"1199145600000":51.206863,"1207008000000":31.887228,"1214870400000":40.978263,"1222819200000":43.772491,"1230768000000":55.558567,"1238544000000":33.850915,"1246406400000":42.076383,"1254355200000":45.642292,"1262304000000":59.76678,"1270080000000":35.191877,"1277942400000":44.319737,"1285891200000":47.913736}'
+        # aust = pd.read_json(aust_json, typ='Series').sort_index()
         data = [41.727457999999999, 24.04185, 32.328102999999999,
                 37.328707999999999, 46.213152999999998, 29.346326000000001,
                 36.482909999999997, 42.977719, 48.901524999999999,
@@ -87,14 +125,14 @@ def setup_class(cls):
                 43.772491000000002, 55.558566999999996, 33.850915000000001,
                 42.076383, 45.642291999999998, 59.766779999999997,
                 35.191876999999998, 44.319737000000003, 47.913736]
-        index= ['2005-03-01 00:00:00', '2005-06-01 00:00:00', '2005-09-01 00:00:00',
-                '2005-12-01 00:00:00', '2006-03-01 00:00:00', '2006-06-01 00:00:00',
-                '2006-09-01 00:00:00', '2006-12-01 00:00:00', '2007-03-01 00:00:00',
-                '2007-06-01 00:00:00', '2007-09-01 00:00:00', '2007-12-01 00:00:00',
-                '2008-03-01 00:00:00', '2008-06-01 00:00:00', '2008-09-01 00:00:00',
-                '2008-12-01 00:00:00', '2009-03-01 00:00:00', '2009-06-01 00:00:00',
-                '2009-09-01 00:00:00', '2009-12-01 00:00:00', '2010-03-01 00:00:00',
-                '2010-06-01 00:00:00', '2010-09-01 00:00:00', '2010-12-01 00:00:00']
+        index = ['2005-03-01 00:00:00', '2005-06-01 00:00:00', '2005-09-01 00:00:00',
+                 '2005-12-01 00:00:00', '2006-03-01 00:00:00', '2006-06-01 00:00:00',
+                 '2006-09-01 00:00:00', '2006-12-01 00:00:00', '2007-03-01 00:00:00',
+                 '2007-06-01 00:00:00', '2007-09-01 00:00:00', '2007-12-01 00:00:00',
+                 '2008-03-01 00:00:00', '2008-06-01 00:00:00', '2008-09-01 00:00:00',
+                 '2008-12-01 00:00:00', '2009-03-01 00:00:00', '2009-06-01 00:00:00',
+                 '2009-09-01 00:00:00', '2009-12-01 00:00:00', '2010-03-01 00:00:00',
+                 '2010-06-01 00:00:00', '2010-09-01 00:00:00', '2010-12-01 00:00:00']
         aust = pd.Series(data, index)
         aust.index = pd.DatetimeIndex(aust.index,
                                       freq=pd.infer_freq(aust.index))
@@ -105,90 +143,93 @@ def test_predict(self):
                                     seasonal='mul').fit()
         fit2 = ExponentialSmoothing(self.aust, seasonal_periods=4, trend='add',
                                     seasonal='mul').fit()
-#        fit3 = ExponentialSmoothing(self.aust, seasonal_periods=4, trend='add', seasonal='mul').fit(remove_bias=True, use_basinhopping=True)
+        # fit3 = ExponentialSmoothing(self.aust, seasonal_periods=4, trend='add',
+        # seasonal='mul').fit(remove_bias=True, use_basinhopping=True)
         assert_almost_equal(fit1.predict('2011-03-01 00:00:00',
                                          '2011-12-01 00:00:00'),
-                            [61.3083,37.3730,46.9652,51.5578], 3)
+                            [61.3083, 37.3730, 46.9652, 51.5578], 3)
         assert_almost_equal(fit2.predict(end='2011-12-01 00:00:00'),
-                            [61.3083,37.3730,46.9652,51.5578], 3)
-#        assert_almost_equal(fit3.predict('2010-10-01 00:00:00', '2010-10-01 00:00:00'), [49.087], 3)
+                            [61.3083, 37.3730, 46.9652, 51.5578], 3)
+
+    # assert_almost_equal(fit3.predict('2010-10-01 00:00:00', '2010-10-01 00:00:00'), [49.087], 3)
 
     def test_ndarray(self):
         fit1 = ExponentialSmoothing(self.aust.values, seasonal_periods=4,
                                     trend='add', seasonal='mul').fit()
-        assert_almost_equal(fit1.forecast(4), [61.3083,37.3730,46.9652,51.5578], 3)
+        assert_almost_equal(fit1.forecast(4), [61.3083, 37.3730, 46.9652, 51.5578], 3)
 
+    @pytest.mark.xfail(reason='Optimizer does not converge')
     def test_forecast(self):
         fit1 = ExponentialSmoothing(self.aust, seasonal_periods=4, trend='add',
                                     seasonal='add').fit()
         assert_almost_equal(fit1.forecast(steps=4),
-                            [60.9542,36.8505,46.1628,50.1272], 3)
+                            [60.9542, 36.8505, 46.1628, 50.1272], 3)
 
     def test_simple_exp_smoothing(self):
-        fit1 = SimpleExpSmoothing(self.oildata_oil).fit(0.2,optimized=False)
-        fit2 = SimpleExpSmoothing(self.oildata_oil).fit(0.6,optimized=False)
+        fit1 = SimpleExpSmoothing(self.oildata_oil).fit(0.2, optimized=False)
+        fit2 = SimpleExpSmoothing(self.oildata_oil).fit(0.6, optimized=False)
         fit3 = SimpleExpSmoothing(self.oildata_oil).fit()
         assert_almost_equal(fit1.forecast(1), [484.802468], 4)
         assert_almost_equal(fit1.level,
-                            [446.65652290,448.21987962,449.7084985,
-                             444.49324656,446.84886283,445.59670028,
-                             441.54386424,450.26498098,461.4216172,
-                             474.49569042,482.45033014,484.80246797], 4)
+                            [446.65652290, 448.21987962, 449.7084985,
+                             444.49324656, 446.84886283, 445.59670028,
+                             441.54386424, 450.26498098, 461.4216172,
+                             474.49569042, 482.45033014, 484.80246797], 4)
         assert_almost_equal(fit2.forecast(1), [501.837461], 4)
         assert_almost_equal(fit3.forecast(1), [496.493543], 4)
         assert_almost_equal(fit3.params['smoothing_level'], 0.891998, 4)
-        #has to be 3 for old python2.7 scipy versions
+        # has to be 3 for old python2.7 scipy versions
         assert_almost_equal(fit3.params['initial_level'], 447.478440, 3)
 
     def test_holt(self):
         fit1 = Holt(self.air_ausair).fit(smoothing_level=0.8,
                                          smoothing_slope=0.2, optimized=False)
         fit2 = Holt(self.air_ausair, exponential=True).fit(
-                    smoothing_level=0.8, smoothing_slope=0.2,
-                    optimized=False)
+            smoothing_level=0.8, smoothing_slope=0.2,
+            optimized=False)
         fit3 = Holt(self.air_ausair, damped=True).fit(smoothing_level=0.8,
                                                       smoothing_slope=0.2)
-        assert_almost_equal(fit1.forecast(5), [43.76,45.59,47.43,49.27,51.10], 2)
+        assert_almost_equal(fit1.forecast(5), [43.76, 45.59, 47.43, 49.27, 51.10], 2)
         assert_almost_equal(fit1.slope,
-                            [3.617628  ,3.59006512,3.33438212,3.23657639,2.69263502,
-                             2.46388914,2.2229097 ,1.95959226,1.47054601,1.3604894 ,
-                             1.28045881,1.20355193,1.88267152,2.09564416,1.83655482], 4)
+                            [3.617628, 3.59006512, 3.33438212, 3.23657639, 2.69263502,
+                             2.46388914, 2.2229097, 1.95959226, 1.47054601, 1.3604894,
+                             1.28045881, 1.20355193, 1.88267152, 2.09564416, 1.83655482], 4)
         assert_almost_equal(fit1.fittedfcast,
-                           [21.8601    ,22.032368  ,25.48461872,27.54058587,
-                            30.28813356,30.26106173,31.58122149,32.599234  ,
-                            33.24223906,32.26755382,33.07776017,33.95806605,
-                            34.77708354,40.05535303,43.21586036,43.75696849], 4)
+                            [21.8601, 22.032368, 25.48461872, 27.54058587,
+                             30.28813356, 30.26106173, 31.58122149, 32.599234,
+                             33.24223906, 32.26755382, 33.07776017, 33.95806605,
+                             34.77708354, 40.05535303, 43.21586036, 43.75696849], 4)
         assert_almost_equal(fit2.forecast(5),
-                            [44.60,47.24,50.04,53.01,56.15], 2)
+                            [44.60, 47.24, 50.04, 53.01, 56.15], 2)
         assert_almost_equal(fit3.forecast(5),
-                            [42.85,43.81,44.66,45.41,46.06], 2)
+                            [42.85, 43.81, 44.66, 45.41, 46.06], 2)
 
     def test_holt_damp(self):
         fit1 = SimpleExpSmoothing(self.livestock2_livestock).fit()
-        mod4 = Holt(self.livestock2_livestock,damped=True)
+        mod4 = Holt(self.livestock2_livestock, damped=True)
         fit4 = mod4.fit(damping_slope=0.98)
-        mod5 = Holt(self.livestock2_livestock,exponential=True,damped=True)
+        mod5 = Holt(self.livestock2_livestock, exponential=True, damped=True)
         fit5 = mod5.fit()
-        #We accept the below values as we getting a better SSE than text book
-        assert_almost_equal(fit1.params['smoothing_level'],1.00, 2)
-        assert_almost_equal(fit1.params['smoothing_slope'],np.NaN, 2)
-        assert_almost_equal(fit1.params['damping_slope'],np.NaN, 2)
-        assert_almost_equal(fit1.params['initial_level'],263.92, 2)
-        assert_almost_equal(fit1.params['initial_slope'],np.NaN, 2)
-        assert_almost_equal(fit1.sse,6761.35, 2) #6080.26
-
-        assert_almost_equal(fit4.params['smoothing_level'],0.98, 2)
-        assert_almost_equal(fit4.params['smoothing_slope'],0.00, 2)
-        assert_almost_equal(fit4.params['damping_slope'],0.98, 2)
-        assert_almost_equal(fit4.params['initial_level'],257.36, 2)
-        assert_almost_equal(fit4.params['initial_slope'],6.51, 2)
-        assert_almost_equal(fit4.sse,6036.56, 2) #6080.26
-        assert_almost_equal(fit5.params['smoothing_level'],0.97, 2)
-        assert_almost_equal(fit5.params['smoothing_slope'],0.00, 2)
-        assert_almost_equal(fit5.params['damping_slope'],0.98, 2)
-        assert_almost_equal(fit5.params['initial_level'],258.95, 2)
-        assert_almost_equal(fit5.params['initial_slope'],1.02, 2)
-        assert_almost_equal(fit5.sse,6082.00, 2) #6100.11
+        # We accept the below values as we getting a better SSE than text book
+        assert_almost_equal(fit1.params['smoothing_level'], 1.00, 2)
+        assert_almost_equal(fit1.params['smoothing_slope'], np.NaN, 2)
+        assert_almost_equal(fit1.params['damping_slope'], np.NaN, 2)
+        assert_almost_equal(fit1.params['initial_level'], 263.92, 2)
+        assert_almost_equal(fit1.params['initial_slope'], np.NaN, 2)
+        assert_almost_equal(fit1.sse, 6761.35, 2)  # 6080.26
+
+        assert_almost_equal(fit4.params['smoothing_level'], 0.98, 2)
+        assert_almost_equal(fit4.params['smoothing_slope'], 0.00, 2)
+        assert_almost_equal(fit4.params['damping_slope'], 0.98, 2)
+        assert_almost_equal(fit4.params['initial_level'], 257.36, 2)
+        assert_almost_equal(fit4.params['initial_slope'], 6.51, 2)
+        assert_almost_equal(fit4.sse, 6036.56, 2)  # 6080.26
+        assert_almost_equal(fit5.params['smoothing_level'], 0.97, 2)
+        assert_almost_equal(fit5.params['smoothing_slope'], 0.00, 2)
+        assert_almost_equal(fit5.params['damping_slope'], 0.98, 2)
+        assert_almost_equal(fit5.params['initial_level'], 258.95, 2)
+        assert_almost_equal(fit5.params['initial_slope'], 1.02, 2)
+        assert_almost_equal(fit5.sse, 6082.00, 2)  # 6100.11
 
     def test_hw_seasonal(self):
         fit1 = ExponentialSmoothing(self.aust, seasonal_periods=4,
@@ -196,10 +237,12 @@ def test_hw_seasonal(self):
                                     seasonal='additive').fit(use_boxcox=True)
         fit2 = ExponentialSmoothing(self.aust, seasonal_periods=4, trend='add',
                                     seasonal='mul').fit(use_boxcox=True)
-        fit3 = ExponentialSmoothing(self.aust, seasonal_periods=4,
-                                    seasonal='add').fit(use_boxcox=True)
-        fit4 = ExponentialSmoothing(self.aust, seasonal_periods=4,
-                                    seasonal='mul').fit(use_boxcox=True)
+        assert_almost_equal(fit1.forecast(8),
+                            [61.34, 37.24, 46.84, 51.01, 64.47, 39.78, 49.64, 53.90],
+                            2)
+        assert_almost_equal(fit2.forecast(8),
+                            [60.97, 36.99, 46.71, 51.48, 64.46, 39.02, 49.29, 54.32],
+                            2)
         fit5 = ExponentialSmoothing(self.aust, seasonal_periods=4,
                                     trend='mul', seasonal='add'
                                     ).fit(use_boxcox='log')
@@ -207,20 +250,163 @@ def test_hw_seasonal(self):
                                     trend='multiplicative',
                                     seasonal='multiplicative'
                                     ).fit(use_boxcox='log')
-        assert_almost_equal(fit1.forecast(8),
-                            [61.34,37.24,46.84,51.01,64.47,39.78,49.64,53.90],
-                            2)
-        assert_almost_equal(fit2.forecast(8),
-                            [60.97,36.99,46.71,51.48,64.46,39.02,49.29,54.32],
-                            2)
+        # Skip since estimator is unstable
+        # assert_almost_equal(fit5.forecast(1), [60.60], 2)
+        # assert_almost_equal(fit6.forecast(1), [61.47], 2)
+
+    @pytest.mark.xpass(reason='Optimizer does not converge')
+    def test_hw_seasonal_buggy(self):
+        fit3 = ExponentialSmoothing(self.aust, seasonal_periods=4,
+                                    seasonal='add').fit(use_boxcox=True)
         assert_almost_equal(fit3.forecast(8),
-                            [59.91,35.71,44.64,47.62,59.91,35.71,44.64,47.62],
+                            [59.91, 35.71, 44.64, 47.62, 59.91, 35.71, 44.64, 47.62],
                             2)
+        fit4 = ExponentialSmoothing(self.aust, seasonal_periods=4,
+                                    seasonal='mul').fit(use_boxcox=True)
         assert_almost_equal(fit4.forecast(8),
-                            [60.71,35.70,44.63,47.55,60.71,35.70,44.63,47.55],
+                            [60.71, 35.70, 44.63, 47.55, 60.71, 35.70, 44.63, 47.55],
                             2)
-        assert_almost_equal(fit5.forecast(1), [78.53], 2)
-        assert_almost_equal(fit6.forecast(1), [54.82], 2)
 
-    def test_raises(self):
-        pass
+
+@pytest.mark.parametrize('trend_seasonal', (('mul', None), (None, 'mul'), ('mul', 'mul')))
+def test_negative_multipliative(trend_seasonal):
+    trend, seasonal = trend_seasonal
+    y = -np.ones(100)
+    with pytest.raises(ValueError):
+        ExponentialSmoothing(y, trend=trend, seasonal=seasonal, seasonal_periods=10)
+
+
+@pytest.mark.parametrize('seasonal', SEASONALS)
+def test_dampen_no_trend(seasonal):
+    y = -np.ones(100)
+    with pytest.raises(ValueError):
+        ExponentialSmoothing(housing_data, trend=False, seasonal=seasonal, damped=True,
+                             seasonal_periods=10)
+
+
+@pytest.mark.parametrize('seasonal', ('add', 'mul'))
+def test_invalid_seasonal(seasonal):
+    y = pd.Series(-np.ones(100),index=pd.date_range('2000-1-1', periods=100, freq='MS'))
+    with pytest.raises(ValueError):
+        ExponentialSmoothing(y, seasonal=seasonal, seasonal_periods=1)
+
+
+def test_2d_data():
+    with pytest.raises(ValueError):
+        ExponentialSmoothing(pd.concat([housing_data, housing_data], 1)).fit()
+
+
+def test_infer_freq():
+    hd2 = housing_data.copy()
+    hd2.index = list(hd2.index)
+    with warnings.catch_warnings(record=True) as w:
+        mod = ExponentialSmoothing(hd2, trend='add', seasonal='add')
+        assert len(w) == 1
+        assert 'ValueWarning' in str(w[0])
+    assert mod.seasonal_periods == 12
+
+
+@pytest.mark.parametrize('trend', TRENDS)
+@pytest.mark.parametrize('seasonal', SEASONALS)
+def test_start_params(trend, seasonal):
+    mod = ExponentialSmoothing(housing_data, trend='add', seasonal='add')
+    res = mod.fit()
+    res2 = mod.fit(start_params=res.mle_retvals.x)
+    assert res2.sse <= res.sse
+
+
+def test_no_params_to_optimize():
+    mod = ExponentialSmoothing(housing_data)
+    with pytest.warns(EstimationWarning):
+        mod.fit(smoothing_level=0.5, initial_level=housing_data.iloc[0])
+
+
+def test_invalid_start_param_length():
+    mod = ExponentialSmoothing(housing_data)
+    with pytest.raises(ValueError):
+        mod.fit(start_params=np.array([0.5]))
+
+
+def test_basin_hopping():
+    mod = ExponentialSmoothing(housing_data, trend='add')
+    res = mod.fit()
+    res2 = mod.fit(use_basinhopping=True)
+    assert res2.sse <= res.sse
+
+
+def test_debiased():
+    mod = ExponentialSmoothing(housing_data, trend='add')
+    res = mod.fit()
+    res2 = mod.fit(remove_bias=True)
+    assert np.any(res.fittedvalues != res2.fittedvalues)
+
+
+@pytest.mark.parametrize('trend', TRENDS)
+@pytest.mark.parametrize('seasonal', SEASONALS)
+def test_float_boxcox_smoke(trend, seasonal):
+    res = ExponentialSmoothing(housing_data, trend=trend, seasonal=seasonal).fit(use_boxcox=0.5)
+    assert_allclose(res.params['use_boxcox'], 0.5)
+
+
+@pytest.mark.parametrize('trend', TRENDS)
+@pytest.mark.parametrize('seasonal', SEASONALS)
+def test_equivalence_cython_python(trend, seasonal):
+    mod = ExponentialSmoothing(housing_data, trend=trend, seasonal=seasonal)
+    res = mod.fit()
+    res.summary()  # Smoke test
+    params = res.params
+    nobs = housing_data.shape[0]
+    y = np.squeeze(np.asarray(housing_data))
+    m = 12 if seasonal else 0
+    l = np.zeros(nobs)
+    b = np.zeros(nobs)
+    s = np.zeros(nobs + m - 1)
+    p = np.zeros(6 + m)
+    max_seen = np.finfo(np.double).max
+    alpha = params['smoothing_level']
+    beta = params['smoothing_slope']
+    gamma = params['smoothing_seasonal']
+    phi = params['damping_slope']
+    phi = 1.0 if np.isnan(phi) else phi
+    l0 = params['initial_level']
+    b0 = params['initial_slope']
+    p[:6] = alpha, beta, gamma, l0, b0, phi
+    if seasonal:
+        p[6:] = params['initial_seasons']
+    xi = np.ones_like(p).astype(np.uint8)
+    py_func = PY_SMOOTHERS[(seasonal, trend)]
+    cy_func = SMOOTHERS[(seasonal, trend)]
+    p_copy = p.copy()
+    sse_cy = cy_func(p, xi, p_copy, y, l, b, s, m, nobs, max_seen)
+    sse_py = py_func(p, xi, p_copy, y, l, b, s, m, nobs, max_seen)
+    assert_allclose(sse_py, sse_cy)
+
+
+def test_direct_holt_add():
+    mod = SimpleExpSmoothing(housing_data)
+    res = mod.fit()
+    x = np.squeeze(np.asarray(mod.endog))
+    alpha = res.params['smoothing_level']
+    l, b, f, err, xhat = _simple_dbl_exp_smoother(x, alpha, beta=0.0,
+                                                  l0=res.params['initial_level'], b0=0.0,
+                                                  nforecast=5)
+
+    assert_allclose(l, res.level)
+    assert_allclose(f, res.level.iloc[-1] * np.ones(5))
+    assert_allclose(f, res.forecast(5))
+
+    mod = ExponentialSmoothing(housing_data, trend='add')
+    res = mod.fit()
+    x = np.squeeze(np.asarray(mod.endog))
+    alpha = res.params['smoothing_level']
+    beta = res.params['smoothing_slope']
+    l, b, f, err, xhat = _simple_dbl_exp_smoother(x, alpha, beta=beta,
+                                                  l0=res.params['initial_level'],
+                                                  b0=res.params['initial_slope'], nforecast=5)
+
+    assert_allclose(xhat, res.fittedvalues)
+    assert_allclose(l + b, res.level + res.slope)
+    assert_allclose(l, res.level)
+    assert_allclose(b, res.slope)
+    assert_allclose(f, res.level.iloc[-1] + res.slope.iloc[-1] * np.array([1, 2, 3, 4, 5]))
+    assert_allclose(f, res.forecast(5))