def plot_table(returns, w, MAR=0, alpha=0.05, height=9, width=12, t_factor=252, ax=None): r""" Create a table with information about risk measures and risk adjusted return ratios. Parameters ---------- returns : DataFrame Assets returns. w : DataFrame Portfolio weights. MAR: float, optional Minimum acceptable return. alpha: float, optional Significance level for VaR, CVaR, EVaR, DaR and CDaR. height : float, optional Height of the image in inches. The default is 9. width : float, optional Width of the image in inches. The default is 12. t_factor : float, optional Factor used to annualize expected return and expected risks for risk measures based on returns (not drawdowns). The default is 252. .. math:: \begin{align} \text{Annualized Return} & = \text{Return} \, \times \, \text{t_factor} \\ \text{Annualized Risk} & = \text{Risk} \, \times \, \sqrt{\text{t_factor}} \end{align} 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 ------- :: ax = plf.plot_table(returns=Y, w=w1, MAR=0, alpha=0.05, ax=None) .. image:: images/Port_Table.png """ if not isinstance(returns, pd.DataFrame): raise ValueError("returns must be a DataFrame") if not isinstance(w, pd.DataFrame): raise ValueError("w must be a DataFrame") if returns.shape[1] != w.shape[0]: a1 = str(returns.shape) a2 = str(w.shape) raise ValueError("shapes " + a1 + " and " + a2 + " not aligned") if ax is None: ax = plt.gca() fig = plt.gcf() fig.set_figwidth(width) fig.set_figheight(height) mu = returns.mean() cov = returns.cov() days = (returns.index[-1] - returns.index[0]).days + 1 X = returns @ w X = X.to_numpy().ravel() rowLabels = [ "Profitability and Other Inputs", "Mean Return (1)", "Compound Annual Growth Rate (CAGR)", "Minimum Acceptable Return (MAR) (1)", "Significance Level", "", "Risk Measures based on Returns", "Standard Deviation (2)", "Mean Absolute Deviation (MAD) (2)", "Semi Standard Deviation (2)", "First Lower Partial Moment (FLPM) (2)", "Second Lower Partial Moment (SLPM) (2)", "Value at Risk (VaR) (2)", "Conditional Value at Risk (CVaR) (2)", "Entropic Value at Risk (EVaR) (2)", "Worst Realization (2)", "Skewness", "Kurtosis", "", "Risk Measures based on Drawdowns (3)", "Max Drawdown (MDD)", "Average Drawdown (ADD)", "Drawdown at Risk (DaR)", "Conditional Drawdown at Risk (CDaR)", "Ulcer Index", "(1) Annualized, multiplied by " + str(t_factor), "(2) Annualized, multiplied by √" + str(t_factor), "(3) Based on uncompounded cumulated returns", ] indicators = [ "", (mu @ w).to_numpy().item() * t_factor, np.power(np.prod(1 + X), 360 / days) - 1, MAR, alpha, "", "", np.sqrt(w.T @ cov @ w).to_numpy().item() * t_factor**0.5, rk.MAD(X) * t_factor**0.5, rk.SemiDeviation(X) * t_factor**0.5, rk.LPM(X, MAR=MAR, p=1) * t_factor**0.5, rk.LPM(X, MAR=MAR, p=2) * t_factor**0.5, rk.VaR_Hist(X, alpha=alpha) * t_factor**0.5, rk.CVaR_Hist(X, alpha=alpha) * t_factor**0.5, rk.EVaR_Hist(X, alpha=alpha)[0] * t_factor**0.5, rk.WR(X) * t_factor**0.5, st.skew(X, bias=False), st.kurtosis(X, bias=False), "", "", rk.MDD_Abs(X), rk.ADD_Abs(X), rk.DaR_Abs(X), rk.CDaR_Abs(X, alpha=alpha), rk.UCI_Abs(X), "", "", "", ] ratios = [] for i in range(len(indicators)): if i < 6 or indicators[i] == "" or rowLabels[i] in [ "Skewness", "Kurtosis" ]: ratios.append("") else: ratio = (indicators[1] - MAR) / indicators[i] ratios.append(ratio) for i in range(len(indicators)): if indicators[i] != "": if rowLabels[i] in ["Skewness", "Kurtosis"]: indicators[i] = "{:.5f}".format(indicators[i]) else: indicators[i] = "{:.4%}".format(indicators[i]) if ratios[i] != "": ratios[i] = "{:.6f}".format(ratios[i]) data = pd.DataFrame({ "A": rowLabels, "B": indicators, "C": ratios }).to_numpy() ax.set_axis_off() ax.axis("tight") ax.axis("off") colLabels = ["", "Values", "(Return - MAR)/Risk"] colWidths = [0.45, 0.275, 0.275] rowHeight = 0.07 table = ax.table( cellText=data, colLabels=colLabels, colWidths=colWidths, cellLoc="center", loc="upper left", bbox=[-0.03, 0, 1, 1], ) table.auto_set_font_size(False) cellDict = table.get_celld() k = 1 rowHeight = 1 / len(rowLabels) ncols = len(colLabels) nrows = len(rowLabels) for i in range(0, ncols): cellDict[(0, i)].set_text_props(weight="bold", color="white", size="x-large") cellDict[(0, i)].set_facecolor("darkblue") cellDict[(0, i)].set_edgecolor("white") cellDict[(0, i)].set_height(rowHeight) for j in range(1, nrows + 1): cellDict[(j, 0)].set_text_props(weight="bold", color="black", size="x-large", ha="left") cellDict[(j, i)].set_text_props(color="black", size="x-large") cellDict[(j, 0)].set_edgecolor("white") cellDict[(j, i)].set_edgecolor("white") if k % 2 != 0: cellDict[(j, 0)].set_facecolor("whitesmoke") cellDict[(j, i)].set_facecolor("whitesmoke") if j in [6, 19]: cellDict[(j, 0)].set_facecolor("white") cellDict[(j, i)].set_facecolor("white") if j in [1, 7, 20]: cellDict[(j, 0)].set_text_props(color="white") cellDict[(j, 0)].set_facecolor("orange") cellDict[(j, i)].set_facecolor("orange") k = 1 k += 1 cellDict[(j, i)].set_height(rowHeight) for i in range(0, ncols): for j in range(nrows - 2, nrows + 1): cellDict[(j, i)].set_text_props(weight="normal", color="black", size="large") cellDict[(j, i)].set_facecolor("white") fig = plt.gcf() fig.tight_layout() return ax
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