Engle-Granger Cointegration
Calculate the Engle-Granger test for cointegration between every ordered pair of tickers in the Toolkit instance.
Two individually non-stationary series (e.g. two stock prices, each following a random walk) are cointegrated if some linear combination of them is stationary, i.e. they share a long-run equilibrium relationship even though each wanders on its own in the short run. This is the classic statistical foundation for pairs-trading: if two assets are cointegrated, deviations of the spread from its equilibrium level tend to revert, making the spread itself tradeable.
In pairs trading the slope of the regression of the dependent on the independent ticker is the hedge ratio, and its residual is the spread that is traded. Cointegration found in one window often breaks down in the next, so the relationship is usually tested again on a later sample before it is relied on. The result depends on which ticker is the dependent one, so the two orderings can disagree; get_johansen_cointegration tests the pair without that choice.
The formula is as follows:
\[\text{Step 1:}\;\; \text{Aₜ} = \alpha + \beta \cdot \text{Bₜ} + \text{uₜ},\;\; \text{the OLS regression of the dependent on the independent column level (Adj Close by default)}\] \[\text{Step 2:}\;\; \Delta \text{ûₜ} = \text{γ} \cdot \text{ûₜ₋₁} + \operatorname{Σ}(i = 1..p) \text{δᵢ} \cdot \Delta \text{ûₜ₋ᵢ} + \text{epsilonₜ},\;\; \text{an ADF regression without constant on the residuals}\] \[\text{EG} = \text{γ̂} / \operatorname{SE}(\text{γ̂}),\;\; \text{compared with the MacKinnon (1994, 2010) cointegration critical values and p-values for two variables}\] \[p = \text{the number of lags that minimises the AIC},\;\; \text{up to max\_lag (same default as get\_augmented\_dickey\_fuller)}\]For more information about the method, see the following paper:
- Engle, R.F. and Granger, C.W.J. (1987). “Co-integration and Error Correction: Representation, Estimation, and Testing.” Econometrica, 55(2), 251-276.
Also known as: EG test, residual-based cointegration test, pairs-trading test.
Engle-Granger Cointegration in Python or via the MCP
Install the Finance Toolkit with:
pip install financetoolkit -U
Then call get_engle_granger_cointegration as shown below.
from financetoolkit import Toolkit
toolkit = Toolkit(
["KO", "PEP"],
api_key="FINANCIAL_MODELING_PREP_KEY",
start_date="2021-01-01",
end_date="2025-12-31",
)
engle_granger = toolkit.econometrics.get_engle_granger_cointegration()
# The 1% and 10% critical values follow the same pattern as the 5% one.
engle_granger[["Engle-Granger Statistic", "P-Value", "Critical Value 5%", "Cointegrated (5%)"]]
Or, after setting up the Finance Toolkit MCP, ask:
Were the share prices of Coca-Cola and PepsiCo cointegrated from 2021 to 2025, according to the Engle-Granger test at the 5% level?
Which returns:
| Engle-Granger Statistic | P-Value | Critical Value 5% | Cointegrated (5%) | |
|---|---|---|---|---|
| (‘KO’, ‘PEP’) | -1.0266 | 0.8977 | -3.341 | False |
| (‘PEP’, ‘KO’) | -2.0565 | 0.4991 | -3.341 | False |
Using Your Own Data
get_engle_granger_cointegration runs on any data, not only prices: ratios, economic indicators, yield curves or your own dataset. Pass a DataFrame, a Series or a dictionary of them to Econometrics as data, with the dates in the rows or the columns, and set changes to how its changes are calculated: "percentage", "log", "difference" for rates and ratios, or None when the data are changes already. For example, for the monthly unemployment rates of the United States and Germany:
from financetoolkit import Economics, Econometrics
economics = Economics(start_date="2000-01-01")
unemployment = economics.get_unemployment_rate(
countries=["United States", "Germany"], period="monthly"
)
econometrics = Econometrics(data=unemployment, changes="difference")
econometrics.get_engle_granger_cointegration()
Parameters
get_engle_granger_cointegration accepts the following parameters.
| Parameter | Type | Description | Example |
|---|---|---|---|
period | str | The data frequency (daily, weekly, monthly, quarterly, or yearly). Defaults to “daily”. | "weekly" |
column | str | The historical data column to test. Defaults to “Adj Close”. | "Close" |
max_lag | int | The maximum number of lagged differences to consider in the underlying ADF test on the residuals. Defaults to the Schwert (1989) rule of thumb, capped at (n - 5) // 3 for short samples. | 12 |
include_benchmark | bool | Whether to include “Benchmark” among the tickers paired up. Defaults to False. | True |
rounding | int | None | The number of decimals to round the results to. Defaults to None. | 2 |