Source code for Portfolio

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_inputs(self): r""" Reset all inputs parameters of optimization models. """ cons = [ "mu", "cov", "mu_fm", "cov_fm", "mu_bl", "cov_bl", "mu_bl_fm", "cov_bl_fm", "returns_fm", "nav_fm", ] for i in cons: setattr(self, i, 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()