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
def plot_drawdown(nav, w, alpha=0.01, height=8, width=10, ax=None): r""" Create a chart with the evolution of portfolio prices and drawdown. Parameters ---------- nav : DataFrame Cumulative assets returns. w : DataFrame, optional A portfolio specified by the user to compare with the efficient frontier. The default is None. alpha : float, optional Significante level of VaR, CVaR and CDaR. The default is 0.01. height : float, optional Height of the image in inches. The default is 8. width : float, optional Width of the image in inches. The default is 10. ax : matplotlib axis, optional If provided, plot on this axis. The default is None. Raises ------ ValueError When the value cannot be calculated. Returns ------- ax : matplotlib axis. Returns the Axes object with the plot for further tweaking. Example ------- :: nav=port.nav ax = plf.plot_drawdown(nav=nav, w=w1, alpha=0.01, height=8, width=10, ax=None) .. image:: images/Drawdown.png """ if not isinstance(nav, pd.DataFrame): raise ValueError("data must be a DataFrame") if not isinstance(w, pd.DataFrame): raise ValueError("w must be a DataFrame") if w.shape[1] > 1 and w.shape[0] == 0: w = w.T elif w.shape[1] > 1 and w.shape[0] > 0: raise ValueError("w must be a DataFrame") if nav.shape[1] != w.shape[0]: a1 = str(nav.shape) a2 = str(w.shape) raise ValueError("shapes " + a1 + " and " + a2 + " not aligned") if ax is None: fig = plt.gcf() ax = fig.subplots(nrows=2, ncols=1) ax = ax.flatten() fig.set_figwidth(width) fig.set_figheight(height) index = nav.index.tolist() a = np.array(nav, ndmin=2) a = np.insert(a, 0, 0, axis=0) a = np.diff(a, axis=0) a = np.array(a, ndmin=2) @ np.array(w, ndmin=2) prices = 1 + np.insert(a, 0, 0, axis=0) prices = np.cumprod(prices, axis=0) prices = np.ravel(prices).tolist() prices2 = 1 + np.array(np.cumsum(a, axis=0)) prices2 = np.ravel(prices2).tolist() del prices[0] DD = [] peak = -99999 for i in range(0, len(prices)): if prices2[i] > peak: peak = prices2[i] DD.append((peak - prices2[i])) DD = -np.array(DD) titles = [ "Historical Compounded Cumulative Returns", "Historical Uncompounded Drawdown", ] data = [prices, DD] color1 = ["b", "orange"] risk = [-rk.MaxAbsDD(a), -rk.AvgAbsDD(a), -rk.ConAbsDD(a, alpha)] label = [ "Maximum Drawdown: " + "{0:.2%}".format(risk[0]), "Average Drawdown: " + "{0:.2%}".format(risk[1]), "{0:.2%}".format((1 - alpha)) + " Confidence CDaR: " + "{0:.2%}".format(risk[2]), ] color2 = ["r", "limegreen", "fuchsia"] j = 0 ymin = np.min(DD) * 1.4 for i in ax: i.clear() i.plot_date(index, data[j], "-", color=color1[j]) if j == 1: i.fill_between(index, 0, data[j], facecolor=color1[j], alpha=0.3) for k in range(0, 3): i.axhline(y=risk[k], color=color2[k], linestyle="-", label=label[k]) i.set_ylim(ymin, 0) i.legend(loc="lower right") # , fontsize = 'x-small') i.set_title(titles[j]) i.set_yticklabels(["{:3.2%}".format(x) for x in i.get_yticks()]) i.grid(linestyle=":") j = j + 1 fig = plt.gcf() fig.tight_layout() return ax