Repository: statsmodels/statsmodels

Issue:
BUG: UECM.from_ardl residuals
#### Describe the bug

When using statsmodels.tsa.ardl.UECM.from_ardl, the residuals method of the resulting UECM subtracts the fitted values of the UECM (i.e., in differences) from the endogenous value of the ARDL (i.e., in levels). This flows through into the methods for testing residuals (serial correlation, etc.).

As a stop-gap, users can perform tests on the residuals from the ARDL. The UECM should have the same residuals as the equivalent ARDL, since no transformation is made to the residuals in deriving the UECM from the ARDL.

#### Code Sample, a copy-pastable example if possible


```python
import statsmodels.api as sm
import pandas as pd
import numpy as np
import matplotlib as mp

# simulate cointegrated series
w = [1,-1]
N = 100
S = np.ones([N,len(w)])*2000
kappa = 20
sigma = 20
dt = 1/N
corr = 0.6
C = np.ones([len(w),len(w)])*corr+(1-corr)*np.eye(len(w))
for t in range(1,N):
    eps = np.random.multivariate_normal([0]*len(w),C)
    for i in range(len(w)):
        si = 0
        for j in range(len(w)):
            si += w[j]/w[i]*S[t-1,j]
        S[t,i] = S[t-1,i] - kappa*dt*si + sigma**2*np.sqrt(dt)*eps[i]
        
mp.pyplot.plot(S)
mp.pyplot.show()

# fit ardl and uecm
ardl = sm.tsa.ARDL(S[:,[0]], 1, S[:,[1]], 1)
ardlfit = ardl.fit()
uecm = sm.tsa.UECM.from_ardl(ardl)
uecmfit = uecm.fit()

# observed
uecmfit.resid

# expected
ardlfit.resid

# check equality of UECM endog variable and observation in levels
S[:,0].squeeze() == uecmfit.model.endog.squeeze()

```



Discussion:

Patch:
diff --git a/statsmodels/tools/docstring.py b/statsmodels/tools/docstring.py
index 272aaa4c9..b7492410b 100644
--- a/statsmodels/tools/docstring.py
+++ b/statsmodels/tools/docstring.py
@@ -638,7 +638,7 @@ class Docstring:
         block_name = " ".join(map(str.capitalize, block_name.split(" ")))
         if block_name not in self._ds:
             raise ValueError(
-                "{} is not a block in the " "docstring".format(block_name)
+                "{} is not a block in the docstring".format(block_name)
             )
         if not isinstance(block, list) and isinstance(
             self._ds[block_name], list
diff --git a/statsmodels/tsa/ardl/model.py b/statsmodels/tsa/ardl/model.py
index ac20f5a3e..c7450ab56 100644
--- a/statsmodels/tsa/ardl/model.py
+++ b/statsmodels/tsa/ardl/model.py
@@ -4,18 +4,12 @@ from statsmodels.compat.pandas import Appender, Substitution, call_cached_func
 from statsmodels.compat.python import Literal
 
 from collections import defaultdict
+from collections.abc import Hashable, Mapping, Sequence
 import datetime as dt
 from itertools import combinations, product
 import textwrap
 from types import SimpleNamespace
-from typing import (
-    TYPE_CHECKING,
-    Any,
-    NamedTuple,
-    Optional,
-    Union,
-)
-from collections.abc import Hashable, Mapping, Sequence
+from typing import TYPE_CHECKING, Any, NamedTuple, Optional, Union
 import warnings
 
 import numpy as np
@@ -111,14 +105,10 @@ def _check_order(order: int | Sequence[int] | None, causal: bool) -> bool:
         return True
     for v in order:
         if not isinstance(v, (int, np.integer)):
-            raise TypeError(
-                "sequence orders must contain non-negative integer values"
-            )
+            raise TypeError("sequence orders must contain non-negative integer values")
     order = [int(v) for v in order]
     if len(set(order)) != len(order) or min(order) < 0:
-        raise ValueError(
-            "sequence orders must contain distinct non-negative values"
-        )
+        raise ValueError("sequence orders must contain distinct non-negative values")
     if int(causal) and min(order) < 1:
         raise ValueError(
             "sequence orders must be strictly positive when causal is True"
@@ -362,9 +352,7 @@ class ARDL(AutoReg):
             if isinstance(fixed, pd.DataFrame):
                 self._fixed_names = list(fixed.columns)
             else:
-                self._fixed_names = [
-                    f"z.{i}" for i in range(fixed_arr.shape[1])
-                ]
+                self._fixed_names = [f"z.{i}" for i in range(fixed_arr.shape[1])]
             self._fixed = fixed_arr
         else:
             self._fixed = np.empty((self.data.endog.shape[0], 0))
@@ -463,9 +451,7 @@ class ARDL(AutoReg):
         if self._x.shape[1] == 0:
             return np.empty((0,)), np.empty((0, 0)), np.empty((0, 0))
         ols_mod = OLS(self._y, self._x)
-        ols_res = ols_mod.fit(
-            cov_type=cov_type, cov_kwds=cov_kwds, use_t=use_t
-        )
+        ols_res = ols_mod.fit(cov_type=cov_type, cov_kwds=cov_kwds, use_t=use_t)
         cov_params = ols_res.cov_params()
         use_t = ols_res.use_t
         if cov_type == "nonrobust" and not use_t:
@@ -542,9 +528,7 @@ class ARDL(AutoReg):
         params, cov_params, norm_cov_params = self._fit(
             cov_type=cov_type, cov_kwds=cov_kwds, use_t=use_t
         )
-        res = ARDLResults(
-            self, params, cov_params, norm_cov_params, use_t=use_t
-        )
+        res = ARDLResults(self, params, cov_params, norm_cov_params, use_t=use_t)
         return ARDLResultsWrapper(res)
 
     def _construct_regressors(
@@ -553,9 +537,7 @@ class ARDL(AutoReg):
         """Construct and format model regressors"""
         # TODO: Missing adjustment
         self._maxlag = max(self._lags) if self._lags else 0
-        _endog_reg, _endog = lagmat(
-            self.data.endog, self._maxlag, original="sep"
-        )
+        _endog_reg, _endog = lagmat(self.data.endog, self._maxlag, original="sep")
         assert isinstance(_endog, np.ndarray)
         assert isinstance(_endog_reg, np.ndarray)
         self._endog_reg, self._endog = _endog_reg, _endog
@@ -649,9 +631,7 @@ class ARDL(AutoReg):
 
         if (end + 1) < self.endog.shape[0] and exog is None and fixed is None:
             adjusted_start = max(start - self._hold_back, 0)
-            return pad_x(
-                self._x[adjusted_start : end + 1 - self._hold_back], pad
-            )
+            return pad_x(self._x[adjusted_start : end + 1 - self._hold_back], pad)
 
         # If anything changed, rebuild x array
         exog = self.data.exog if exog is None else np.asarray(exog)
@@ -757,8 +737,7 @@ class ARDL(AutoReg):
                     )
                 if sorted(arr.columns) != sorted(self.data.orig_exog.columns):
                     raise ValueError(
-                        f"{name} must have the same columns as the original "
-                        "exog"
+                        f"{name} must have the same columns as the original exog"
                     )
             else:
                 arr = array_like(arr, name, ndim=2, optional=False)
@@ -778,9 +757,7 @@ class ARDL(AutoReg):
         if exog is not None:
             exog = check_exog(exog, "exog", self.data.orig_exog, True)
         if exog_oos is not None:
-            exog_oos = check_exog(
-                exog_oos, "exog_oos", self.data.orig_exog, False
-            )
+            exog_oos = check_exog(exog_oos, "exog_oos", self.data.orig_exog, False)
         if fixed is not None:
             fixed = check_exog(fixed, "fixed", self._fixed, True)
         if fixed_oos is not None:
@@ -824,12 +801,8 @@ class ARDL(AutoReg):
         if self.exog is not None and exog_oos is None and num_oos:
             exog_oos = np.full((num_oos, self.exog.shape[1]), np.nan)
             if isinstance(self.data.orig_exog, pd.DataFrame):
-                exog_oos = pd.DataFrame(
-                    exog_oos, columns=self.data.orig_exog.columns
-                )
-        x = self._forecasting_x(
-            start, end, num_oos, exog, exog_oos, fixed, fixed_oos
-        )
+                exog_oos = pd.DataFrame(exog_oos, columns=self.data.orig_exog.columns)
+        x = self._forecasting_x(start, end, num_oos, exog, exog_oos, fixed, fixed_oos)
         if dynamic is False:
             dynamic_start = end + 1 - start
         else:
@@ -1018,9 +991,7 @@ class ARDLResults(AutoRegResults):
         scale: float = 1.0,
         use_t: bool = False,
     ):
-        super().__init__(
-            model, params, normalized_cov_params, scale, use_t=use_t
-        )
+        super().__init__(model, params, normalized_cov_params, scale, use_t=use_t)
         self._cache = {}
         self._params = params
         self._nobs = model.nobs
@@ -1301,9 +1272,7 @@ class ARDLResults(AutoRegResults):
         ]
 
         smry = Summary()
-        smry.add_table_2cols(
-            self, gleft=top_left, gright=top_right, title=title
-        )
+        smry.add_table_2cols(self, gleft=top_left, gright=top_right, title=title)
         smry.add_table_params(self, alpha=alpha, use_t=False)
 
         return smry
@@ -1337,15 +1306,11 @@ class ARDLOrderSelectionResults(AROrderSelectionResults):
         def _to_dict(d):
             return d[0], dict(d[1:])
 
-        self._aic = pd.Series(
-            {v[0]: _to_dict(k) for k, v in ics.items()}, dtype=object
-        )
+        self._aic = pd.Series({v[0]: _to_dict(k) for k, v in ics.items()}, dtype=object)
         self._aic.index.name = self._aic.name = "AIC"
         self._aic = self._aic.sort_index()
 
-        self._bic = pd.Series(
-            {v[1]: _to_dict(k) for k, v in ics.items()}, dtype=object
-        )
+        self._bic = pd.Series({v[1]: _to_dict(k) for k, v in ics.items()}, dtype=object)
         self._bic.index.name = self._bic.name = "BIC"
         self._bic = self._bic.sort_index()
 
@@ -1789,9 +1754,7 @@ class UECM(ARDL):
         if isinstance(order, Mapping):
             for k, v in order.items():
                 if not isinstance(v, _INT_TYPES) and v is not None:
-                    raise TypeError(
-                        "order values must be positive integers or None"
-                    )
+                    raise TypeError("order values must be positive integers or None")
         elif not (isinstance(order, _INT_TYPES) or order is None):
             raise TypeError(
                 "order must be None, a positive integer, or a dict "
@@ -1800,9 +1763,7 @@ class UECM(ARDL):
         # TODO: Check order is >= 1
         order = super()._check_order(order)
         if not order:
-            raise ValueError(
-                "Model must contain at least one exogenous variable"
-            )
+            raise ValueError("Model must contain at least one exogenous variable")
         for key, val in order.items():
             if val == [0]:
                 raise ValueError(
@@ -1935,15 +1896,11 @@ class UECM(ARDL):
         params, cov_params, norm_cov_params = self._fit(
             cov_type=cov_type, cov_kwds=cov_kwds, use_t=use_t
         )
-        res = UECMResults(
-            self, params, cov_params, norm_cov_params, use_t=use_t
-        )
+        res = UECMResults(self, params, cov_params, norm_cov_params, use_t=use_t)
         return UECMResultsWrapper(res)
 
     @classmethod
-    def from_ardl(
-        cls, ardl: ARDL, missing: Literal["none", "drop", "raise"] = "none"
-    ):
+    def from_ardl(cls, ardl: ARDL, missing: Literal["none", "drop", "raise"] = "none"):
         """
         Construct a UECM from an ARDL model
 
@@ -2068,9 +2025,7 @@ class UECM(ARDL):
             params, exog, exog_oos, start, end
         )
         if num_oos != 0:
-            raise NotImplementedError(
-                "Out-of-sample forecasts are not supported"
-            )
+            raise NotImplementedError("Out-of-sample forecasts are not supported")
         pred = np.full(self.endog.shape[0], np.nan)
         pred[-self._x.shape[0] :] = self._x @ params
         return pred[start : end + 1]
@@ -2144,6 +2099,14 @@ class UECMResults(ARDLResults):
             return pd.DataFrame(val, columns=lbls, index=lbls)
         return pd.Series(val, index=lbls, name=name)
 
+    @cache_readonly
+    def resid(self):
+        """
+        The residuals of the model.
+        """
+        model = self.model
+        return model._y - self.fittedvalues
+
     @cache_readonly
     def ci_params(self) -> np.ndarray | pd.Series:
         """Parameters of normalized cointegrating relationship"""
@@ -2272,11 +2235,9 @@ class UECMResults(ARDLResults):
         use_t: bool = True,
         asymptotic: bool = True,
         nsim: int = 100_000,
-        seed: int
-        | Sequence[int]
-        | np.random.RandomState
-        | np.random.Generator
-        | None = None,
+        seed: (
+            int | Sequence[int] | np.random.RandomState | np.random.Generator | None
+        ) = None,
     ):
         r"""
         Cointegration bounds test of Pesaran, Shin, and Smith
@@ -2461,11 +2422,7 @@ def _pss_simulate(
     case: Literal[1, 2, 3, 4, 5],
     nobs: int,
     nsim: int,
-    seed: int
-    | Sequence[int]
-    | np.random.RandomState
-    | np.random.Generator
-    | None,
+    seed: int | Sequence[int] | np.random.RandomState | np.random.Generator | None,
 ) -> tuple[pd.DataFrame, pd.Series]:
     rs: np.random.RandomState | np.random.Generator
     if not isinstance(seed, np.random.RandomState):
diff --git a/statsmodels/tsa/arima_process.py b/statsmodels/tsa/arima_process.py
index 912ea6594..b1ed1d641 100644
--- a/statsmodels/tsa/arima_process.py
+++ b/statsmodels/tsa/arima_process.py
@@ -307,7 +307,7 @@ def arma_periodogram(ar, ma, worN=None, whole=0):
         import warnings
 
         warnings.warn(
-            "Warning: nan in frequency response h, maybe a unit " "root",
+            "Warning: nan in frequency response h, maybe a unit root",
             RuntimeWarning,
             stacklevel=2,
         )
diff --git a/statsmodels/tsa/stattools.py b/statsmodels/tsa/stattools.py
index fc7fb0433..f9a840ad8 100644
--- a/statsmodels/tsa/stattools.py
+++ b/statsmodels/tsa/stattools.py
@@ -379,7 +379,7 @@ def adfuller(
         resstore.critvalues = critvalues
         resstore.nobs = nobs
         resstore.H0 = (
-            "The coefficient on the lagged level equals 1 - " "unit root"
+            "The coefficient on the lagged level equals 1 - unit root"
         )
         resstore.HA = "The coefficient on the lagged level < 1 - stationary"
         resstore.icbest = icbest
diff --git a/tools/nbgenerate.py b/tools/nbgenerate.py
index be74e18a7..a3a6da031 100755
--- a/tools/nbgenerate.py
+++ b/tools/nbgenerate.py
@@ -320,7 +320,7 @@ parser.add_argument(
     "--fail-on-error",
     dest="error_fail",
     action="store_true",
-    help="Fail when an error occurs when executing a cell " "in a notebook.",
+    help="Fail when an error occurs when executing a cell in a notebook.",
 )
 parser.add_argument(
     "--skip-existing",


Test patch:
diff --git a/statsmodels/tsa/ardl/tests/test_ardl.py b/statsmodels/tsa/ardl/tests/test_ardl.py
index ea2d63630..a5bf6bad1 100644
--- a/statsmodels/tsa/ardl/tests/test_ardl.py
+++ b/statsmodels/tsa/ardl/tests/test_ardl.py
@@ -159,9 +159,7 @@ def _convert_to_numpy(data, fixed, order, seasonal, use_numpy):
 def test_model_init(
     data: Dataset, lags, order, trend, causal, fixed, use_numpy, seasonal
 ):
-    y, x, z, order, period = _convert_to_numpy(
-        data, fixed, order, seasonal, use_numpy
-    )
+    y, x, z, order, period = _convert_to_numpy(data, fixed, order, seasonal, use_numpy)
 
     mod = ARDL(
         y,
@@ -194,23 +192,15 @@ def test_model_init(
 def test_ardl_order_exceptions(data):
     with pytest.raises(ValueError, match="lags must be a non-negative"):
         ARDL(data.y, -1)
-    with pytest.raises(
-        ValueError, match="All values in lags must be positive"
-    ):
+    with pytest.raises(ValueError, match="All values in lags must be positive"):
         ARDL(data.y, [-1, 0, 2])
     with pytest.raises(ValueError, match="integer orders must be at least"):
         ARDL(data.y, 2, data.x, order=0, causal=True)
     with pytest.raises(ValueError, match="integer orders must be at least"):
         ARDL(data.y, 2, data.x, -1, causal=False)
-    with pytest.raises(
-        ValueError, match="sequence orders must be strictly positive"
-    ):
-        ARDL(
-            data.y, 2, data.x, {"lry": [0, 1], "ibo": 3, "ide": 0}, causal=True
-        )
-    with pytest.raises(
-        TypeError, match="sequence orders must contain non-negative"
-    ):
+    with pytest.raises(ValueError, match="sequence orders must be strictly positive"):
+        ARDL(data.y, 2, data.x, {"lry": [0, 1], "ibo": 3, "ide": 0}, causal=True)
+    with pytest.raises(TypeError, match="sequence orders must contain non-negative"):
         ARDL(
             data.y,
             2,
@@ -218,9 +208,7 @@ def test_ardl_order_exceptions(data):
             {"lry": [1, "apple"], "ibo": 3, "ide": 1},
             causal=True,
         )
-    with pytest.raises(
-        ValueError, match="sequence orders must contain distinct"
-    ):
+    with pytest.raises(ValueError, match="sequence orders must contain distinct"):
         ARDL(
             data.y,
             2,
@@ -228,9 +216,7 @@ def test_ardl_order_exceptions(data):
             {"lry": [1, 1, 2, 3], "ibo": 3, "ide": [1, 1, 1]},
             causal=True,
         )
-    with pytest.raises(
-        ValueError, match="sequence orders must be strictly positive"
-    ):
+    with pytest.raises(ValueError, match="sequence orders must be strictly positive"):
         ARDL(data.y, 2, data.x, [0, 1, 2], causal=True)
 
 
@@ -245,21 +231,15 @@ def test_ardl_order_keys_exceptions(data):
             {"lry": [1, 2], "ibo": 3, "other": 4},
             causal=False,
         )
-    with pytest.warns(
-        SpecificationWarning, match="exog contains variables that"
-    ):
+    with pytest.warns(SpecificationWarning, match="exog contains variables that"):
         ARDL(data.y, 2, data.x, {"lry": [1, 2]}, causal=False)
 
 
 def test_ardl_deterministic_exceptions(data):
     with pytest.raises(TypeError):
         ARDL(data.y, 2, data.x, 2, deterministic="seasonal")
-    with pytest.warns(
-        SpecificationWarning, match="When using deterministic, trend"
-    ):
-        deterministic = DeterministicProcess(
-            data.y.index, constant=True, order=1
-        )
+    with pytest.warns(SpecificationWarning, match="When using deterministic, trend"):
+        deterministic = DeterministicProcess(data.y.index, constant=True, order=1)
         ARDL(data.y, 2, data.x, 2, deterministic=deterministic, trend="ct")
 
 
@@ -335,9 +315,7 @@ def test_ardl_only_y_lag(data):
 
 
 def test_ardl_only_x(data):
-    res = ARDL(
-        data.y, None, data.x, {"lry": 1, "ibo": 2, "ide": 3}, trend="n"
-    ).fit()
+    res = ARDL(data.y, None, data.x, {"lry": 1, "ibo": 2, "ide": 3}, trend="n").fit()
     assert res.params.shape[0] == 9
     res = ARDL(
         data.y,
@@ -365,9 +343,7 @@ def test_ardl_only_seasonal(data):
 
 def test_ardl_only_deterministic(data):
     deterministic = DeterministicProcess(data.y.index, constant=True, order=3)
-    res = ARDL(
-        data.y, None, data.x, None, trend="n", deterministic=deterministic
-    ).fit()
+    res = ARDL(data.y, None, data.x, None, trend="n", deterministic=deterministic).fit()
     assert res.params.shape[0] == 4
     check_results(res)
 
@@ -398,9 +374,7 @@ def test_ardl_parameter_names(data):
         "ide.L2",
     ]
     assert mod.exog_names == expected
-    mod = ARDL(
-        np.asarray(data.y), 2, np.asarray(data.x), 2, causal=False, trend="ct"
-    )
+    mod = ARDL(np.asarray(data.y), 2, np.asarray(data.x), 2, causal=False, trend="ct")
     expected = [
         "const",
         "trend",
@@ -472,9 +446,7 @@ def test_against_autoreg(data, trend, seasonal):
 @pytest.mark.parametrize("start", [None, 0, 2, 4])
 @pytest.mark.parametrize("end", [None, 20])
 @pytest.mark.parametrize("dynamic", [20, True])
-def test_against_autoreg_predict_start_end(
-    data, trend, seasonal, start, end, dynamic
-):
+def test_against_autoreg_predict_start_end(data, trend, seasonal, start, end, dynamic):
     ar = AutoReg(data.y, 3, trend=trend, seasonal=seasonal)
     ardl = ARDL(data.y, 3, trend=trend, seasonal=seasonal)
     ar_res = ar.fit()
@@ -489,13 +461,9 @@ def test_against_autoreg_predict_start_end(
 def test_invalid_init(data):
     with pytest.raises(ValueError, match="lags must be a non-negative"):
         ARDL(data.y, -1)
-    with pytest.raises(
-        ValueError, match="All values in lags must be positive"
-    ):
+    with pytest.raises(ValueError, match="All values in lags must be positive"):
         ARDL(data.y, [-1, 1, 2])
-    with pytest.raises(
-        ValueError, match="All values in lags must be positive"
-    ):
+    with pytest.raises(ValueError, match="All values in lags must be positive"):
         ARDL(data.y, [1, 2, 2, 3])
     with pytest.raises(ValueError, match="hold_back must be "):
         ARDL(data.y, 3, data.x, 4, hold_back=3)
@@ -517,9 +485,7 @@ def test_prediction_exceptions(data, fixed, use_numpy):
         res.forecast(1)
     if isinstance(x, pd.DataFrame):
         exog_oos = np.asarray(data.x)[:12]
-        with pytest.raises(
-            TypeError, match="exog_oos must be a DataFrame when"
-        ):
+        with pytest.raises(TypeError, match="exog_oos must be a DataFrame when"):
             res.forecast(12, exog=exog_oos)
         with pytest.raises(ValueError, match="must have the same columns"):
             res.forecast(12, exog=data.x.iloc[:12, :1])
@@ -544,16 +510,12 @@ def test_prediction_wrong_shape(data):
     res = ARDL(data.y, 4, x, [1, 3]).fit()
     with pytest.raises(ValueError, match="exog must have the same number"):
         res.predict(exog=np.asarray(data.x)[:, :1])
-    with pytest.raises(
-        ValueError, match="exog must have the same number of rows"
-    ):
+    with pytest.raises(ValueError, match="exog must have the same number of rows"):
         res.predict(exog=np.asarray(data.x)[:-2])
     res = ARDL(data.y, 4, data.x, [1, 3]).fit()
     with pytest.raises(ValueError, match="exog must have the same columns"):
         res.predict(exog=data.x.iloc[:, :1])
-    with pytest.raises(
-        ValueError, match="exog must have the same number of rows"
-    ):
+    with pytest.raises(ValueError, match="exog must have the same number of rows"):
         res.predict(exog=data.x.iloc[:-2])
 
 
@@ -562,16 +524,12 @@ def test_prediction_wrong_shape_fixed(data):
     res = ARDL(data.y, 4, fixed=x).fit()
     with pytest.raises(ValueError, match="fixed must have the same number"):
         res.predict(fixed=np.asarray(data.x)[:, :1])
-    with pytest.raises(
-        ValueError, match="fixed must have the same number of rows"
-    ):
+    with pytest.raises(ValueError, match="fixed must have the same number of rows"):
         res.predict(fixed=np.asarray(data.x)[:-2])
     res = ARDL(data.y, 4, fixed=data.x).fit()
     with pytest.raises(ValueError, match="fixed must have the same number"):
         res.predict(fixed=data.x.iloc[:, :1])
-    with pytest.raises(
-        ValueError, match="fixed must have the same number of rows"
-    ):
+    with pytest.raises(ValueError, match="fixed must have the same number of rows"):
         res.predict(fixed=data.x.iloc[:-2])
 
 
@@ -682,9 +640,7 @@ def test_uecm_model_init(
 
 def test_from_ardl_none(data):
     with pytest.warns(SpecificationWarning):
-        mod = UECM.from_ardl(
-            ARDL(data.y, 2, data.x, {"lry": 2, "ide": 2, "ibo": None})
-        )
+        mod = UECM.from_ardl(ARDL(data.y, 2, data.x, {"lry": 2, "ide": 2, "ibo": None}))
     assert mod.ardl_order == (2, 2, 2)
 
 
@@ -825,9 +781,7 @@ def test_bounds_test_seed(seed):
         {"lry": 1, "ibo": 3, "ide": 2},
     )
     res = mod.fit()
-    bounds_result = res.bounds_test(
-        case=3, asymptotic=False, seed=seed, nsim=10_000
-    )
+    bounds_result = res.bounds_test(case=3, asymptotic=False, seed=seed, nsim=10_000)
     assert (bounds_result.p_values >= 0.0).all()
     assert (bounds_result.p_values <= 1.0).all()
     assert (bounds_result.crit_vals > 0.0).all().all()
@@ -848,3 +802,22 @@ def test_bounds_test_simulate_order():
     )
     assert_allclose(bounds_result.stat, bounds_result_sim.stat)
     assert (bounds_result_sim.p_values > bounds_result.p_values).all()
+
+
+def test_resids_ardl_uecm():
+    ardl_mod = ARDL(
+        dane_data.lrm,
+        3,
+        dane_data[["lry", "ibo", "ide"]],
+        {"lry": 1, "ibo": 3, "ide": 2},
+    )
+    ardl_res = ardl_mod.fit()
+    uecm_mod = UECM(
+        dane_data.lrm,
+        3,
+        dane_data[["lry", "ibo", "ide"]],
+        {"lry": 1, "ibo": 3, "ide": 2},
+    )
+    uecm_res = uecm_mod.fit()
+
+    assert_allclose(uecm_res.resid, ardl_res.resid)
diff --git a/statsmodels/tsa/vector_ar/tests/test_var_jmulti.py b/statsmodels/tsa/vector_ar/tests/test_var_jmulti.py
index db9d010be..1bf647f51 100644
--- a/statsmodels/tsa/vector_ar/tests/test_var_jmulti.py
+++ b/statsmodels/tsa/vector_ar/tests/test_var_jmulti.py
@@ -625,7 +625,7 @@ def test_whiteness():
             obtained = results_sm[ds][dt].test_whiteness(nlags=lags)
             # test statistic
             err_msg = build_err_msg(
-                ds, dt, "WHITENESS OF RESIDUALS - " "TEST STATISTIC"
+                ds, dt, "WHITENESS OF RESIDUALS - TEST STATISTIC"
             )
             desired = results_ref[ds][dt]["whiteness"]["test statistic"]
             assert_allclose(
@@ -633,7 +633,7 @@ def test_whiteness():
             )
             # p-value
             err_msg = build_err_msg(
-                ds, dt, "WHITENESS OF RESIDUALS - " "P-VALUE"
+                ds, dt, "WHITENESS OF RESIDUALS - P-VALUE"
             )
             desired = results_ref[ds][dt]["whiteness"]["p-value"]
             assert_allclose(
@@ -647,7 +647,7 @@ def test_whiteness():
             err_msg = build_err_msg(
                 ds,
                 dt,
-                "WHITENESS OF RESIDUALS - " "TEST STATISTIC (ADJUSTED TEST)",
+                "WHITENESS OF RESIDUALS - TEST STATISTIC (ADJUSTED TEST)",
             )
             desired = results_ref[ds][dt]["whiteness"]["test statistic adj."]
             assert_allclose(
@@ -655,7 +655,7 @@ def test_whiteness():
             )
             # p-value (adjusted Portmanteau test)
             err_msg = build_err_msg(
-                ds, dt, "WHITENESS OF RESIDUALS - " "P-VALUE (ADJUSTED TEST)"
+                ds, dt, "WHITENESS OF RESIDUALS - P-VALUE (ADJUSTED TEST)"
             )
             desired = results_ref[ds][dt]["whiteness"]["p-value adjusted"]
             assert_allclose(
diff --git a/statsmodels/tsa/vector_ar/tests/test_vecm.py b/statsmodels/tsa/vector_ar/tests/test_vecm.py
index 4341369d2..345bde500 100644
--- a/statsmodels/tsa/vector_ar/tests/test_vecm.py
+++ b/statsmodels/tsa/vector_ar/tests/test_vecm.py
@@ -1681,7 +1681,7 @@ def test_whiteness():
             )
             # test statistic
             err_msg = build_err_msg(
-                ds, dt, "WHITENESS OF RESIDUALS - " "TEST STATISTIC"
+                ds, dt, "WHITENESS OF RESIDUALS - TEST STATISTIC"
             )
             desired = results_ref[ds][dt]["whiteness"]["test statistic"]
             assert_allclose(
@@ -1701,7 +1701,7 @@ def test_whiteness():
                 )
             # p-value
             err_msg = build_err_msg(
-                ds, dt, "WHITENESS OF RESIDUALS - " "P-VALUE"
+                ds, dt, "WHITENESS OF RESIDUALS - P-VALUE"
             )
             desired = results_ref[ds][dt]["whiteness"]["p-value"]
             assert_allclose(
@@ -1721,7 +1721,7 @@ def test_whiteness():
             err_msg = build_err_msg(
                 ds,
                 dt,
-                "WHITENESS OF RESIDUALS - " "TEST STATISTIC (ADJUSTED TEST)",
+                "WHITENESS OF RESIDUALS - TEST STATISTIC (ADJUSTED TEST)",
             )
             desired = results_ref[ds][dt]["whiteness"]["test statistic adj."]
             assert_allclose(
@@ -1741,7 +1741,7 @@ def test_whiteness():
                 )
             # p-value (adjusted Portmanteau test)
             err_msg = build_err_msg(
-                ds, dt, "WHITENESS OF RESIDUALS - " "P-VALUE (ADJUSTED TEST)"
+                ds, dt, "WHITENESS OF RESIDUALS - P-VALUE (ADJUSTED TEST)"
             )
             desired = results_ref[ds][dt]["whiteness"]["p-value adjusted"]
             assert_allclose(