Repository: statsmodels/statsmodels

Issue:
BUG: Exponential smoothing non zero-indexed rangeIndexes have mutated forecast steps (first digit multipled by two?)
I understand that the documentation of `ExponentialSmoothing().fit().forecast()` or `.predict()` requires the passed observations to be either zero-indexed or be datestring/datetime objects.

However, when this is not true, the function does not throw an error, instead it returns forecasts/predictions with some mutated number of steps. For example,

```
import statsmodels
from statsmodels.tsa.holtwinters import ExponentialSmoothing
import pandas as pd

print('version:', statsmodels.__version__)
for ix in [0, 10, 100, 1000, 2000]:

    ts_1 = pd.Series([85601, 89662, 85122, 84400, 78250, 84434, 71072, 70357, 72635, 73210],
                     index=range(ix,ix+10))
    model = ExponentialSmoothing(ts_1, trend='add', damped=False).fit()
    if (ts_1.index != model.fittedvalues.index).all():
        print('Warning: training index does not match.')
    print(f'training index: [{model.fittedvalues.index[0]} - {model.fittedvalues.index[-1]}] ||', 'forecast index:', list(model.forecast(steps=10).index))
#     print(f'training index: [{model.fittedvalues.index[0]} - {model.fittedvalues.index[-1]}] ||', 'forecast index:', list(model.predict(start=ix+10, end=ix+20).index))
```

We should have expected either 
1) an error 
2) coercing the index to be zero-indexed and have a `An unsupported index was provided` warning (e.g. if we change the series to have `index=list(range(ix, ix+10))`, it throws that warning and returns a forecast of the next steps, indexed based on the length of observations)
3) (ideal) forecasts of the actual next steps, indexed to be consecutive with the passed observations. 

Instead, we get an output like:
```
version: 0.11.1
training index: [0 - 9] || forecast index: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
training index: [10 - 19] || forecast index: [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
training index: [100 - 109] || forecast index: [210, 211, 212, 213, 214, 215, 216, 217, 218, 219]
training index: [1000 - 1009] || forecast index: [2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019]
training index: [2000 - 2009] || forecast index: [4010, 4011, 4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019]
```

#### Output of ``import statsmodels.api as sm; sm.show_versions()``

INSTALLED VERSIONS
------------------
Python: 3.7.4.final.0
OS: Darwin 19.3.0 Darwin Kernel Version 19.3.0: Thu Jan  9 20:58:23 PST 2020; root:xnu-6153.81.5~1/RELEASE_X86_64 x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8

statsmodels
===========

Installed: 0.11.1 (/usr/local/lib/python3.7/site-packages/statsmodels)

Required Dependencies
=====================

cython: Not installed
numpy: 1.17.3 (/usr/local/lib/python3.7/site-packages/numpy)
scipy: 1.3.1 (/usr/local/lib/python3.7/site-packages/scipy)
pandas: 0.25.2 (/usr/local/lib/python3.7/site-packages/pandas)
    dateutil: 2.8.1 (/Users/aaronlee/Library/Python/3.7/lib/python/site-packages/dateutil)
patsy: 0.5.1 (/usr/local/lib/python3.7/site-packages/patsy)

Optional Dependencies
=====================

matplotlib: 3.1.1 (/usr/local/lib/python3.7/site-packages/matplotlib)
    backend: module://ipykernel.pylab.backend_inline 
cvxopt: Not installed
joblib: 0.14.0 (/usr/local/lib/python3.7/site-packages/joblib)

Developer Tools
================

IPython: 7.12.0 (/Users/aaronlee/Library/Python/3.7/lib/python/site-packages/IPython)
    jinja2: 2.10.3 (/usr/local/lib/python3.7/site-packages/jinja2)
sphinx: Not installed
    pygments: 2.5.2 (/Users/aaronlee/Library/Python/3.7/lib/python/site-packages/pygments)
pytest: Not installed
virtualenv: Not installed


Discussion:
Thanks for letting us know about this!

Resolving patch:
diff --git a/statsmodels/tsa/holtwinters.py b/statsmodels/tsa/holtwinters.py
--- a/statsmodels/tsa/holtwinters.py
+++ b/statsmodels/tsa/holtwinters.py
@@ -348,8 +348,12 @@ def forecast(self, steps=1):
         """
         try:
             freq = getattr(self.model._index, 'freq', 1)
-            start = self.model._index[-1] + freq
-            end = self.model._index[-1] + steps * freq
+            if isinstance(freq, int):
+                start = self.model._index.shape[0]
+                end = start + steps - 1
+            else:
+                start = self.model._index[-1] + freq
+                end = self.model._index[-1] + steps * freq
             return self.model.predict(self.params, start=start, end=end)
         except (AttributeError, ValueError):
             # May occur when the index does not have a freq
@@ -912,7 +916,10 @@ def predict(self, params, start=None, end=None):
         """
         if start is None:
             freq = getattr(self._index, 'freq', 1)
-            start = self._index[-1] + freq
+            if isinstance(freq, int):
+                start = self._index.shape[0] + freq
+            else:
+                start = self._index[-1] + freq
         start, end, out_of_sample, prediction_index = self._get_prediction_index(
             start=start, end=end)
         if out_of_sample > 0:
@@ -1090,6 +1097,8 @@ def fit(self, smoothing_level=None, smoothing_slope=None, smoothing_seasonal=Non
                     # solution to parameters
                     _bounds = [bnd for bnd, flag in zip(bounds, xi) if flag]
                     lb, ub = np.asarray(_bounds).T.astype(float)
+                    ub[np.isnan(ub)] = np.inf
+                    lb[np.isnan(lb)] = -np.inf
                     initial_p = p[xi]
 
                     # Ensure strictly inbounds


Test patch:
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
@@ -879,3 +879,14 @@ def test_simulate_boxcox(austourists):
     mean = np.mean(res, axis=1)
 
     assert np.all(np.abs(mean - expected) < 5)
+
+
+@pytest.mark.parametrize("ix", [10, 100, 1000, 2000])
+def test_forecast_index(ix):
+    # GH 6549
+    ts_1 = pd.Series([85601, 89662, 85122, 84400, 78250, 84434, 71072, 70357, 72635, 73210],
+                     index=range(ix, ix + 10))
+    model = ExponentialSmoothing(ts_1, trend='add', damped=False).fit()
+    index = model.forecast(steps=10).index
+    assert index[0] == ix + 10
+    assert index[-1] == ix + 19