In this notebook we show how to use the exponential cone to model the perspective of the log_sum_exp
function and its application in portfolio optimization.
The Entropic Value at Risk (EVaR) which is a new risk measure introduced by Ahmadi-Javid (2012). It is the upper bound based on Chernoff Inequality of Value at Risk (VaR) and Conditional Value at Risk (CVaR), formally it is defined as:
$$ \text{EVaR}_{\alpha}(X) = \inf_{z>0} \left \{z\log \left ( \frac{1}{\alpha} M_{X} (\frac{1}{z}) \right ) \right \} $$Where $M_{X} (t) = \text{E} [e^{tX}]$ is the moment generating function and $\alpha \in [0,1]$ is the significance level.
To discretize the EVaR we need the perspective of the log_sum_exp
function, we can do this using the exponential cone in CVXPY. The discipined convex programming (DCP) problem of EVaR minimization was proposed by Cajas (2021) and it is posed as:
Where $t$ is an auxiliar variable that represents the perspectives of the log_sum_exp
function, $z$ is the factor of perspective function, $u_{j}$ is an auxiliary variable, $x$ are the weights of assets, $\mu$ is the mean vector of expected returns, $\bar{\mu}$ the minimum expected return of portfolio, $K_{\text{exp}}$ is an exponential cone and $r$ is the matrix of observed returns.
####################################
# Downloading Data
####################################
!pip install --quiet yfinance
import numpy as np
import pandas as pd
import yfinance as yf
import warnings
warnings.filterwarnings("ignore")
yf.pdr_override()
pd.options.display.float_format = '{:.4%}'.format
# Date range
start = '2016-01-01'
end = '2019-12-30'
# Tickers of assets
assets = ['TGT', 'CMCSA', 'CPB', 'MO', 'T', 'BAX', 'BMY',
'MSFT', 'SEE', 'VZ', 'CNP', 'NI', 'GE', 'GOOG']
assets.sort()
# Downloading data
data = yf.download(assets, start = start, end = end)
data = data.loc[:,('Adj Close', slice(None))]
data.columns = assets
# Calculating returns
Y = data[assets].pct_change().dropna()
display(Y.head())
[*********************100%***********************] 14 of 14 completed
BAX | BMY | CMCSA | CNP | CPB | GE | GOOG | MO | MSFT | NI | SEE | T | TGT | VZ | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Date | ||||||||||||||
2016-01-05 | 0.4036% | 1.9693% | 0.0180% | 0.9305% | 0.3678% | 0.0977% | 0.0998% | 2.0213% | 0.4562% | 1.5881% | 0.9758% | 0.6987% | 1.7539% | 1.3735% |
2016-01-06 | 0.2412% | -1.7556% | -0.7727% | -1.2473% | -0.1736% | -1.5940% | 0.1400% | 1.0589% | -1.8165% | 0.5548% | -1.5647% | -0.1466% | -1.0155% | -0.9034% |
2016-01-07 | -1.6573% | -2.7699% | -1.1047% | -1.9769% | -1.2207% | -4.2314% | -2.3170% | -1.7408% | -3.4783% | -2.2066% | -3.1557% | -1.6148% | -0.2700% | -0.5492% |
2016-01-08 | -1.6037% | -2.5425% | 0.1099% | -0.2241% | 0.5707% | -1.7950% | -1.6410% | 0.1720% | 0.3067% | -0.1539% | -0.1448% | 0.0895% | -3.3838% | -0.9719% |
2016-01-11 | -1.6851% | -1.0215% | 0.0915% | -1.1791% | 0.5674% | 0.4569% | 0.2184% | 2.0948% | -0.0573% | 1.6436% | -0.1451% | 1.2224% | 1.4570% | 0.5800% |
####################################
# Finding the Min EVaR Portfolio
####################################
import cvxpy as cp
# Defining initial inputs
mu = Y.mean().to_numpy().reshape(1,-1)
returns = Y.to_numpy()
n = returns.shape[0]
# Defining initial variables
w = cp.Variable((mu.shape[1], 1))
alpha = 0.05
ret = mu @ w
X = returns @ w
# Entropic Value at Risk Model Variables
t = cp.Variable((1, 1))
z = cp.Variable((1, 1), nonneg=True)
ui = cp.Variable((n, 1))
constraints = [cp.sum(ui) <= z,
cp.constraints.ExpCone(-X - t, np.ones((n, 1)) @ z, ui)] # Exponential cone constraint
# Budget and weights constraints
constraints += [cp.sum(w) == 1,
w >= 0]
# Defining risk objective
risk = t + z * np.log(1 / (alpha * n))
objective = cp.Minimize(risk)
# Solving problem
prob = cp.Problem(objective, constraints)
prob.solve()
# Showing Optimal Weights
weights = pd.DataFrame(w.value, index=assets, columns=['Weights'])
display(weights)
Weights | |
---|---|
BAX | 5.8220% |
BMY | 8.9075% |
CMCSA | 7.9345% |
CNP | 15.6326% |
CPB | 15.5868% |
GE | 0.0000% |
GOOG | 4.7998% |
MO | 1.8486% |
MSFT | 0.0000% |
NI | 12.4118% |
SEE | 7.5494% |
T | 1.1044% |
TGT | 17.1416% |
VZ | 1.2611% |
The Entropic Drawdown at Risk (EDaR) which is a new risk measure introduced by Cajas (2021). It is the upper bound based on Chernoff Inequality of Drawdown at Risk (DaR) and Conditional Drawdown at Risk (CDaR), formally it is defined as:
$$ \begin{equation} \begin{aligned} \text{EDaR}_{\alpha}(X) & = \text{EVaR}_{\alpha}(\text{DD}(X)) \\ \text{EDaR}_{\alpha}(X) & = \inf_{z>0} \left \{ z \ln \left (\frac{1}{\alpha}M_{\text{DD}(X)} \left (\frac{1}{z} \right ) \right ) \right \} \\ \end{aligned} \end{equation} $$where $M_{X}(t)$ is the moment generating function of $t$, $\alpha \in [0,1]$ is the significance level and $\text{DD}(X)$ is the drawdown of $X$.
This problem is a linear fractional programming problem and can be converted to a DCP problem using Charnes and Cooper transformation. The DCP problem of maximization of return EDaR ratio was proposed by Cajas (2021) and it is posed as:
$$ \begin{equation} \begin{aligned} & \underset{y, \, k, \, z, \, t, \, u, \, d}{\text{min}} & & t + z \ln \left ( \frac{1}{\alpha T} \right )\\ & \text{s.t.} & & \mu y^{\tau} - r_{f} k= 1 \\ & & & \sum_{i=1}^{N} y_{i} = k \\ & & & z \geq \sum^{T}_{j=1} u_{j} \\ & & & (d_{j} - R_{j} y^{\tau} - t, z, u_{j}) \in K_{\text{exp}} \; \forall \; j =1, \ldots, T \\ & & & d_{j} \geq R_{j} y^{\tau} \; \forall \; j=1, \ldots, T \\ & & & d_{j} \geq d_{j-1} \; \forall \; j=1, \ldots, T \\ & & & d_{j} \geq 0 \; \forall \; j=1, \ldots, T \\ & & & d_{0} = 0 \\ & & & k \geq 0 \\ & & & y_{i} \geq 0 \; ; \; \forall \; i =1, \ldots, N \\ \end{aligned} \end{equation} $$where $R_{j} x^{\tau} = \sum^{j}_{i=1} r_{i} x^{\tau}$, $d_{j}$ is a variable that represents the uncompounded cumulative return of the portfolio and $r_{f}$ is the risk free rate.
Finally, the optimal portfolio is obtained making the transformation $x = y / k$.
#######################################
# Finding the max return/EDaR Portfolio
#######################################
# Defining initial inputs
mu = Y.mean().to_numpy().reshape(1,-1)
returns = Y.to_numpy()
nav = Y.cumsum().to_numpy()
n = returns.shape[0]
# Defining initial variables
w = cp.Variable((mu.shape[1], 1))
k = cp.Variable((1, 1))
rf0 = 0
alpha = 0.05
ret = mu @ w
X1 = nav @ w
# Drawdown Variables
d = cp.Variable((nav.shape[0] + 1, 1))
constraints = [d[1:] >= X1,
d[1:] >= d[:-1],
d[1:] >= 0,
d[0] == 0]
# Entropic Drawdown at Risk Model Variables
t = cp.Variable((1, 1))
z = cp.Variable((1, 1), nonneg=True)
ui = cp.Variable((n, 1))
constraints += [cp.sum(ui) <= z,
cp.constraints.ExpCone(d[1:] - X1 - t, np.ones((n, 1)) @ z, ui)] # Exponential cone constraint
# Budget and weights constraints
constraints += [cp.sum(w) == k,
ret - rf0 * k == 1,
w >= 0,
k >= 0]
# Defining risk objective
risk = t + z * np.log(1 / (alpha * n))
objective = cp.Minimize(risk)
# Solving problem
prob = cp.Problem(objective, constraints)
prob.solve()
# Showing Optimal Weights
weights = pd.DataFrame(w.value/k.value, index=assets, columns=['Weights'])
display(weights)
Weights | |
---|---|
BAX | 0.6007% |
BMY | 0.0000% |
CMCSA | 0.0000% |
CNP | 40.7637% |
CPB | 0.0000% |
GE | 0.0000% |
GOOG | 0.0000% |
MO | 0.0000% |
MSFT | 53.4767% |
NI | 0.0000% |
SEE | 0.0000% |
T | 0.0000% |
TGT | 1.4542% |
VZ | 3.7048% |
For more portfolio optimization models and applications, you can see the CVXPY based library Riskfolio-Lib.