import numpy as np
import pandas as pd
import cvxpy as cv
import riskfolio.RiskFunctions as rk
import riskfolio.ParamsEstimation as pe
import riskfolio.AuxFunctions as af
[docs]class Portfolio(object):
r"""
Class that creates a portfolio object with all properties needed to
calculate optimum portfolios.
Parameters
----------
returns : DataFrame, optional
A dataframe that containts the returns of the assets.
The default is None.
sht : bool, optional
Indicate if the portfolio consider short positions (negative weights).
The default is False.
uppersht : float, optional
Indicate the maximum value of the sum of short positions.
The default is 0.2.
upperlng : float, optional
Indicate the maximum value of the sum of long positions (positive
weights). The default is 1.
factors : DataFrame, optional
A dataframe that containts the returns of the factors.
The default is None.
alpha : float, optional
Significance level of CVaR and CDaR. The default is 0.01.
kindbench : bool, optional
True if the benchmark is a portfolio with detailed weights and False if
the benchmark is an index. The default is True.
allowTO : bool, optional
Indicate if there is turnover constraints. The default is False.
turnover : float, optional
The maximum limit of turnover deviatons. The default is 0.05.
allowTE : bool, optional
Indicate if there is tracking error constraints.. The default is False.
TE : float, optional
The maximum limit of tracking error deviatons. The default is 0.05.
benchindex : DataFrame, optional
A dataframe that containts the returns of an index. If kindbench is
the tracking error constraints are calculated respect to this index.
The default is None.
benchweights : DataFrame, optional
A dataframe that containts the weights of an index. The default is the
equally weighted portfolio 1/N.
ainequality : nd-array, optional
The matrix :math:`A` of the linear constraint :math:`A \geq B`.
The default is None.
binequality : 1d-array, optional
The matrix :math:`B` of the linear constraint :math:`A \geq B`.
The default is None.
upperdev : float, optional
Constraint on max level of standard deviation. The default is None.
uppermad : float, optional
Constraint on max level of MAD. The default is None.
uppersdev : float, optional
Constraint on max level of semi standard deviation. The default is None.
upperflpm : float, optional
Constraint on max level of first lower partial moment.
The default is None.
upperslpm : float, optional
Constraint on max level of second lower partial moment.
The default is None.
upperCVaR : float, optional
Constraint on max level of CVaR. The default is None.
upperwr : float, optional
Constraint on max level of worst realization. The default is None.
uppermdd : float, optional
Constraint on max level of maximum drawdown of uncompounded cumulated
returns. The default is None.
upperadd : float, optional
Constraint on max level of average drawdown of uncompounded cumulated
returns. The default is None.
upperCDaR : float, optional
Constraint on max level of conditional drawdown at risk of
uncompounded cumulated returns. The default is None.
"""
def __init__(
self,
returns=None,
sht=False,
uppersht=0.2,
upperlng=1,
factors=None,
alpha=0.01,
kindbench=True,
allowTO=False,
turnover=0.05,
allowTE=False,
TE=0.05,
benchindex=None,
benchweights=None,
ainequality=None,
binequality=None,
upperdev=None,
uppermad=None,
uppersdev=None,
upperflpm=None,
upperslpm=None,
upperCVaR=None,
upperwr=None,
uppermdd=None,
upperadd=None,
upperCDaR=None,
):
self._returns = returns
self.sht = sht
self.uppersht = uppersht
self.upperlng = upperlng
self._factors = factors
self.alpha = alpha
self.kindbench = kindbench
self.benchindex = benchindex
self._benchweights = benchweights
self._ainequality = ainequality
self._binequality = binequality
self.upperdev = upperdev
self.uppermad = uppermad
self.uppersdev = uppersdev
self.upperCVaR = upperCVaR
self.upperwr = upperwr
self.uppermdd = uppermdd
self.upperadd = uppermdd
self.upperCDaR = upperCDaR
self.upperflpm = upperflpm
self.upperslpm = upperslpm
self.allowTO = allowTO
self.turnover = turnover
self.allowTE = allowTE
self.TE = TE
# Inputs of Optimization Models
self.mu = None
self.cov = None
self.mu_fm = None
self.cov_fm = None
self.mu_bl = None
self.cov_bl = None
self.mu_bl_fm = None
self.cov_bl_fm = None
self.returns_fm = None
self.nav_fm = None
@property
def returns(self):
a = self._returns
if isinstance(a, pd.DataFrame):
self.assetslist = a.columns.tolist()
self.nav = a.cumsum()
self.numassets = len(self.assetslist)
else:
raise NameError("returns must be a DataFrame")
return a
@returns.setter
def returns(self, value):
a = value
if a is not None and isinstance(a, pd.DataFrame):
self._returns = a
self.assetslist = a.columns.tolist()
self.nav = a.cumsum()
self.numassets = len(self.assetslist)
else:
raise NameError("returns must be a DataFrame")
@property
def factors(self):
return self._factors
@factors.setter
def factors(self, value):
a = value
if a is not None and isinstance(a, pd.DataFrame):
if self.returns.index.equals(a.index):
self._factors = a
else:
raise NameError("factors must be a DataFrame")
@property
def benchweights(self):
n = self.numassets
if self._benchweights is not None:
if self._benchweights.shape[0] == n and self._benchweights.shape[1] == 1:
a = self._benchweights
else:
raise NameError("Weights must have a size of shape (n_assets,1)")
else:
a = np.matrix(np.ones([n, 1]) / n)
return a
@benchweights.setter
def benchweights(self, value):
a = value
n = self.numassets
if a is not None:
if a.shape[0] == n and a.shape[1] == 1:
a = a
else:
raise NameError("Weights must have a size of shape (n_assets,1)")
else:
a = np.matrix(np.ones([n, 1]) / n)
self._benchweights = a
@property
def ainequality(self):
a = self._ainequality
if a is not None:
if a.shape[1] == self.numassets:
a = a
else:
raise NameError(
"The matrix ainequality must have the same number of columns that assets' number"
)
return a
@ainequality.setter
def ainequality(self, value):
a = value
if a is not None:
if a.shape[1] == self.numassets:
a = a
else:
raise NameError(
"The matrix ainequality must have the same number of columns that assets' number"
)
self._ainequality = a
@property
def binequality(self):
a = self._binequality
if a is not None:
if a.shape[1] == 1:
a = a
else:
raise NameError("The matrix binequality must have one column")
return a
@binequality.setter
def binequality(self, value):
a = value
if a is not None:
if a.shape[1] == 1:
a = a
else:
raise NameError("The matrix binequality must have one column")
self._binequality = a
[docs] def assets_stats(self, method_mu="hist", method_cov="hist", **kwargs):
r"""
Calculate the inputs that will be use by the optimization method when
we select the input model='Classic'.
Parameters
----------
**kwargs : dict
All aditional parameters of mean_vector and covar_matrix functions.
See Also
--------
riskfolio.ParamsEstimation.mean_vector
riskfolio.ParamsEstimation.covar_matrix
"""
self.mu = pe.mean_vector(self.returns, method=method_mu, **kwargs)
self.cov = pe.covar_matrix(self.returns, method=method_cov, **kwargs)
value = af.is_pos_def(self.cov, threshold=1e-8)
if value == False:
print("You must convert self.cov to a positive definite matrix")
[docs] def blacklitterman_stats(
self,
P,
Q,
rf=0,
w=None,
delta=None,
eq=True,
method_mu="hist",
method_cov="hist",
**kwargs
):
r"""
Calculate the inputs that will be use by the optimization method when
we select the input model='BL'.
Parameters
----------
P : DataFrame of shape (n_views, n_assets)
Analyst's views matrix, can be relative or absolute.
Q: DataFrame of shape (n_views, 1)
Expected returns of analyst's views.
delta: float
Risk aversion factor. The default value is 1.
rf: scalar, optional
Risk free rate. The default is 0.
w : DataFrame of shape (n_assets, 1)
Weights matrix, where n_assets is the number of assets.
The default is None.
eq: bool, optional
Indicates if use equilibrum or historical excess returns.
The default is True.
**kwargs : dict
Other variables related to the mean and covariance estimation.
See Also
--------
riskfolio.ParamsEstimation.black_litterman
"""
X = self.returns
if w is None:
w = self.benchweights
if delta is None:
a = np.matrix(self.mu) * np.matrix(w)
delta = (a - rf) / (np.matrix(w).T * np.matrix(self.cov) * np.matrix(w))
delta = delta.item()
mu, cov, w = pe.black_litterman(
X=X,
w=w,
P=P,
Q=Q,
delta=delta,
rf=rf,
eq=eq,
method_mu=method_mu,
method_cov=method_cov,
**kwargs
)
self.mu_bl = mu
self.cov_bl = cov
value = af.is_pos_def(self.cov_bl, threshold=1e-8)
if value == False:
print("You must convert self.cov_bl to a positive definite matrix")
[docs] def factors_stats(self, method_mu="hist", method_cov="hist", **kwargs):
r"""
Calculate the inputs that will be use by the optimization method when
we select the input model='FM'.
Parameters
----------
**kwargs : dict
All aditional parameters of risk_factors function.
See Also
--------
riskfolio.ParamsEstimation.forward_regression
riskfolio.ParamsEstimation.backward_regression
riskfolio.ParamsEstimation.loadings_matrix
riskfolio.ParamsEstimation.risk_factors
"""
X = self.factors
Y = self.returns
mu, cov, returns, nav = pe.risk_factors(
X, Y, method_mu=method_mu, method_cov=method_cov, **kwargs
)
self.mu_fm = mu
self.cov_fm = cov
self.returns_fm = returns
self.nav_fm = nav
value = af.is_pos_def(self.cov_fm, threshold=1e-8)
if value == False:
print("You must convert self.cov_fm to a positive definite matrix")
[docs] def optimization(
self, model="Classic", rm="MV", obj="Sharpe", rf=0, l=2, hist=True
):
r"""
This method that calculates the optimum portfolio according to the
optimization model selected by the user. The general problem that
solves is:
.. math::
\begin{align}
&\underset{x}{\text{optimize}} & & F(w)\\
&\text{s. t.} & & Aw \geq B\\
& & & \phi_{i}(w) \leq c_{i}\\
\end{align}
Where:
:math:`F(w)` is the objective function.
:math:`Aw \geq B` is a set of linear constraints.
:math:`\phi_{i}(w) \leq c_{i}` are constraints on maximum values of
several risk measures.
Parameters
----------
model : str can be 'Classic', 'BL' or 'FM'
The model used for optimize the portfolio.
The default is 'Classic'. Posible values are:
- 'Classic': use estimates of expected return vector and covariance matrix that depends on historical data.
- 'BL': use estimates of expected return vector and covariance matrix based on the Black Litterman model.
- 'FM': use estimates of expected return vector and covariance matrix based on a Risk Factor model specified by the user.
rm : str, optional
The risk measure used to optimze the portfolio.
The default is 'MV'. Posible values are:
- 'MV': Standard Deviation.
- 'MAD': Mean Absolute Deviation.
- 'MSV': Semi Standard Deviation.
- 'FLPM': First Lower Partial Moment (Omega Ratio).
- 'SLPM': Second Lower Partial Moment (Sortino Ratio).
- 'CVaR': Conditional Value at Risk.
- 'WR': Worst Realization (Minimax)
- 'MDD': Maximum Drawdown of uncompounded returns (Calmar Ratio).
- 'ADD': Average Drawdown of uncompounded returns.
- 'CDaR': Conditional Drawdown at Risk of uncompounded returns.
obj : str can be {'MinRisk', 'Utility', 'Sharpe' or 'MaxRet'.
Objective function of the optimization model.
The default is 'Sharpe'. Posible values are:
- 'MinRisk': Minimize the selected risk measure.
- 'Utility': Maximize the Utility function :math:`mu w - l \phi_{i}(w)`.
- 'Sharpe': Maximize the risk adjusted return ratio based on the selected risk measure.
- 'MaxRet': Maximize the expected return of the portfolio.
rf : float, optional
Risk free rate, must be in the same period of assets returns.
The default is 0.
l : scalar, optional
Risk aversion factor of the 'Utility' objective function.
The default is 2.
hist : bool, optional
Indicate if uses historical or factor estimation of returns to
calculate risk measures that depends on scenarios (All except
'MV' risk measure). The default is True.
Returns
-------
w : DataFrame
The weights of optimum portfolio.
"""
# General model Variables
mu = None
sigma = None
returns = None
if model == "Classic":
mu = np.matrix(self.mu)
sigma = np.matrix(self.cov)
returns = np.matrix(self.returns)
nav = np.matrix(self.nav)
elif model == "FM":
mu = np.matrix(self.mu_fm)
if hist == False:
sigma = np.matrix(self.cov_fm)
returns = np.matrix(self.returns_fm)
nav = np.matrix(self.nav_fm)
elif hist == True:
sigma = np.matrix(self.cov)
returns = np.matrix(self.returns)
nav = np.matrix(self.nav)
elif model == "BL":
mu = np.matrix(self.mu_bl)
if hist == False:
sigma = np.matrix(self.cov_bl)
elif hist == True:
sigma = np.matrix(self.cov)
returns = np.matrix(self.returns)
nav = np.matrix(self.nav)
elif model == "BL_FM":
mu = np.matrix(self.mu_bl_fm)
if hist == False:
sigma = np.matrix(self.cov_bl_fm)
returns = np.matrix(self.returns_fm)
nav = np.matrix(self.nav_fm)
elif hist == True:
sigma = np.matrix(self.cov)
returns = np.matrix(self.returns)
nav = np.matrix(self.nav)
# General Model Variables
returns = np.matrix(returns)
w = cv.Variable((mu.shape[1], 1))
k = cv.Variable((1, 1))
rf0 = cv.Parameter(nonneg=True)
rf0.value = rf
n = cv.Parameter(nonneg=True)
n.value = returns.shape[0]
ret = mu * w
# MV Model Variables
risk1 = cv.quad_form(w, sigma)
returns_1 = af.cov_returns(sigma) * 1000
n1 = cv.Parameter(nonneg=True)
n1.value = returns_1.shape[0]
risk1_1 = cv.norm(returns_1 * w, "fro") / cv.sqrt(n1 - 1)
# MAD Model Variables
madmodel = False
Y = cv.Variable((returns.shape[0], 1))
u = np.matrix(np.ones((returns.shape[0], 1)) * mu)
a = returns - u
risk2 = cv.sum(Y) / n
# madconstraints=[a*w >= -Y, a*w <= Y, Y >= 0]
madconstraints = [a * w <= Y, Y >= 0]
# Semi Variance Model Variables
risk3 = cv.norm(Y, "fro") / cv.sqrt(n - 1)
# CVaR Model Variables
alpha1 = self.alpha
VaR = cv.Variable(1)
alpha = cv.Parameter(nonneg=True)
alpha.value = alpha1
X = returns * w
Z = cv.Variable((returns.shape[0], 1))
risk4 = VaR + 1 / (alpha * n) * cv.sum(Z)
cvarconstraints = [Z >= 0, Z >= -X - VaR]
# Worst Realization (Minimax) Model Variables
M = cv.Variable(1)
risk5 = M
wrconstraints = [-X <= M]
# Lower Partial Moment Variables
lpmmodel = False
lpm = cv.Variable((returns.shape[0], 1))
lpmconstraints = [lpm >= 0]
if obj == "Sharpe":
lpmconstraints += [lpm >= rf0 * k - X]
else:
lpmconstraints += [lpm >= rf0 - X]
# First Lower Partial Moment (Omega) Model Variables
risk6 = cv.sum(lpm) / n
# Second Lower Partial Moment (Sortino) Model Variables
risk7 = cv.norm(lpm, "fro") / cv.sqrt(n - 1)
# Drawdown Model Variables
drawdown = False
if obj == "Sharpe":
X1 = k + nav * w
else:
X1 = 1 + nav * w
U = cv.Variable((nav.shape[0] + 1, 1))
ddconstraints = [U[1:] >= X1, U[1:] >= U[:-1]]
if obj == "Sharpe":
ddconstraints += [U[1:] >= k, U[0] == k]
else:
ddconstraints += [U[1:] >= 1, U[0] == 1]
# Maximum Drawdown Model Variables
MDD = cv.Variable(1)
risk8 = MDD
mddconstraints = [MDD >= U[1:] - X1]
# Average Drawdown Model Variables
risk9 = 1 / n * cv.sum(U[1:] - X1)
# Conditional Drawdown Model Variables
CDaR = cv.Variable(1)
Zd = cv.Variable((nav.shape[0], 1))
risk10 = CDaR + 1 / (alpha * n) * cv.sum(Zd)
cdarconstraints = [Zd >= U[1:] - X1 - CDaR, Zd >= 0]
# Tracking Error Model Variables
c = self.benchweights
if self.kindbench == True:
bench = np.matrix(returns) * c
else:
bench = self.benchindex
if obj == "Sharpe":
TE = cv.norm(returns * w - bench * k, "fro") / cv.sqrt(n - 1)
else:
TE = cv.norm(returns * w - bench, "fro") / cv.sqrt(n - 1)
# Problem aditional linear constraints
if obj == "Sharpe":
constraints = [
cv.sum(w) == self.upperlng * k,
k >= 0,
mu * w - rf0 * k == 1,
]
if self.sht == False:
constraints += [w <= self.upperlng * k, w * 1000 >= 0]
elif self.sht == True:
constraints += [
w <= self.upperlng * k,
w >= -self.uppersht * k,
cv.sum(cv.neg(w)) <= self.uppersht * k,
]
else:
constraints = [cv.sum(w) == self.upperlng]
if self.sht == False:
constraints += [w <= self.upperlng, w * 1000 >= 0]
elif self.sht == True:
constraints += [
w <= self.upperlng,
w >= -self.uppersht,
cv.sum(cv.neg(w)) <= self.uppersht,
]
if self.ainequality is not None and self.binequality is not None:
A = np.matrix(self.ainequality)
B = np.matrix(self.binequality)
if obj == "Sharpe":
constraints += [A * w - B * k >= 0]
else:
constraints += [A * w - B >= 0]
# Turnover Constraints
if obj == "Sharpe":
if self.allowTO == True:
constraints += [cv.abs(w - c * k) * 1000 <= self.turnover * k * 1000]
else:
if self.allowTO == True:
constraints += [cv.abs(w - c) * 1000 <= self.turnover * 1000]
# Tracking error Constraints
if obj == "Sharpe":
if self.allowTE == True:
constraints += [TE <= self.TE * k]
else:
if self.allowTE == True:
constraints += [TE <= self.TE]
# Problem risk Constraints
if self.upperdev is not None:
if obj == "Sharpe":
constraints += [risk1_1 <= self.upperdev * k]
else:
constraints += [risk1 <= self.upperdev ** 2]
if self.uppermad is not None:
if obj == "Sharpe":
constraints += [risk2 <= self.uppermad * k / 2]
else:
constraints += [risk2 <= self.uppermad / 2]
madmodel = True
if self.uppersdev is not None:
if obj == "Sharpe":
constraints += [risk3 <= self.uppersdev * k]
else:
constraints += [risk3 <= self.uppersdev]
madmodel = True
if self.upperCVaR is not None:
if obj == "Sharpe":
constraints += [risk4 <= self.upperCVaR * k]
else:
constraints += [risk4 <= self.upperCVaR]
constraints += cvarconstraints
if self.upperwr is not None:
if obj == "Sharpe":
constraints += [-X <= self.upperwr * k]
else:
constraints += [-X <= self.upperwr]
constraints += wrconstraints
if self.upperflpm is not None:
if obj == "Sharpe":
constraints += [risk6 <= self.upperflpm * k]
else:
constraints += [risk6 <= self.upperflpm]
lpmmodel = True
if self.upperslpm is not None:
if obj == "Sharpe":
constraints += [risk7 <= self.upperslpm * k]
else:
constraints += [risk7 <= self.upperslpm]
lpmmodel = True
if self.uppermdd is not None:
if obj == "Sharpe":
constraints += [U[1:] - X1 <= self.uppermdd * k]
else:
constraints += [U[1:] - X1 <= self.uppermdd]
constraints += mddconstraints
drawdown = True
if self.upperadd is not None:
if obj == "Sharpe":
constraints += [risk9 <= self.upperadd * k]
else:
constraints += [risk9 <= self.upperadd]
drawdown = True
if self.upperCDaR is not None:
if obj == "Sharpe":
constraints += [risk10 <= self.upperCDaR * k]
else:
constraints += [risk10 <= self.upperCDaR]
constraints += cdarconstraints
drawdown = True
# Defining risk function
if rm == "MV":
if model != "Classic":
risk = risk1_1
elif model == "Classic":
risk = risk1
elif rm == "MAD":
risk = risk2
madmodel = True
elif rm == "MSV":
risk = risk3
madmodel = True
elif rm == "CVaR":
risk = risk4
if self.upperCVaR is None:
constraints += cvarconstraints
elif rm == "WR":
risk = risk5
if self.upperwr is None:
constraints += wrconstraints
elif rm == "FLPM":
risk = risk6
lpmmodel = True
elif rm == "SLPM":
risk = risk7
lpmmodel = True
elif rm == "MDD":
risk = risk8
drawdown = True
if self.uppermdd is None:
constraints += mddconstraints
elif rm == "ADD":
risk = risk9
drawdown = True
elif rm == "CDaR":
risk = risk10
drawdown = True
if self.upperCDaR is None:
constraints += cdarconstraints
if madmodel == True:
constraints += madconstraints
if lpmmodel == True:
constraints += lpmconstraints
if drawdown == True:
constraints += ddconstraints
# Frontier Variables
portafolio = {}
for i in self.assetslist:
portafolio.update({i: []})
# Optimization Process
# Defining solvers
solvers = [cv.ECOS, cv.SCS, cv.OSQP, cv.CVXOPT, cv.GLPK]
# Defining objective function
if obj == "Sharpe":
if rm != "Classic":
objective = cv.Minimize(risk)
elif rm == "Classic":
objective = cv.Minimize(risk * 1000)
elif obj == "MinRisk":
objective = cv.Minimize(risk)
elif obj == "Utility":
objective = cv.Maximize(ret - l * risk)
elif obj == "MaxRet":
objective = cv.Maximize(ret)
try:
prob = cv.Problem(objective, constraints)
for solver in solvers:
try:
prob.solve(
solver=solver, parallel=True, max_iters=2000, abstol=1e-10
)
except:
pass
if w.value is not None:
break
if obj == "Sharpe":
weights = np.matrix(w.value / k.value).T
else:
weights = np.matrix(w.value).T
if self.sht == False:
weights = np.abs(weights) / np.sum(np.abs(weights))
for j in self.assetslist:
portafolio[j].append(weights[0, self.assetslist.index(j)])
except:
pass
optimum = pd.DataFrame(portafolio, index=["weights"], dtype=np.float64).T
return optimum
[docs] def frontier_limits(self, model="Classic", rm="MV", rf=0, hist=True):
r"""
Method that calculates the minimum risk and maximum return portfolios
available with current assets and constraints.
Parameters
----------
model : str, optional
Methodology used to estimate input parameters.
The default is 'Classic'.
rm : str, optional
Risk measure used by the optimization model. The default is 'MV'.
rf : scalar, optional
Risk free rate. The default is 0.
hist : bool, optional
Indicate if uses historical or factor estimation of returns to
calculate risk measures that depends on scenarios (All except
'MV' risk measure). The default is True.
Returns
-------
limits : DataFrame
A dataframe that containts the weights of the portfolios.
Notes
-----
This method is preferable (faster) to use instead of efficient_frontier
method to know the range of expected return and expected risk.
"""
w_min = self.optimization(
model=model, rm=rm, obj="MinRisk", rf=rf, l=0, hist=hist
)
w_max = self.optimization(
model=model, rm=rm, obj="MaxRet", rf=rf, l=0, hist=hist
)
limits = pd.concat([w_min, w_max], axis=1)
limits.columns = ["w_min", "w_max"]
return limits
[docs] def efficient_frontier(self, model="Classic", rm="MV", points=20, rf=0, hist=True):
r"""
Method that calculates several portfolios in the efficient frontier
of the selected risk measure, available with current assets and
constraints.
Parameters
----------
model : str, optional
Methodology used to estimate input parameters.
The default is 'Classic'.
rm : str, optional
Risk measure used by the optimization model. The default is 'MV'.
points : scalar, optional
Number of point calculated from the efficient frontier.
The default is 50.
rf : scalar, optional
Risk free rate. The default is 0.
hist : bool, optional
Indicate if uses historical or factor estimation of returns to
calculate risk measures that depends on scenarios (All except
'MV' risk measure). The default is True.
Returns
-------
frontier : DataFrame
A dataframe that containts the weights of the portfolios.
Notes
-----
It's recommendable that don't use this method when there are too many
assets (more than 100) and you are using a scenario based risk measure
(all except standard deviation). It's preferable to use frontier_limits
method (faster) to know the range of expected return and expected risk.
"""
mu = None
sigma = None
returns = None
if model == "Classic":
mu = np.matrix(self.mu)
sigma = np.matrix(self.cov)
returns = np.matrix(self.returns)
nav = np.matrix(self.nav)
elif model == "FM":
mu = np.matrix(self.mu_fm)
if hist == False:
sigma = np.matrix(self.cov_fm)
returns = np.matrix(self.returns_fm)
nav = np.matrix(self.nav_fm)
elif hist == True:
sigma = np.matrix(self.cov)
returns = np.matrix(self.returns)
nav = np.matrix(self.nav)
elif model == "BL":
mu = np.matrix(self.mu_bl)
if hist == False:
sigma = np.matrix(self.cov_bl)
elif hist == True:
sigma = np.matrix(self.cov)
returns = np.matrix(self.returns)
nav = np.matrix(self.nav)
elif model == "BL_FM":
mu = np.matrix(self.mu_bl_fm_2)
if hist == False:
sigma = np.matrix(self.cov_bl_fm_2)
returns = np.matrix(self.returns_fm)
nav = np.matrix(self.nav_fm)
elif hist == True:
sigma = np.matrix(self.cov)
returns = np.matrix(self.returns)
nav = np.matrix(self.nav)
alpha1 = self.alpha
limits = self.frontier_limits(model="Classic", rm=rm, rf=rf, hist=hist)
w_min = np.matrix(limits.iloc[:, 0]).T
w_max = np.matrix(limits.iloc[:, 1]).T
ret_min = (mu * w_min).item()
ret_max = (mu * w_max).item()
if rm == "MV":
risk_min = np.sqrt(w_min.T * sigma * w_min).item()
risk_max = np.sqrt(w_max.T * sigma * w_max).item()
elif rm == "MAD":
risk_min = rk.MAD(returns * w_min)
risk_max = rk.MAD(returns * w_max)
elif rm == "MSV":
risk_min = rk.SemiDeviation(returns * w_min)
risk_max = rk.SemiDeviation(returns * w_max)
elif rm == "CVaR":
risk_min = rk.CVaR_Hist(returns * w_min, alpha1)
risk_max = rk.CVaR_Hist(returns * w_max, alpha1)
elif rm == "WR":
risk_min = rk.WR(returns * w_min)
risk_max = rk.WR(returns * w_max)
elif rm == "FLPM":
risk_min = rk.LPM(returns * w_min, rf, 1)
risk_max = rk.LPM(returns * w_max, rf, 1)
elif rm == "SLPM":
risk_min = rk.LPM(returns * w_min, rf, 2)
risk_max = rk.LPM(returns * w_max, rf, 2)
elif rm == "MDD":
risk_min = rk.MaxAbsDD(returns * w_min)
risk_max = rk.MaxAbsDD(returns * w_max)
elif rm == "ADD":
risk_min = rk.AvgAbsDD(returns * w_min)
risk_max = rk.AvgAbsDD(returns * w_max)
elif rm == "CDaR":
risk_min = rk.ConAbsDD(returns * w_min, alpha1)
risk_max = rk.ConAbsDD(returns * w_max, alpha1)
mus = np.linspace(ret_min, ret_max + (ret_max - ret_min) / (points), points + 1)
risks = np.linspace(
risk_min, risk_max + (risk_max - risk_min) / (points), points + 1
)
risk_lims = [
"upperdev",
"uppermad",
"uppersdev",
"upperCVaR",
"upperwr",
"upperflpm",
"upperslpm",
"uppermdd",
"upperadd",
"upperCDaR",
]
risk_names = [
"MV",
"MAD",
"MSV",
"CVaR",
"WR",
"FLPM",
"SLPM",
"MDD",
"ADD",
"CDaR",
]
item = risk_names.index(rm)
frontier = []
n = 0
for i in range(len(risks)):
try:
if n == 0:
w = self.optimization(
model=model, rm=rm, obj="MinRisk", rf=rf, l=0, hist=hist
)
else:
setattr(self, risk_lims[item], risks[i])
w = self.optimization(
model=model, rm=rm, obj="MaxRet", rf=rf, l=0, hist=hist
)
n += 1
frontier.append(w)
except:
pass
setattr(self, risk_lims[item], None)
frontier = pd.concat(frontier, axis=1)
frontier.columns = list(range(len(risks)))
return frontier
[docs] def reset_risk_constraints(self):
r"""
Reset all risk constraints.
"""
cons = [
"upperdev",
"uppermad",
"uppersdev",
"upperCVaR",
"upperwr",
"upperflpm",
"upperslpm",
"uppermdd",
"upperadd",
"upperCDaR",
]
for i in cons:
setattr(self, i, None)
[docs] def reset_linear_constraints(self):
r"""
Reset all linear constraints.
"""
self.ainequality = None
self.binequality = None
[docs] def reset_all(self):
r"""
Reset portfolio object to defatult values.
"""
self.sht = False
self.uppersht = 0.2
self.upperlng = 1
self._factors = None
self.alpha = 0.01
self.kindbench = True
self.benchindex = None
self._benchweights = None
self.allowTO = False
self.turnover = 0.05
self.allowTE = False
self.TE = 0.05
self.reset_risk_constraints()
self.reset_linear_constraints()
self.reset_inputs()