WalzoneInterview Prep
📞 Interviewing soon? Practice with a realistic AI mock phone interview — it calls you, then scores you. First 15 min FREE →

Wall Street Quant · Financial Models · question 129 of 155

Given price data for two historically cointegrated stocks, develop a statistical arbitrage strategy.?

📕 Buy this interview preparation book: 155 Wall Street Quant questions & answers — PDF + EPUB for $5

Statistical arbitrage, in the context of cointegration, aims to exploit the temporary price deviations between two or more securities that exhibit a long-term equilibrium relationship. We will consider a pair trading strategy based on cointegration between two historically cointegrated stocks. This is done in a few steps:

1. Preprocess the data.

2. Test for cointegration.

3. Fit a linear model with least squares.

4. Implement a pairs trading/mean-reversion strategy.

5. Measure performance and risk.

Before implementing the example, let’s import the required libraries:

import numpy as np
import pandas as pd
import statsmodels.api as sm
import matplotlib.pyplot as plt

Let’s consider we have historical daily adjusted closing price data for two stocks (Stock A and Stock B) named ’stock_A’ and ’stock_B’. Here’s a sample of the stocks’ DataFrame:

print(data.head())
        stock_A  stock_B
0  153.629898  153.629898
1  153.629898  153.629898
2  153.629898  153.629898
3  152.537521  152.537521
4  152.537521  152.537521

## Step 1: Preprocess the price data

First, let’s ensure the data do not have any missing or non-numeric values. We can also generate the log returns of the two stocks:

data = data.dropna()

log_returns_A = np.log(data['stock_A']).diff().dropna()
log_returns_B = np.log(data['stock_B']).diff().dropna()

## Step 2: Test for cointegration

We test for cointegration using the ADF (Augmented Dickey-Fuller) and the Engle-Granger two-step methodology. First, make sure that the individual log price series are non-stationary, then we test for the stationarity of the residuals from a linear regression.

from statsmodels.tsa.stattools import adfuller

adf_A = adfuller(log_returns_A)
adf_B = adfuller(log_returns_B)

adf_A_diff = adfuller(log_returns_A.diff().dropna())
adf_B_diff = adfuller(log_returns_B.diff().dropna())

print("ADF p-value for Stock A: ", adf_A[1])
print("ADF p-value for Stock B: ", adf_B[1])
print("ADF p-value for Stock A first differences: ", adf_A_diff[1])
print("ADF p-value for Stock B first differences: ", adf_B_diff[1])

If the ADF test indicates that the two price series are non-stationary, then we proceed with the Engle-Granger test:

from statsmodels.tsa.stattools import coint

eg_test = coint(log_returns_A, log_returns_B)
print("Engle-Granger test p-value: ", eg_test[1])

If the Engle-Granger test’s p-value is less than a predefined level of significance (e.g., 0.05), we can conclude that the two price series are cointegrated.

## Step 3: Fit a linear model with least squares

Next, we fit a linear regression model to establish the cointegration relationship between the two stocks:

X = sm.add_constant(log_returns_A)
model = sm.OLS(log_returns_B, X).fit()
hedge_ratio = model.params[1]

We obtain the hedging ratio called ’hedge_ratio’. This signifies how many units of Stock B we have to sell short for each unit of Stock A we buy.

## Step 4: Implement a pairs trading/mean-reversion strategy

Now, let’s create a trading signal based on the residual values obtained from the linear regression. We start by subtracting the log returns of Stock A and Stock B after considering the hedge ratio:

residuals = log_returns_B - hedge_ratio * log_returns_A

rolling_mean = residuals.rolling(window=60).mean()
rolling_std = residuals.rolling(window=60).std()

# Calculate z-score
z_score = (residuals - rolling_mean) / rolling_std

We then create trading signals based on the z-scores:

entry_threshold = 2
exit_threshold = 0

longs = (z_score <= -entry_threshold)
shorts = (z_score >= entry_threshold)

exits = (np.abs(z_score) <= exit_threshold)

positions = pd.DataFrame(index=residuals.index).fillna(0)
positions['stock_A'] = longs.astype(int) - shorts.astype(int)
positions['stock_B'] = -hedge_ratio * positions['stock_A']

## Step 5: Measure performance and risk

Finally, let’s measure performance and risk using various metrics:

pnl_A = positions['stock_A'].shift(1) * log_returns_A
pnl_B = positions['stock_B'].shift(1) * log_returns_B

total_pnl = pnl_A + pnl_B
total_ret = total_pnl.cumsum()

sharpe_ratio = np.sqrt(252) * total_pnl.mean() / total_pnl.std()
print("Sharpe Ratio: ", sharpe_ratio)

plt.plot(total_ret)
plt.title("Cumulative Returns")
plt.xlabel("Time")
plt.ylabel("Return")
plt.show()

This chart shows the cumulative returns of our strategy. You can also calculate other performance metrics such as max drawdown, annual return, or the number of trades executed.

This provides an example of a statistical arbitrage strategy based on cointegration between two historically cointegrated stocks. The success of the strategy depends on the quality of the cointegration relationship, the market’s overall conditions, and execution efficiency.

Reading is step one. Saying it out loud is the interview. Our AI interviewer calls your phone and runs a realistic Wall Street Quant interview — then scores it.
📞 Practice Wall Street Quant — free 15 min
📕 Buy this interview preparation book: 155 Wall Street Quant questions & answers — PDF + EPUB for $5

All 155 Wall Street Quant questions · All topics