Note
The following implementations and documentation closely follow the work of Tim Leung: Tim Leung and Xin Li Optimal Mean reversion Trading: Mathematical Analysis and Practical Applications.
Trading Under the Exponential Ornstein-Uhlenbeck Modelο
Model fittingο
Note
We are solving the optimal stopping problem for a mean-reverting portfolio that is constructed by holding \(\alpha = \frac{A}{S_0^{(1)}}\) of a risky asset \(S^{(1)}\) and shorting \(\beta = \frac{B}{S_0^{(2)}}\) of another risky asset \(S^{(2)}\), yielding a portfolio value:
Since in terms of mean-reversion we care only about the ratio between \(\alpha\) and \(\beta\), without the loss of generality we can set \(\alpha = const\) and A = $1, while varying \(\beta\) to find the optimal strategy \((\alpha,\beta^*)\) .
The Exponential Ornstein-Uhlenbeck (XOU) process is defined the following way:
where \(X\) is the Ornstein-Uhlenbeck process.
Note
The definition of the OU process and the fitting procedure details are presented in Trading Under the Exponential Ornstein-Uhlenbeck Model.
In other words, \(X\) is a log-price of a positive XOU process \(\xi\).
The main parameters of the XOU model coincide with the parameters of the OU model:
\(\theta\) β long term mean level, all future trajectories of π will evolve around a mean level π in the long run.
\(\mu\) - the speed of reversion, characterizes the velocity at which such trajectories will regroup around \(\theta\) in time.
\(\sigma\) - instantaneous volatility, measures instant by instant the amplitude of randomness entering the system. Higher values imply more randomness.
To fit the XOU process to our data and find the optimal ratio between the two assets we are using the same approach as we utilized for the OU process: firstly, we are maximizing the average log-likelihood function with respect to model parameters, and secondly choosing the \(\beta^*\) that provides the maximum value of the said max log-likelihood function.
Optimal Stopping Approachο
First of all, letβs assume that the investor already has a position the value of which follows the XOU process. When the investor closes his position at the time \(\tau\) he receives the value \(\xi_{\tau}=e^{X_{\tau}}\) and pays a constant transaction cost \(c_s > 0\). To maximize the expected discounted value we need to solve the optimal stopping problem:
where \(T\) denotes the set of all possible stopping times and \(r > 0\) is our subjective constant discount rate. \(V^{\xi}(x)\) represents the expected liquidation value accounted with \(\xi\).
Current price plus transaction cost constitute the cost of entering the trade. After subtracting the found cost from the expected optimal value of liquidation - \(V(x)\) we can formalize the optimal entry problem:
with
To sum up this problem, we, as an investor, want to maximize the expected discounted difference between the current price of the position - \(e^{x_{\nu}}\) and its expected liquidation value \(V^{\xi}(X_{\nu})\) minus transaction cost \(c_b\).
Note
This approach presumes that the investor wants to commit only two trades: entering the position, and liquidating it.
Optimal stopping problemο
Theorem 3.2 (p.54):
The optimal liquidation problem admits the solution:
The optimal liquidation level \(b^*\) is found from the equation:
Corresponding optimal liquidation time is given by
Theorem 3.4 (p.54):
The optimal entry timing problem admits the solution:
The optimal entry interval \((a^{\xi*},d^{\xi*})\) is found using the respective equations:
Corresponding optimal entry time is given by
To summarize: the investor should enter the market when the price enters the interval \([e^{a^{\xi*}}, e^{d^{\xi*}}]\) for the first time, and exit as soon as it reaches the price level \(e^{b{\xi*}}\).
Optimal Switching Approachο
If there is no limit on the number of times the investor will open or close the position, the sequential trading times are modeled by the stopping times \(\nu_1,\tau_1,\nu_2,\tau_2,... \in T\) such that
Where \(\nu_i\) are times when the share of a risky asset was bought and \(\tau_i\) - when it was sold. In the case of pairs trading, we consider our spread as such an asset.
Naturally, the optimal timing of trades would depend on the initial position at the beginning of the training period. Under the XOU model, if the investor starts with a zero position the first move to make in that case would be to buy the share. Therefore, we can formulate the following problem :
Where \(\Lambda_0\) is the set of admissible times, and helper functions denoted as such:
However, if the investor already holds a position in a said asset, the first action would be to sell, and the problem will look the following way:
With \(\Lambda_1\) as the set of admissible times.
Optimal switching problemο
To find the optimal levels, first, two helper functions have to be denoted:
Theorem 3.7 (p.56):
Under the optimal switching approach it is optimal to re-enter the market if and only if all of the following conditions hold true:
There are two distinct roots to \(f_b:\ x_{b1},x_{b2}\)
\(\exists \tilde{a}^* \in (x_{b1},x_{b2})\) satisfying \(F(\tilde{a}^*)e^{\tilde{a}^*}=F'(\tilde{a}^*)(e^{\tilde{a}^*}+c_b)\)
The following inequality must hold true:
In case any of the conditions are not met - re-entering the market is deemed not optimal. It would be advised to exit at the optimal liquidation price without re-entering in the case when the investor had already entered the market beforehand, or donβt enter the market at all in the case when he or she starts with a zero position.
How to use this submoduleο
In this module, the user can obtain the solution to the optimal stopping problem that is established for one entry and one exit point, or to the optimal switching problem that accounts for multiple entries and exit points.
Step 1: Model fittingο
During the module fitting stage, we need to use the fit
function to fit the XOU model to our training data. The data provided
should consist of a log-price of an already created mean-reverting portfolio.
Implementationο
- class ExponentialOrnsteinUhlenbeckο
This class implements the algorithm for solving the optimal stopping problem and optimal switching problem for assets with mean-reverting tendencies based on the Exponential Ornstein-Uhlenbeck model mentioned in the following publication: βTim Leung and Xin Li Optimal Mean reversion Trading: Mathematical Analysis and Practical Applications(November 26, 2015)β <https://www.amazon.com/Optimal-Mean-Reversion-Trading-Mathematical/dp/9814725919>`_
- __init__()ο
Initializes the module parameters.
- ExponentialOrnsteinUhlenbeck.fit(data: DataFrame, data_frequency: str, discount_rate: tuple, transaction_cost: tuple, start: str | None = None, end: str | None = None, stop_loss: float | None = None)ο
Fits the Exponential Ornstein-Uhlenbeck model to given data and assigns the discount rates, transaction costs and stop-loss level for further exit or entry-level calculation.
- Parameters:
data β (np.array/pd.DataFrame) An array with time series of portfolio prices / An array with time series of of two assets prices. The dimensions should be either nx1 or nx2.
data_frequency β (str) Data frequency [βDβ - daily, βMβ - monthly, βYβ - yearly].
discount_rate β (float/tuple) A discount rate either for both entry and exit time or a list/tuple of discount rates with exit rate and entry rate in respective order.
transaction_cost β (float/tuple) A transaction cost either for both entry and exit time or a list/tuple of transaction costs with exit cost and entry cost in respective order.
start β (Datetime) A date from which you want your training data to start.
end β (Datetime) A date at which you want your training data to end.
stop_loss β (float/int) A stop-loss level - the position is assumed to be closed immediately upon reaching this pre-defined price level.
Tip
To retrain the model just use one of the functions fit_to_portfolio
or fit_to_assets
.
You have a choice either to use the new dataset or to change the training time interval of your currently
used dataset.
- ExponentialOrnsteinUhlenbeck.fit_to_portfolio(data: array | None = None, start: str | None = None, end: str | None = None)ο
Fits the Exponential Ornstein-Uhlenbeck model to time series for portfolio prices.
- Parameters:
data β (np.array) All given prices of two assets to construct a portfolio from.
start β (Datetime) A date from which you want your training data to start.
end β (Datetime) A date at which you want your training data to end.
- ExponentialOrnsteinUhlenbeck.fit_to_assets(data: array | None = None, start: str | None = None, end: str | None = None)ο
Creates the optimal portfolio in terms of the Exponential Ornstein-Uhlenbeck model from two given time series for asset prices and fits the values of the modelβs parameters. (p.13)
- Parameters:
data β (np.array) All given prices of two assets to construct a portfolio from.
start β (Datetime) A date from which you want your training data to start.
end β (Datetime) A date at which you want your training data to end.
Step 2: Determining the optimal entry and exit valuesο
To get the optimal liquidation or entry level for your data we need to call one of the functions mentioned below. They present the solutions to the equations established during the theoretical part. To choose whether to account for the stop-loss level or not choose the respective set of functions.
Implementationο
\(b^{\xi*}\): - optimal level of liquidation:
- ExponentialOrnsteinUhlenbeck.xou_optimal_liquidation_level() float ο
Calculates the optimal liquidation portfolio level. (p.54)
- Returns:
(float) Optimal liquidation portfolio level.
\([a^{\xi*}, d^{\xi*}]\) - optimal level of entry, accounting for preferred stop-loss level:
- ExponentialOrnsteinUhlenbeck.xou_optimal_entry_interval() tuple ο
Calculates the optimal entry interval for the portfolio price. (p.35)
- Returns:
(tuple) Optimal entry interval.
Tip
General rule for the use of the optimal levels:
If not bought, buy the portfolio as soon as portfolio price reaches the optimal entry level (enters the interval).
If bought, liquidate the position as soon as portfolio price reaches the optimal liquidation level.
Step 3: Determining the optimal switching entry and exit valuesο
To get the optimal switching values we need to use the optimal_switching_levels
function that either returns
the interval of optimal switching prices or an optimal liquidation level if it is deemed not optimal to re-enter the market.
Implementationο
- ExponentialOrnsteinUhlenbeck.optimal_switching_levels() array ο
Calculates the optimal switching levels.
- Returns:
(np.array) Optimal switching levels.
Step 4: (Optional) Plot the optimal levels on your dataο
Additionally, you have the ability to plot your optimal levels for default problem and optimal switching onto your out-of-sample data. Similarly to the fit step you have a choice whether to use portfolio prices or an array of asset prices. In the case of the latter optimal coefficient found during the fit stage will be used to create a portfolio.
Implementationο
- ExponentialOrnsteinUhlenbeck.xou_plot_levels(data: DataFrame, switching: bool = False) Figure ο
Plots the found optimal exit and entry levels on the graph alongside with the given data.
- Parameters:
data β (np.array/pd.DataFrame) Time series of portfolio prices.
switching β (bool) A flag whether to take stop-loss level into account. when showcasing the results.
- Returns:
(plt.Figure) Figure with optimal exit and entry levels.
Tip
To view all the model stats, including the optimal levels call the xou_description
function.
- ExponentialOrnsteinUhlenbeck.xou_description(switching: bool = False) Series ο
Returns all the general parameters of the model, training interval timestamps if provided, the goodness of fit, allocated trading costs and discount rates, which stands for the optimal ratio between two assets in the created portfolio, default optimal levels calculated. If re-entering the market is optimal shows optimal switching levels.
- Parameters:
switching β (bool) Flag that signals whether to output switching data.
- Returns:
(pd.Series) Summary data for all model parameters and optimal levels.
Exampleο
import numpy as np
from arbitragelab.optimal_mean_reversion import ExponentialOrnsteinUhlenbeck
example = ExponentialOrnsteinUhlenbeck()
# We establish our training sample
delta_t = 1/252
np.random.seed(30)
xou_example = example.ou_model_simulation(n=1000, theta_given=1, mu_given=0.6,
sigma_given=0.2, delta_t_given=delta_t)
# Model fitting
example.fit(xou_example, data_frequency="D", discount_rate=0.05,
transaction_cost=[0.02, 0.02])
# You can separately solve optimal stopping
# and optimal switching problems
# Solving the optimal stopping problem
b = example.xou_optimal_liquidation_level()
a,d = example.xou_optimal_entry_interval()
# Solving the optimal switching problem
d_switch, b_switch = example.optimal_switching_levels()
# You can display the results using the plot
fig = example.xou_plot_levels(np.exp(xou_example), switching=True)
# Or you can view the model statistics
example.xou_description(switching=True)
Research Notebookο
The following research notebook can be used to better understand the concepts of trading under the Exponential Ornstein-Uhlenbeck Model.