Example #1
0
    def _naive_risk(self, returns, cov, rm="MV", rf=0):
        assets = returns.columns.tolist()
        n = len(assets)

        if rm == "equal":
            weight = np.ones((n, 1)) * 1 / n
        else:
            inv_risk = np.zeros((n, 1))
            for i in assets:
                k = assets.index(i)
                w = np.zeros((n, 1))
                w[k, 0] = 1
                w = pd.DataFrame(w, columns=["weights"], index=assets)
                if rm == "vol":
                    risk = rk.Sharpe_Risk(w,
                                          cov=cov,
                                          returns=returns,
                                          rm="MV",
                                          rf=rf,
                                          alpha=self.alpha)
                else:
                    risk = rk.Sharpe_Risk(w,
                                          cov=cov,
                                          returns=returns,
                                          rm=rm,
                                          rf=rf,
                                          alpha=self.alpha)
                inv_risk[k, 0] = risk

            if rm == "MV":
                inv_risk = 1 / np.power(inv_risk, 2)
            else:
                inv_risk = 1 / inv_risk
            weight = inv_risk * (1 / np.sum(inv_risk))

        weight = weight.reshape(-1, 1)

        return weight
Example #2
0
    def _hierarchical_recursive_bisection(self,
                                          Z,
                                          rm="MV",
                                          rf=0,
                                          linkage="ward",
                                          model="HERC"):

        # Transform linkage to tree and reverse order
        root, nodes = hr.to_tree(Z, rd=True)
        nodes = nodes[::-1]
        weight = pd.Series(1, index=self.cov.index)  # Set initial weights to 1

        clusters_inds = hr.fcluster(Z, self.k, criterion="maxclust")
        clusters = {
            i: []
            for i in range(min(clusters_inds),
                           max(clusters_inds) + 1)
        }
        for i, v in enumerate(clusters_inds):
            clusters[v].append(i)

        # Loop through k clusters
        for i in nodes[:self.k - 1]:
            if i.is_leaf() == False:  # skip leaf-nodes
                left = i.get_left().pre_order(
                )  # lambda i: i.id) # get left cluster
                right = i.get_right().pre_order(
                )  # lambda i: i.id) # get right cluster
                left_set = set(left)
                right_set = set(right)
                left_risk = 0
                right_risk = 0

                # Allocate weight to clusters
                if rm == "equal":
                    w_1 = 0.5

                else:
                    for j in clusters.keys():
                        if set(clusters[j]).issubset(left_set):
                            # Left cluster
                            left_cov = self.cov.iloc[clusters[j], clusters[j]]
                            left_returns = self.returns.iloc[:, clusters[j]]
                            left_weight = self._naive_risk(left_returns,
                                                           left_cov,
                                                           rm=rm,
                                                           rf=rf)

                            if rm == "vol":
                                left_risk_ = rk.Sharpe_Risk(
                                    left_weight,
                                    cov=left_cov,
                                    returns=left_returns,
                                    rm="MV",
                                    rf=rf,
                                    alpha=self.alpha,
                                )
                            else:
                                left_risk_ = rk.Sharpe_Risk(
                                    left_weight,
                                    cov=left_cov,
                                    returns=left_returns,
                                    rm=rm,
                                    rf=rf,
                                    alpha=self.alpha,
                                )
                                if rm == "MV":
                                    left_risk_ = np.power(left_risk_, 2)

                            left_risk += left_risk_

                        if set(clusters[j]).issubset(right_set):
                            # Right cluster
                            right_cov = self.cov.iloc[clusters[j], clusters[j]]
                            right_returns = self.returns.iloc[:, clusters[j]]
                            right_weight = self._naive_risk(right_returns,
                                                            right_cov,
                                                            rm=rm,
                                                            rf=rf)

                            if rm == "vol":
                                right_risk_ = rk.Sharpe_Risk(
                                    right_weight,
                                    cov=right_cov,
                                    returns=right_returns,
                                    rm="MV",
                                    rf=rf,
                                    alpha=self.alpha,
                                )
                            else:
                                right_risk_ = rk.Sharpe_Risk(
                                    right_weight,
                                    cov=right_cov,
                                    returns=right_returns,
                                    rm=rm,
                                    rf=rf,
                                    alpha=self.alpha,
                                )
                                if rm == "MV":
                                    right_risk_ = np.power(right_risk_, 2)

                            right_risk += right_risk_

                    w_1 = 1 - left_risk / (left_risk + right_risk)

                weight[left] *= w_1  # weight 1
                weight[right] *= 1 - w_1  # weight 2

        # Get constituents of k clusters
        clustered_assets = pd.Series(hr.cut_tree(Z,
                                                 n_clusters=self.k).flatten(),
                                     index=self.cov.index)

        # Multiply within-cluster weight with inter-cluster weight
        for i in range(self.k):
            cluster = clustered_assets.loc[clustered_assets == i]
            cluster_cov = self.cov.loc[cluster.index, cluster.index]
            cluster_returns = self.returns.loc[:, cluster.index]
            if model == "HERC":
                cluster_weights = pd.Series(
                    self._naive_risk(cluster_returns,
                                     cluster_cov,
                                     rm=rm,
                                     rf=rf).flatten(),
                    index=cluster_cov.index,
                )
            elif model == "HERC2":
                cluster_weights = pd.Series(
                    self._naive_risk(cluster_returns,
                                     cluster_cov,
                                     rm="equal",
                                     rf=rf).flatten(),
                    index=cluster_cov.index,
                )
            weight.loc[cluster_weights.index] *= cluster_weights

        return weight
Example #3
0
    def _recursive_bisection(self, sort_order, rm="MV", rf=0):
        weight = pd.Series(1, index=sort_order)  # set initial weights to 1
        items = [sort_order]

        while len(items) > 0:  # loop while weights is under 100%
            items = [
                i[j:k] for i in items for j, k in (
                    (0, len(i) // 2),
                    (len(i) // 2, len(i)),
                )  # get cluster indi
                if len(i) > 1
            ]

            # allocate weight to left and right cluster
            for i in range(0, len(items), 2):
                left_cluster = items[i]
                right_cluster = items[i + 1]

                # Left cluster
                left_cov = self.cov.iloc[left_cluster, left_cluster]
                left_returns = self.returns.iloc[:, left_cluster]
                left_weight = self._naive_risk(left_returns,
                                               left_cov,
                                               rm=rm,
                                               rf=rf)

                if rm == "vol":
                    left_risk = rk.Sharpe_Risk(
                        left_weight,
                        cov=left_cov,
                        returns=left_returns,
                        rm="MV",
                        rf=rf,
                        alpha=self.alpha,
                    )
                else:
                    left_risk = rk.Sharpe_Risk(
                        left_weight,
                        cov=left_cov,
                        returns=left_returns,
                        rm=rm,
                        rf=rf,
                        alpha=self.alpha,
                    )
                    if rm == "MV":
                        left_risk = np.power(left_risk, 2)

                # Right cluster
                right_cov = self.cov.iloc[right_cluster, right_cluster]
                right_returns = self.returns.iloc[:, right_cluster]
                right_weight = self._naive_risk(right_returns,
                                                right_cov,
                                                rm=rm,
                                                rf=rf)

                if rm == "vol":
                    right_risk = rk.Sharpe_Risk(
                        right_weight,
                        cov=right_cov,
                        returns=right_returns,
                        rm="MV",
                        rf=rf,
                        alpha=self.alpha,
                    )
                else:
                    right_risk = rk.Sharpe_Risk(
                        right_weight,
                        cov=right_cov,
                        returns=right_returns,
                        rm=rm,
                        rf=rf,
                        alpha=self.alpha,
                    )
                    if rm == "MV":
                        right_risk = np.power(right_risk, 2)

                # Allocate weight to clusters
                alpha = 1 - left_risk / (left_risk + right_risk)
                weight[left_cluster] *= alpha  # weight 1
                weight[right_cluster] *= 1 - alpha  # weight 2

        weight.index = self.asset_order

        return weight
def plot_frontier(
    w_frontier,
    mu,
    cov=None,
    returns=None,
    rm="MV",
    rf=0,
    alpha=0.05,
    cmap="viridis",
    w=None,
    label="Portfolio",
    marker="*",
    s=16,
    c="r",
    height=6,
    width=10,
    t_factor=252,
    ax=None,
):
    r"""
    Creates a plot of the efficient frontier for a risk measure specified by
    the user.

    Parameters
    ----------
    w_frontier : DataFrame
        Portfolio weights of some points in the efficient frontier.
    mu : DataFrame of shape (1, n_assets)
        Vector of expected returns, where n_assets is the number of assets.
    cov : DataFrame of shape (n_features, n_features)
        Covariance matrix, where n_features is the number of features.
    returns : DataFrame of shape (n_samples, n_features)
        Features matrix, where n_samples is the number of samples and
        n_features is the number of features.
    rm : str, optional
        The risk measure used to estimate the frontier.
        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.
        - 'EVaR': Conditional Value at Risk.
        - 'WR': Worst Realization (Minimax)
        - 'MDD': Maximum Drawdown of uncompounded returns (Calmar Ratio).
        - 'ADD': Average Drawdown of uncompounded returns.
        - 'DaR': Drawdown at Risk of uncompounded returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded returns.
        - 'UCI': Ulcer Index of uncompounded returns.

    rf : float, optional
        Risk free rate or minimum aceptable return. The default is 0.
    alpha : float, optional
        Significante level of VaR, CVaR, EVaR, DaR and CDaR.
        The default is 0.05.
    cmap : cmap, optional
        Colorscale, represente the risk adjusted return ratio.
        The default is 'viridis'.
    w : DataFrame of shape (n_assets, 1), optional
        A portfolio specified by the user. The default is None.
    label : str, optional
        Name of portfolio that appear on plot legend.
        The default is 'Portfolio'.
    marker : str, optional
        Marker of w. The default is "*".
    s : float, optional
        Size of marker. The default is 16.
    c : str, optional
        Color of marker. The default is 'r'.
    height : float, optional
        Height of the image in inches. The default is 6.
    width : float, optional
        Width of the image in inches. The default is 10.
    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 Axes
        Returns the Axes object with the plot for further tweaking.

    Example
    -------
    ::

        label = 'Max Risk Adjusted Return Portfolio'
        mu = port.mu
        cov = port.cov
        returns = port.returns

        ax = plf.plot_frontier(w_frontier=ws, mu=mu, cov=cov, returns=returns,
                               rm=rm, rf=0, alpha=0.05, cmap='viridis', w=w1,
                               label=label, marker='*', s=16, c='r',
                               height=6, width=10, t_factor=252, ax=None)

    .. image:: images/MSV_Frontier.png


    """

    if not isinstance(w_frontier, pd.DataFrame):
        raise ValueError("w_frontier must be a DataFrame")

    if not isinstance(mu, pd.DataFrame):
        raise ValueError("mu must be a DataFrame")

    if not isinstance(cov, pd.DataFrame):
        raise ValueError("cov must be a DataFrame")

    if not isinstance(returns, pd.DataFrame):
        raise ValueError("returns must be a DataFrame")

    if returns.shape[1] != w_frontier.shape[0]:
        a1 = str(returns.shape)
        a2 = str(w_frontier.shape)
        raise ValueError("shapes " + a1 + " and " + a2 + " not aligned")

    if w is not None:
        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 column 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_ = np.array(mu, ndmin=2)

    ax.set_ylabel("Expected Return")
    item = rmeasures.index(rm)
    x_label = rm_names[item] + " (" + rm + ")"
    ax.set_xlabel("Expected Risk - " + x_label)

    title = "Efficient Frontier Mean - " + x_label
    ax.set_title(title)

    X1 = []
    Y1 = []
    Z1 = []

    for i in range(w_frontier.shape[1]):
        weights = np.array(w_frontier.iloc[:, i], ndmin=2).T
        risk = rk.Sharpe_Risk(weights,
                              cov=cov,
                              returns=returns,
                              rm=rm,
                              rf=rf,
                              alpha=alpha)

        ret = mu_ @ weights
        ret = ret.item() * t_factor

        if rm not in ["MDD", "ADD", "CDaR", "UCI"]:
            risk = risk * t_factor**0.5

        ratio = (ret - rf) / risk

        X1.append(risk)
        Y1.append(ret)
        Z1.append(ratio)

    ax1 = ax.scatter(X1, Y1, c=Z1, cmap=cmap)

    if w is not None:
        X2 = []
        Y2 = []
        for i in range(w.shape[1]):
            weights = np.array(w.iloc[:, i], ndmin=2).T
            risk = rk.Sharpe_Risk(weights,
                                  cov=cov,
                                  returns=returns,
                                  rm=rm,
                                  rf=rf,
                                  alpha=alpha)
            ret = mu_ @ weights
            ret = ret.item() * t_factor

            if rm not in ["MDD", "ADD", "CDaR", "UCI"]:
                risk = risk * t_factor**0.5

            ratio = (ret - rf) / risk

            X2.append(risk)
            Y2.append(ret)

        ax.scatter(X2, Y2, marker=marker, s=s**2, c=c, label=label)
        ax.legend(loc="upper left")

    xmin = np.min(X1) - np.abs(np.max(X1) - np.min(X1)) * 0.1
    xmax = np.max(X1) + np.abs(np.max(X1) - np.min(X1)) * 0.1
    ymin = np.min(Y1) - np.abs(np.max(Y1) - np.min(Y1)) * 0.1
    ymax = np.max(Y1) + np.abs(np.max(Y1) - np.min(Y1)) * 0.1

    ax.set_ylim(ymin, ymax)
    ax.set_xlim(xmin, xmax)

    ax.xaxis.set_major_locator(plt.AutoLocator())

    ax.set_yticklabels(["{:.4%}".format(x) for x in ax.get_yticks()])
    ax.set_xticklabels(["{:.4%}".format(x) for x in ax.get_xticks()])

    ax.tick_params(axis="y", direction="in")
    ax.tick_params(axis="x", direction="in")

    ax.grid(linestyle=":")

    colorbar = ax.figure.colorbar(ax1)
    colorbar.set_label("Risk Adjusted Return Ratio")

    fig = plt.gcf()
    fig.tight_layout()

    return ax
Example #5
0
def plot_frontier(
    w_frontier,
    mu,
    cov=None,
    returns=None,
    rm="MV",
    rf=0,
    alpha=0.01,
    cmap="viridis",
    w=None,
    label="Portfolio",
    marker="*",
    s=16,
    c="r",
    height=6,
    width=10,
    ax=None,
):
    """
    Creates a plot of the efficient frontier for a risk measure specified by
    the user.
    
    Parameters
    ----------
    w_frontier : DataFrame
        Portfolio weights of some points in the efficient frontier.
    mu : DataFrame of shape (1, n_assets)
        Vector of expected returns, where n_assets is the number of assets.
    cov : DataFrame of shape (n_features, n_features)
        Covariance matrix, where n_features is the number of features.
    returns : DataFrame of shape (n_samples, n_features)
        Features matrix, where n_samples is the number of samples and 
        n_features is the number of features.
    rm : str, optional
        Risk measure used to create the frontier. The default is 'MV'.
    rf : float, optional
        Risk free rate or minimum aceptable return. The default is 0.
    alpha : float, optional
        Significante level of VaR, CVaR and CDaR. The default is 0.01.
    cmap : cmap, optional
        Colorscale, represente the risk adjusted return ratio.
        The default is 'viridis'.
    w : DataFrame, optional
        A portfolio specified by the user. The default is None.
    label : str, optional
        Name of portfolio that appear on plot legend.
        The default is 'Portfolio'.
    marker : str, optional
        Marker of w_. The default is '*'.
    s : float, optional
        Size of marker. The default is 16.
    c : str, optional
        Color of marker. The default is 'r'.
    height : float, optional
        Height of the image in inches. The default is 6.
    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 Axes
        Returns the Axes object with the plot for further tweaking.
        
    Example
    -------
    ::
        
        label = 'Max Risk Adjusted Return Portfolio'
        mu = port.mu
        cov = port.cov
        returns = port.returns
        
        ax = plf.plot_frontier(w_frontier=ws, mu=mu, cov=cov, returns=returns,
                               rm=rm, rf=0, alpha=0.01, cmap='viridis', w=w1,
                               label='Portfolio', marker='*', s=16, c='r',
                               height=6, width=10, ax=None)
        
    .. image:: images/MSV_Frontier.png
    
    """

    if not isinstance(w_frontier, pd.DataFrame):
        raise ValueError("w_frontier must be a DataFrame")

    if not isinstance(mu, pd.DataFrame):
        raise ValueError("mu must be a DataFrame")

    if not isinstance(cov, pd.DataFrame):
        raise ValueError("cov must be a DataFrame")

    if not isinstance(returns, pd.DataFrame):
        raise ValueError("returns must be a DataFrame")

    if returns.shape[1] != w_frontier.shape[0]:
        a1 = str(returns.shape)
        a2 = str(w_frontier.shape)
        raise ValueError("shapes " + a1 + " and " + a2 + " not aligned")

    if w is not None:
        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 column 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_ = np.array(mu, ndmin=2)

    ax.set_ylabel("Expected Return")
    item = rmeasures.index(rm)
    x_label = rm_names[item] + " (" + rm + ")"
    ax.set_xlabel("Expected Risk - " + x_label)

    title = "Efficient Frontier Mean - " + x_label
    ax.set_title(title)

    X1 = []
    Y1 = []
    Z1 = []

    for i in range(w_frontier.shape[1]):
        weights = np.array(w_frontier.iloc[:, i], ndmin=2).T
        risk = rk.Sharpe_Risk(
            weights, cov=cov, returns=returns, rm=rm, rf=rf, alpha=alpha
        )
        ret = mu_ @ weights
        ret = ret.item()
        ratio = (ret - rf) / risk

        X1.append(risk)
        Y1.append(ret)
        Z1.append(ratio)

    ax1 = ax.scatter(X1, Y1, c=Z1, cmap=cmap)

    if w is not None:
        X2 = []
        Y2 = []
        for i in range(w.shape[1]):
            weights = np.array(w.iloc[:, i], ndmin=2).T
            risk = rk.Sharpe_Risk(
                weights, cov=cov, returns=returns, rm=rm, rf=rf, alpha=alpha
            )
            ret = mu_ @ weights
            ret = ret.item()
            ratio = (ret - rf) / risk

            X2.append(risk)
            Y2.append(ret)

        ax.scatter(X2, Y2, marker=marker, s=s ** 2, c=c, label=label)
        ax.legend(loc="upper left")

    xmin = np.min(X1) - np.abs(np.max(X1) - np.min(X1)) * 0.1
    xmax = np.max(X1) + np.abs(np.max(X1) - np.min(X1)) * 0.1
    ymin = np.min(Y1) - np.abs(np.max(Y1) - np.min(Y1)) * 0.1
    ymax = np.max(Y1) + np.abs(np.max(Y1) - np.min(Y1)) * 0.1

    ax.set_ylim(ymin, ymax)
    ax.set_xlim(xmin, xmax)

    ax.set_yticklabels(["{:.4%}".format(x) for x in ax.get_yticks()])
    ax.set_xticklabels(["{:.4%}".format(x) for x in ax.get_xticks()])

    ax.tick_params(axis="y", direction="in")
    ax.tick_params(axis="x", direction="in")

    ax.grid(linestyle=":")

    colorbar = ax.figure.colorbar(ax1)
    colorbar.set_label("Risk Adjusted Return Ratio")

    fig = plt.gcf()
    fig.tight_layout()

    return ax
Example #6
0
def plot_frontier(
    w_frontier,
    mu,
    cov=None,
    returns=None,
    rm="MV",
    rf=0,
    alpha=0.05,
    cmap="viridis",
    w=None,
    label="Portfolio",
    marker="*",
    s=16,
    c="r",
    height=6,
    width=10,
    ax=None,
):
    """
    Creates a plot of the efficient frontier for a risk measure specified by
    the user.

    Parameters
    ----------
    w_frontier : DataFrame
        Portfolio weights of some points in the efficient frontier.
    mu : DataFrame of shape (1, n_assets)
        Vector of expected returns, where n_assets is the number of assets.
    cov : DataFrame of shape (n_features, n_features)
        Covariance matrix, where n_features is the number of features.
    returns : DataFrame of shape (n_samples, n_features)
        Features matrix, where n_samples is the number of samples and
        n_features is the number of features.
    rm : str, optional
        The risk measure used to estimate the frontier.
        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.
        - 'EVaR': Conditional Value at Risk.
        - 'WR': Worst Realization (Minimax)
        - 'MDD': Maximum Drawdown of uncompounded returns (Calmar Ratio).
        - 'ADD': Average Drawdown of uncompounded returns.
        - 'DaR': Drawdown at Risk of uncompounded returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded returns.
        - 'UCI': Ulcer Index of uncompounded returns.

    rf : float, optional
        Risk free rate or minimum acceptable return. The default is 0.
    alpha : float, optional
        Significante level of VaR, CVaR, EVaR, DaR and CDaR.
        The default is 0.05.
    cmap : cmap, optional
        Colorscale, represente the risk adjusted return ratio.
        The default is 'viridis'.
    w : DataFrame of shape (n_assets, 1), optional
        A portfolio specified by the user. The default is None.
    label : str, optional
        Name of portfolio that appear on plot legend.
        The default is 'Portfolio'.
    marker : str, optional
        Marker of w_. The default is '*'.
    s : float, optional
        Size of marker. The default is 16.
    c : str, optional
        Color of marker. The default is 'r'.
    height : float, optional
        Height of the image in inches. The default is 6.
    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 Axes
        Returns the Axes object with the plot for further tweaking.

    Example
    -------
    ::

        label = 'Max Risk Adjusted Return Portfolio'
        mu = port.mu
        cov = port.cov
        returns = port.returns

        ax = plf.plot_frontier(w_frontier=ws, mu=mu, cov=cov, returns=returns,
                               rm=rm, rf=0, alpha=0.05, cmap='viridis', w=w1,
                               label='Portfolio', marker='*', s=16, c='r',
                               height=6, width=10, ax=None)

    .. image:: images/MSV_Frontier.png

    """

    if not isinstance(w_frontier, pd.DataFrame):
        raise ValueError("w_frontier must be a DataFrame")

    if not isinstance(mu, pd.DataFrame):
        raise ValueError("mu must be a DataFrame")

    if not isinstance(cov, pd.DataFrame):
        raise ValueError("cov must be a DataFrame")

    if not isinstance(returns, pd.DataFrame):
        raise ValueError("returns must be a DataFrame")

    if returns.shape[1] != w_frontier.shape[0]:
        a1 = str(returns.shape)
        a2 = str(w_frontier.shape)
        raise ValueError("shapes " + a1 + " and " + a2 + " not aligned")

    if w is not None:
        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 column DataFrame")

        if returns.shape[1] != w.shape[0]:
            a1 = str(returns.shape)
            a2 = str(w.shape)
            raise ValueError("shapes " + a1 + " and " + a2 + " not aligned")


    mu_ = np.array(mu, ndmin=2)

    item = rmeasures.index(rm)
    x_label = rm_names[item] + " (" + rm + ")"
    # title = "Efficient Frontier Mean - " + x_label

    pretty_container_bgcolor = '#f9f9f9'
    fig = go.Figure(
        layout=go.Layout(
            # title=title,
            plot_bgcolor=pretty_container_bgcolor,
            hovermode='x',
            hoverdistance=100,
            spikedistance=1000,
            xaxis=dict(
                title=rm_names[item] + " (" + rm + ")",
                linecolor='#9a9a9a',
                showgrid=False,
                showspikes=True,
                spikethickness=3,
                spikedash='dot',
                spikecolor='#FF0000',
                spikemode='across'
            ),
            yaxis=dict(
                title="Expected Return",
                linecolor='#9a9a9a',
                showgrid=False
            )
        )
    )


    X1 = []
    Y1 = []
    Z1 = []

    for i in range(w_frontier.shape[1]):
        weights = np.array(w_frontier.iloc[:, i], ndmin=2).T
        risk = rk.Sharpe_Risk(
            weights, cov=cov, returns=returns, rm=rm, rf=rf, alpha=alpha
        )
        ret = mu_ @ weights
        ret = ret.item()
        ratio = (ret - rf) / risk

        X1.append(risk)
        Y1.append(ret)
        Z1.append(ratio)

    fig.add_trace(
        go.Scatter(
            x=X1,
            y=Y1
        )
    )

    # ax1 = ax.scatter(X1, Y1, c=Z1, cmap=cmap)

    if w is not None:
        X2 = []
        Y2 = []
        for i in range(w.shape[1]):
            weights = np.array(w.iloc[:, i], ndmin=2).T
            risk = rk.Sharpe_Risk(
                weights, cov=cov, returns=returns, rm=rm, rf=rf, alpha=alpha
            )
            ret = mu_ @ weights
            ret = ret.item()
            ratio = (ret - rf) / risk

            X2.append(risk)
            Y2.append(ret)

        fig.add_trace(
            go.Scatter(
                x=X2,
                y=Y2
            )
        )

        fig.update_layout(
            margin=dict(l=100, r=100, t=100, b=100)
        )

    #
    # xmin = np.min(X1) - np.abs(np.max(X1) - np.min(X1)) * 0.1
    # xmax = np.max(X1) + np.abs(np.max(X1) - np.min(X1)) * 0.1
    # ymin = np.min(Y1) - np.abs(np.max(Y1) - np.min(Y1)) * 0.1
    # ymax = np.max(Y1) + np.abs(np.max(Y1) - np.min(Y1)) * 0.1
    #
    # ax.set_ylim(ymin, ymax)
    # ax.set_xlim(xmin, xmax)
    #
    # ax.set_yticklabels(["{:.4%}".format(x) for x in ax.get_yticks()])
    # ax.set_xticklabels(["{:.4%}".format(x) for x in ax.get_xticks()])
    #
    # ax.tick_params(axis="y", direction="in")
    # ax.tick_params(axis="x", direction="in")
    #
    # ax.grid(linestyle=":")
    #
    # colorbar = ax.figure.colorbar(ax1)
    # colorbar.set_label("Risk Adjusted Return Ratio")
    #
    # fig = plt.gcf()
    # fig.tight_layout()

    return fig