Note

The following implementations and documentation closely follow the publication by Bock, M. and Mestel, R: A regime-switching relative value arbitrage rule. Operations Research Proceedings 2008, pages 9–14. Springer..

Regime-Switching Arbitrage Rule



The traditional pairs trading strategy usually fails when fundamental or economic reasons cause a structural break on one of the stocks in the pair. This break will cause the temporary spread deviations formed by the pair to become persistent spread deviations which will not revert. Under these circumstances, betting on the spread to revert to its historical mean would imply a loss.

To overcome the problem of detecting whether the deviations are temporary or longer-lasting, the paper by Bock and Mestel bridges the literature on Markov regime-switching and the scientific work on statistical arbitrage to develop useful trading rules for pairs trading.

Assumptions

Series Formed by the Trading Pair

It models the series \(X_t\) formed by the trading pair as,

\[X_t = \mu_{s_t} + \epsilon_t,\]

where

\(E[\epsilon_t] = 0\), \(\sigma^2_{\epsilon_t} = \sigma^2_{s_t}\) and \(s_t\) denotes the current regime.

Markov Regime-Switching Model

A two-state, first-order Markov-switching process for \(s_t\) is considered with the following transition probabilities:

\[\begin{split}\Bigg\{ \begin{matrix} prob[s_t = 1 | s_{t-1} = 1] = p \\ prob[s_t = 2 | s_{t-1} = 2] = q \\ \end{matrix}\end{split}\]

where

\(1\) indicates a regime with a higher mean (\(\mu_{1}\)) while \(2\) indicates a regime with a lower mean (\(\mu_{2}\)).

Strategy

The trading signal \(z_t\) is determined in the following way:

\(Case\ 1 \ \ current\ regime = 1\)

\[\begin{split}z_t = \left\{\begin{array}{l} -1,\ if\ X_t \geq \mu_1 + \delta \cdot \sigma_1 \\ +1,\ if\ X_t \leq \mu_1 - \delta \cdot \sigma_1 \wedge P(s_t = 1 | X_t) \geq \rho \\ 0,\ otherwise \end{array}\right.\end{split}\]

\(Case\ 2 \ \ current\ regime = 2\)

\[\begin{split}z_t = \left\{\begin{array}{l} -1,\ if\ X_t \geq \mu_2 + \delta \cdot \sigma_2 \wedge P(s_t = 2 | X_t) \geq \rho\\ +1,\ if\ X_t \leq \mu_2 - \delta \cdot \sigma_2 \\ 0,\ otherwise \end{array}\right.\end{split}\]

where

\(P(\cdot)\) denotes the smoothed probabilities for each state,

\(\delta\) and \(\rho\) denote the standard deviation sensitivity parameter and the probability threshold of the trading strategy, respectively.

To be more specific, the trading signal can be described as,

\(Case\ 1 \ \ current\ regime = 1\)

\[\begin{split}\left\{\begin{array}{l} Open\ a\ long\ trade,\ if\ X_t \leq \mu_1 - \delta \cdot \sigma_1 \wedge P(s_t = 1 | X_t) \geq \rho \\ Close\ a\ long\ trade,\ if\ X_t \geq \mu_1 + \delta \cdot \sigma_1 \\ Open\ a\ short\ trade,\ if\ X_t \geq \mu_1 + \delta \cdot \sigma_1 \\ Close\ a\ short\ trade,\ if\ X_t \leq \mu_1 - \delta \cdot \sigma_1 \wedge P(s_t = 1 | X_t) \geq \rho \\ Do\ nothing,\ otherwise \end{array}\right.\end{split}\]

\(Case\ 2 \ \ current\ regime = 2\)

\[\begin{split}\left\{\begin{array}{l} Open\ a\ long\ trade,\ if\ X_t \leq \mu_2 - \delta \cdot \sigma_2 \\ Close\ a\ long\ trade,\ if\ X_t \geq \mu_2 + \delta \cdot \sigma_2 \wedge P(s_t = 2 | X_t) \geq \rho\\ Open\ a\ short\ trade,\ if\ X_t \geq \mu_2 + \delta \cdot \sigma_2 \wedge P(s_t = 2 | X_t) \geq \rho\\ Close\ a\ short\ trade,\ if\ X_t \leq \mu_2 - \delta \cdot \sigma_2 \\ Do\ nothing,\ otherwise \end{array}\right.\end{split}\]

Steps to Execute the Strategy

Step 1: Select a Trading Pair

In this paper, they used the DJ STOXX 600 component as the asset pool and applied the cointegration test for the pairs selection. One can use the same method as the paper did or other pairs selection algorithms like the distance approach for finding trading pairs.

Step 2: Construct the Spread Series

In this paper, they used \(\frac{P^A_t}{P^B_t}\) as the spread series. One can use the same method as the paper did or other formulae like \((P^A_t/P^A_0) - \beta \cdot (P^B_t/P^B_0)\) and \(ln(P^A_t/P^A_0) - \beta \cdot ln(P^B_t/P^B_0)\) for constructing the spread series.

Step 3: Estimate the Parameters of the Markov Regime-Switching Model

Fit the Markov regime-switching model to the spread series with a rolling time window to estimate \(\mu_1\), \(\mu_2\), \(\sigma_1\), \(\sigma_2\) and the current regime.

Step 4: Determine the Signal of the Strategy

Determine the current signal based on the strategy and estimated parameters.

Step 5: Decide the Trade

Decide the trade based on the signal at time \(t\) and the position at \(t - 1\). Possible combinations are listed below:

\(Position_{t - 1}\)

\(Open\ a\ long\ trade\)

\(Close\ a\ long\ trade\)

\(Open\ a\ short\ trade\)

\(Close\ a\ short\ trade\)

\(Trade\ Action\)

\(Position_{t}\)

0

True

False

False

X

Open a long trade

+1

0

False

X

True

False

Open a short trade

-1

0

Otherwise

Do nothing

0

+1

False

True

False

X

Close a long trade

0

+1

False

X

True

False

Close a long trade and open a short trade

-1

+1

Otherwise

Do nothing

+1

-1

False

X

False

True

Close a short trade

0

-1

True

False

False

X

Close a short trade and open a long trade

+1

-1

Otherwise

Do nothing

-1

where X denotes the don’t-care term, the value of X could be either True or False.

Implementation

Tip

If the user is not satisfied with the default trading strategy described in the paper, one can use the change_strategy method to modify it.

Examples

Code Example

>>> import matplotlib.pyplot as plt
>>> import yfinance as yf
>>> from arbitragelab.time_series_approach.regime_switching_arbitrage_rule import (
...     RegimeSwitchingArbitrageRule,
... )
>>> data = yf.download("CL=F NG=F", start="2015-01-01", end="2020-01-01", progress=False)[
...     "Adj Close"
... ]
>>> # Construct spread series
>>> ratt = data["NG=F"] / data["CL=F"]
>>> rsar = RegimeSwitchingArbitrageRule(delta=1.5, rho=0.6)
>>> window_size = 60
>>> # Get the current signal
>>> signal = rsar.get_signal(
...     ratt[-window_size:], switching_variance=False, silence_warnings=True
... )
>>> # [Open long, close long, open short, close short]
>>> list(signal)  
[True, False, False, True]
>>> signals = rsar.get_signals(
...     ratt, window_size, switching_variance=True, silence_warnings=True
... )
>>> signals  
array(...)
>>> signals.shape
(1256, 4)
>>> # Decide on trades based on the signals
>>> trades = rsar.get_trades(signals)
>>> trades  
array(...)
>>> trades.shape
(1256, 4)
>>> # Plot trades
>>> rsar.plot_trades(ratt, trades)  
<Figure...>
>>> # Changing rules
>>> cl_rule = lambda Xt, mu, delta, sigma: Xt >= mu
>>> cs_rule = lambda Xt, mu, delta, sigma: Xt <= mu
>>> rsar.change_strategy("High", "Long", "Open", cl_rule)
>>> rsar.change_strategy("High", "Short", "Close", cs_rule)
>>> # Get signals on a rolling basis
>>> signals = rsar.get_signals(
...     ratt, window_size, switching_variance=True, silence_warnings=True
... )
>>> signals  
array(...)
>>> signals.shape
(1256, 4)
>>> # Deciding the trades based on the signals
>>> trades = rsar.get_trades(signals)
>>> trades  
array(...)
>>> trades.shape
(1256, 4)
>>> # Plotting trades
>>> rsar.plot_trades(ratt, trades)  
<Figure...>

Research Notebook

The following research notebook can be used to better understand the strategy described above.

Research Article


Presentation Slides


References

  1. Bock, M. and Mestel, R., A regime-switching relative value arbitrage rule. Operations Research Proceedings 2008, pages 9–14. Springer.