Example #1
0
    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
Example #2
0
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