.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/time_series/unit_roots/plot_02_engle_granger.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via JupyterLite. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_time_series_unit_roots_plot_02_engle_granger.py: Engle-Granger cointegration =========================== Granger and Newbold (1974) showed that regressing one random walk on an independent one gives "significant" slopes and high :math:`R^2` most of the time: a spurious regression. Yet some nonstationary series *are* tied together, such as spot and futures prices, or two share classes of the same company. Robert Engle and Clive Granger (1987) called them cointegrated: some combination :math:`y_t - b x_t` is stationary. Their two-step test regresses :math:`y` on :math:`x` and runs a Dickey-Fuller test on the residuals, with critical values that allow for :math:`b` having been fitted. It is the textbook basis of pairs trading. .. GENERATED FROM PYTHON SOURCE LINES 17-23 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np from financialkit.time_series import engle_granger from financialkit.time_series.utils import simulate_cointegrated_pair .. GENERATED FROM PYTHON SOURCE LINES 24-28 A cointegrated pair and a spurious one -------------------------------------- Two log prices that share a stochastic trend (the spread is an AR(1) with a half-life of about a week), and two independent random walks. .. GENERATED FROM PYTHON SOURCE LINES 28-56 .. code-block:: Python rng = np.random.default_rng(1987) n = 750 y, x = simulate_cointegrated_pair(n, beta=1.2, intercept=0.5, phi=0.9, sigma_x=0.02, sigma_u=0.01, rng=rng) a, b = np.cumsum(0.02 * rng.standard_normal(n)), np.cumsum(0.02 * rng.standard_normal(n)) coint = engle_granger(y, x) spur = engle_granger(a, b) for name, res in (("cointegrated", coint), ("spurious", spur)): cv = res.critical_values["5%"] print(f"{name:13s} b = {res.beta[0]:6.3f}, R^2 = {res.r_squared:.2f}, EG = {res.statistic:6.2f} (5% cv {cv:.2f}), reject: {res.rejects()}") fig, axes = plt.subplots(2, 2, figsize=(9, 5.5), sharex=True) axes[0, 0].plot(y, label="y") axes[0, 0].plot(x, label="x") axes[0, 0].set_title("cointegrated pair") axes[0, 0].legend() axes[1, 0].plot(coint.residuals, color="C2") axes[1, 0].set_title("spread $y - a - bx$: stationary") axes[0, 1].plot(a) axes[0, 1].plot(b) axes[0, 1].set_title(f"independent walks, $R^2$ = {spur.r_squared:.2f}") axes[1, 1].plot(spur.residuals, color="C3") axes[1, 1].set_title("residual: still a random walk") for ax in axes[1]: ax.set_xlabel("day") fig.tight_layout() .. image-sg:: /api/gallery/time_series/unit_roots/images/sphx_glr_plot_02_engle_granger_001.png :alt: cointegrated pair, independent walks, $R^2$ = 0.05, spread $y - a - bx$: stationary, residual: still a random walk :srcset: /api/gallery/time_series/unit_roots/images/sphx_glr_plot_02_engle_granger_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none cointegrated b = 1.195, R^2 = 0.96, EG = -6.01 (5% cv -3.34), reject: True spurious b = 0.592, R^2 = 0.05, EG = -2.13 (5% cv -3.34), reject: False .. GENERATED FROM PYTHON SOURCE LINES 57-61 Spurious regression in numbers ------------------------------ Across 300 pairs of independent walks the slope's naive t-ratio exceeds 2 in most of them, but the Engle-Granger test rejects close to its 5% size. .. GENERATED FROM PYTHON SOURCE LINES 61-72 .. code-block:: Python big_t, eg_reject = [], [] for _ in range(300): a, b = np.cumsum(rng.standard_normal((2, 250)), axis=1) X = np.column_stack([np.ones(250), b]) coef, res, *_ = np.linalg.lstsq(X, a, rcond=None) se = np.sqrt(res[0] / 248 * np.linalg.inv(X.T @ X)[1, 1]) big_t.append(abs(coef[1] / se) > 2) eg_reject.append(engle_granger(a, b, lags=0).rejects()) print(f"|t| > 2 in {np.mean(big_t):.0%} of spurious regressions; Engle-Granger rejects in {np.mean(eg_reject):.0%}") plt.show() .. rst-class:: sphx-glr-script-out .. code-block:: none |t| > 2 in 84% of spurious regressions; Engle-Granger rejects in 3% .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.146 seconds) .. _sphx_glr_download_api_gallery_time_series_unit_roots_plot_02_engle_granger.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../../../lite/lab/index.html?path=api/gallery/time_series/unit_roots/plot_02_engle_granger.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_02_engle_granger.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_02_engle_granger.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_02_engle_granger.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_