"""Optimization View"""
__docformat__ = "numpy"

# pylint: disable=R0913, R0914, C0302, too-many-branches, too-many-statements, line-too-long
# flake8: noqa: E501

import logging
import math
import warnings
from datetime import date
from typing import Any, Dict, List, Optional

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import riskfolio as rp
from dateutil.relativedelta import relativedelta, FR
from matplotlib.gridspec import GridSpec
from matplotlib.lines import Line2D

from openbb_terminal.config_plot import PLOT_DPI
from openbb_terminal.config_terminal import theme
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import plot_autoscale, print_rich_table
from openbb_terminal.portfolio.portfolio_optimization import (
    optimizer_helper,
    optimizer_model,
)
from openbb_terminal.rich_config import console

warnings.filterwarnings("ignore")

logger = logging.getLogger(__name__)

objectives_choices = {
    "minrisk": "MinRisk",
    "sharpe": "Sharpe",
    "utility": "Utility",
    "maxret": "MaxRet",
    "erc": "ERC",
}

risk_names = {
    "mv": "volatility",
    "mad": "mean absolute deviation",
    "gmd": "gini mean difference",
    "msv": "semi standard deviation",
    "var": "value at risk (VaR)",
    "cvar": "conditional value at risk (CVaR)",
    "tg": "tail gini",
    "evar": "entropic value at risk (EVaR)",
    "rg": "range",
    "cvrg": "CVaR range",
    "tgrg": "tail gini range",
    "wr": "worst realization",
    "flpm": "first lower partial moment",
    "slpm": "second lower partial moment",
    "mdd": "maximum drawdown uncompounded",
    "add": "average drawdown uncompounded",
    "dar": "drawdown at risk (DaR) uncompounded",
    "cdar": "conditional drawdown at risk (CDaR) uncompounded",
    "edar": "entropic drawdown at risk (EDaR) uncompounded",
    "uci": "ulcer index uncompounded",
    "mdd_rel": "maximum drawdown compounded",
    "add_rel": "average drawdown compounded",
    "dar_rel": "drawdown at risk (DaR) compounded",
    "cdar_rel": "conditional drawdown at risk (CDaR) compounded",
    "edar_rel": "entropic drawdown at risk (EDaR) compounded",
    "uci_rel": "ulcer index compounded",
}

risk_choices = {
    "mv": "MV",
    "mad": "MAD",
    "gmd": "GMD",
    "msv": "MSV",
    "var": "VaR",
    "cvar": "CVaR",
    "tg": "TG",
    "evar": "EVaR",
    "rg": "RG",
    "cvrg": "CVRG",
    "tgrg": "TGRG",
    "wr": "WR",
    "flpm": "FLPM",
    "slpm": "SLPM",
    "mdd": "MDD",
    "add": "ADD",
    "dar": "DaR",
    "cdar": "CDaR",
    "edar": "EDaR",
    "uci": "UCI",
    "mdd_rel": "MDD_Rel",
    "add_rel": "ADD_Rel",
    "dar_rel": "DaR_Rel",
    "cdar_rel": "CDaR_Rel",
    "edar_rel": "EDaR_Rel",
    "uci_rel": "UCI_Rel",
}

time_factor = {
    "D": 252.0,
    "W": 52.0,
    "M": 12.0,
}

dict_conversion = {"period": "historic_period", "start": "start_period"}


@log_start_end(log=logger)
def d_period(interval: str = "1y", start_date: str = "", end_date: str = ""):
    """
    Builds a date range string

    Parameters
    ----------
    interval : str
        interval starting today
    start_date: str
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    """
    extra_choices = {
        "ytd": "[Year-to-Date]",
        "max": "[All-time]",
    }

    if start_date == "":
        if interval in extra_choices:
            p = extra_choices[interval]
        else:
            if interval[-1] == "d":
                p = "[" + interval[:-1] + " Days]"
            elif interval[-1] == "w":
                p = "[" + interval[:-1] + " Weeks]"
            elif interval[-1] == "o":
                p = "[" + interval[:-2] + " Months]"
            elif interval[-1] == "y":
                p = "[" + interval[:-1] + " Years]"
        if p[1:3] == "1 ":
            p = p.replace("s", "")
    else:
        if end_date == "":
            end_ = date.today()
            if end_.weekday() >= 5:
                end_ = end_ + relativedelta(weekday=FR(-1))
            end_date = end_.strftime("%Y-%m-%d")
        p = "[From " + start_date + " to " + end_date + "]"

    return p


@log_start_end(log=logger)
def portfolio_performance(
    weights: dict,
    data: pd.DataFrame,
    freq: str = "D",
    risk_measure: str = "MV",
    risk_free_rate: float = 0,
    alpha: float = 0.05,
    a_sim: float = 100,
    beta: float = None,
    b_sim: float = None,
):
    """
    Prints portfolio performance indicators

    Parameters
    ----------
    weights: dict
        Portfolio weights
    data: pd.DataFrame
        Stock returns dataframe
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    risk_measure : str, optional
        The risk measure used. The default is 'MV'. Possible values are:

        - 'MV': Variance.
        - 'MAD': Mean Absolute Deviation.
        - 'MSV': Semi Standard Deviation.
        - 'FLPM': First Lower Partial Moment (Omega Ratio).
        - 'SLPM': Second Lower Partial Moment (Sortino Ratio).
        - 'VaR': Value at Risk.
        - 'CVaR': Conditional Value at Risk.
        - 'TG': Tail Gini.
        - 'EVaR': Entropic Value at Risk.
        - 'WR': Worst Realization (Minimax).
        - 'RG': Range of returns.
        - 'CVRG': CVaR range of returns.
        - 'TGRG': Tail Gini range of returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio).
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'DaR': Drawdown at Risk of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio).
        - 'ADD_Rel': Average Drawdown of compounded cumulative returns.
        - 'DaR_Rel': Drawdown at Risk of compounded cumulative returns.
        - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns.
        - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns.
        - 'UCI_Rel': Ulcer Index of compounded cumulative returns.

    risk_free_rate : float, optional
        risk free rate.
    alpha : float, optional
        Significance level of VaR, CVaR, EDaR, DaR, CDaR, EDaR, Tail Gini of
        losses. The default is 0.05.
    a_sim : float, optional
        Number of CVaRs used to approximate Tail Gini of losses. The default
        is 100.
    beta : float, optional
        Significance level of CVaR and Tail Gini of gains. If None it
        duplicates alpha value. The default is None.
    b_sim : float, optional
        Number of CVaRs used to approximate Tail Gini of gains. If None
        it duplicates a_sim value. The default is None.
    """

    try:
        freq = freq.upper()
        weights = pd.Series(weights).to_frame()
        returns = data @ weights
        mu = returns.mean().item() * time_factor[freq]
        sigma = returns.std().item() * time_factor[freq] ** 0.5
        sharpe = (mu - risk_free_rate) / sigma

        factor_1 = str(int(time_factor[freq])) + ") "
        factor_2 = "√" + factor_1

        print("\nAnnual (by " + factor_1 + f"expected return: {100 * mu:.2f}%")
        print("Annual (by " + factor_2 + f"volatility: {100 * sigma:.2f}%")
        print(f"Sharpe ratio: {sharpe:.4f}")

        if risk_measure != "MV":
            risk = rp.Sharpe_Risk(
                weights,
                cov=data.cov(),
                returns=data,
                rm=risk_measure,
                rf=risk_free_rate,
                alpha=alpha,
                a_sim=a_sim,
                beta=beta,
                b_sim=b_sim,
            )

            drawdowns = [
                "MDD",
                "ADD",
                "DaR",
                "CDaR",
                "EDaR",
                "UCI",
                "MDD_Rel",
                "ADD_Rel",
                "DaR_Rel",
                "CDaR_Rel",
                "EDaR_Rel",
                "UCI_Rel",
            ]

            if risk_measure in drawdowns:
                sharpe_2 = (mu - risk_free_rate) / risk
                print(
                    risk_names[risk_measure.lower()].capitalize()
                    + " : "
                    + f"{100 * risk:.2f}%"
                )
            else:
                risk = risk * time_factor[freq] ** 0.5
                sharpe_2 = (mu - risk_free_rate) / risk
                print(
                    "Annual (by "
                    + factor_2
                    + risk_names[risk_measure.lower()]
                    + " : "
                    + f"{100 * risk:.2f}%"
                )

            print(
                "Return / "
                + risk_names[risk_measure.lower()]
                + f" ratio: {sharpe_2:.4f}"
            )
    except Exception as _:
        console.print(
            "[red]\nFailed to calculate portfolio performance indicators.[/red]"
        )


@log_start_end(log=logger)
def display_weights(weights: dict, market_neutral: bool = False):
    """
    Prints weights in a nice format

    Parameters
    ----------
    weights: dict
        weights to display.  Keys are stocks.  Values are either weights or values
    market_neutral : bool
        Flag indicating shorting allowed (negative weights)
    """

    df = optimizer_helper.dict_to_df(weights)

    if df.empty:
        return

    if math.isclose(df.sum()["value"], 1, rel_tol=0.5):
        df["value"] = (df["value"] * 100).apply(lambda s: f"{s:.2f}") + " %"
        df["value"] = (
            df["value"]
            .astype(str)
            .apply(lambda s: " " * (8 - len(s)) + s if len(s) < 8 else "" + s)
        )
    else:
        df["value"] = (df["value"] * 100).apply(lambda s: f"{s:.0f}") + " $"
        df["value"] = (
            df["value"]
            .astype(str)
            .apply(lambda s: " " * (16 - len(s)) + s if len(s) < 16 else "" + s)
        )

    if market_neutral:
        tot_value = df["value"].abs().mean()
        header = "Value ($)" if tot_value > 1.01 else "Value (%)"
        print_rich_table(df, headers=[header], show_index=True, title="Weights")
    else:
        print_rich_table(df, headers=["Value"], show_index=True, title="Weights")


@log_start_end(log=logger)
def display_weights_sa(weights: dict, weights_sa: dict):
    """
    Prints weights in a nice format

    Parameters
    ----------
    weights: dict
        weights to display.  Keys are stocks.  Values are either weights or values
    weights_sa: dict
        weights of sensitivity analysis to display.  Keys are stocks.
        Values are either weights or values
    """
    if not weights or not weights_sa:
        return
    weight_df = pd.DataFrame.from_dict(
        data=weights, orient="index", columns=["value"], dtype=float
    )
    weight_sa_df = pd.DataFrame.from_dict(
        data=weights_sa, orient="index", columns=["value s.a."], dtype=float
    )
    weight_df = weight_df.join(weight_sa_df, how="inner")
    weight_df["value vs value s.a."] = weight_df["value"] - weight_df["value s.a."]

    weight_df["value"] = (weight_df["value"] * 100).apply(lambda s: f"{s:.2f}") + " %"
    weight_df["value"] = (
        weight_df["value"]
        .astype(str)
        .apply(lambda s: " " * (8 - len(s)) + s if len(s) < 8 else "" + s)
    )
    weight_df["value s.a."] = (weight_df["value s.a."] * 100).apply(
        lambda s: f"{s:.2f}"
    ) + " %"
    weight_df["value s.a."] = (
        weight_df["value s.a."]
        .astype(str)
        .apply(
            lambda s: " " * (len("value s.a.") - len(s)) + s
            if len(s) < len("value s.a.")
            else "" + s
        )
    )
    weight_df["value vs value s.a."] = (weight_df["value vs value s.a."] * 100).apply(
        lambda s: f"{s:.2f}"
    ) + " %"
    weight_df["value vs value s.a."] = (
        weight_df["value vs value s.a."]
        .astype(str)
        .apply(
            lambda s: " " * (len("value vs value s.a.") - len(s)) + s
            if len(s) < len("value vs value s.a.")
            else "" + s
        )
    )

    headers = list(weight_df.columns)
    headers = [s.title() for s in headers]
    print_rich_table(
        weight_df, headers=headers, show_index=True, title="Weights Comparison"
    )


@log_start_end(log=logger)
def display_categories(
    weights: dict, categories: dict, column: str = "ASSET_CLASS", title: str = ""
):
    """
    Prints categories in a nice format

    Parameters
    ----------
    weights: dict
        weights to display.  Keys are stocks.  Values are either weights or values
    categories: dict
        categories to display. Keys are stocks.  Values are either weights or values
    column: str
        column selected to show table
        - ASSET_CLASS
        - SECTOR
        - INDUSTRY
        - COUNTRY
    title: str
        title to display
    """

    if column == "CURRENT_INVESTED_AMOUNT":
        console.print(f"[yellow]'{column}' cannot be displayed as a category.[/yellow]")
        return

    df = optimizer_model.get_categories(weights, categories, column)

    if df.empty:
        return

    df["value"] = (df["value"] * 100).apply(lambda s: f"{s:.2f}") + " %"
    df["value"] = (
        df["value"]
        .astype(str)
        .apply(lambda s: " " * (8 - len(s)) + s if len(s) < 8 else "" + s)
    )
    df["CURRENT_WEIGHTS"] = (df["CURRENT_WEIGHTS"] * 100).apply(
        lambda s: f"{s:.2f}"
    ) + " %"
    df["CURRENT_WEIGHTS"] = (
        df["CURRENT_WEIGHTS"]
        .astype(str)
        .apply(
            lambda s: " " * (len("CURRENT_WEIGHTS") - len(s)) + s
            if len(s) < len("CURRENT_WEIGHTS")
            else "" + s
        )
    )
    df["CURRENT_INVESTED_AMOUNT"] = (
        df["CURRENT_INVESTED_AMOUNT"].apply(lambda s: f"{s:,.0f}") + " $"
    )
    df["CURRENT_INVESTED_AMOUNT"] = (
        df["CURRENT_INVESTED_AMOUNT"]
        .astype(str)
        .apply(
            lambda s: " " * (len("CURRENT_INVESTED_AMOUNT") - len(s)) + s
            if len(s) < len("CURRENT_INVESTED_AMOUNT")
            else "" + s
        )
    )

    df.reset_index(level=1, inplace=True)
    headers = [s.title() for s in list(df.columns)]
    show_index = True
    if column == "CURRENCY":
        show_index = False

    print_rich_table(df, headers=headers, show_index=show_index, title=title)


@log_start_end(log=logger)
def display_categories_sa(
    weights: dict, weights_sa: dict, categories: dict, column: str, title: str = ""
):
    """
    Prints categories in a nice format

    Parameters
    ----------
    weights: dict
        weights to display.  Keys are stocks.  Values are either weights or values
    weights_sa: dict
        weights of sensitivity analysis to display.  Keys are stocks.  Values are either weights or values
    categories: dict
        categories to display. Keys are stocks.  Values are either weights or values
    column: int.
        column selected to show table
        - ASSET_CLASS
        - SECTOR
        - INDUSTRY
        - COUNTRY
    """
    if not weights or not weights_sa:
        return
    weight_df = pd.DataFrame.from_dict(
        data=weights, orient="index", columns=["value"], dtype=float
    )
    weight_sa_df = pd.DataFrame.from_dict(
        data=weights_sa, orient="index", columns=["value s.a."], dtype=float
    )
    categories_df = pd.DataFrame.from_dict(data=categories, dtype=float)

    col = list(categories_df.columns).index(column)
    categories_df = weight_df.join(categories_df.iloc[:, [col, 4, 5]], how="inner")
    categories_df = categories_df.join(weight_sa_df, how="inner")
    categories_df.set_index(column, inplace=True)
    categories_df.groupby(level=0).sum()

    table_df = pd.pivot_table(
        categories_df,
        values=["value", "value s.a.", "CURRENT_INVESTED_AMOUNT"],
        index=["CURRENCY", column],
        aggfunc=np.sum,
    )
    table_df["CURRENT_WEIGHTS"] = (
        table_df["CURRENT_INVESTED_AMOUNT"]
        .groupby(level=0)
        .transform(lambda x: x / sum(x))
    )
    table_df["value"] = (
        table_df["value"].groupby(level=0).transform(lambda x: x / sum(x))
    )
    table_df["value s.a."] = (
        table_df["value s.a."].groupby(level=0).transform(lambda x: x / sum(x))
    )
    table_df = pd.concat(
        [
            d.append(d.sum().rename((k, "TOTAL " + k)))
            for k, d in table_df.groupby(level=0)
        ]
    )
    table_df["value vs value s.a."] = table_df["value"] - table_df["value s.a."]

    table_df = table_df.iloc[:, [0, 3, 1, 2, 4]]

    table_df["value"] = (table_df["value"] * 100).apply(lambda s: f"{s:.2f}") + " %"
    table_df["value"] = (
        table_df["value"]
        .astype(str)
        .apply(lambda s: " " * (8 - len(s)) + s if len(s) < 8 else "" + s)
    )
    table_df["value s.a."] = (table_df["value s.a."] * 100).apply(
        lambda s: f"{s:.2f}"
    ) + " %"
    table_df["value s.a."] = (
        table_df["value s.a."]
        .astype(str)
        .apply(
            lambda s: " " * (len("value s.a.") - len(s)) + s
            if len(s) < len("value s.a.")
            else "" + s
        )
    )
    table_df["value vs value s.a."] = (table_df["value vs value s.a."] * 100).apply(
        lambda s: f"{s:.2f}"
    ) + " %"
    table_df["value vs value s.a."] = (
        table_df["value vs value s.a."]
        .astype(str)
        .apply(
            lambda s: " " * (len("value vs value s.a.") - len(s)) + s
            if len(s) < len("value vs value s.a.")
            else "" + s
        )
    )
    table_df["CURRENT_WEIGHTS"] = (table_df["CURRENT_WEIGHTS"] * 100).apply(
        lambda s: f"{s:.2f}"
    ) + " %"
    table_df["CURRENT_WEIGHTS"] = (
        table_df["CURRENT_WEIGHTS"]
        .astype(str)
        .apply(
            lambda s: " " * (len("CURRENT_WEIGHTS") - len(s)) + s
            if len(s) < len("CURRENT_WEIGHTS")
            else "" + s
        )
    )
    table_df["CURRENT_INVESTED_AMOUNT"] = (
        table_df["CURRENT_INVESTED_AMOUNT"].apply(lambda s: f"{s:,.0f}") + " $"
    )
    table_df["CURRENT_INVESTED_AMOUNT"] = (
        table_df["CURRENT_INVESTED_AMOUNT"]
        .astype(str)
        .apply(
            lambda s: " " * (len("CURRENT_INVESTED_AMOUNT") - len(s)) + s
            if len(s) < len("CURRENT_INVESTED_AMOUNT")
            else "" + s
        )
    )

    table_df.reset_index(inplace=True)
    table_df.set_index("CURRENCY", inplace=True)

    headers = list(table_df.columns)
    headers = [s.title() for s in headers]
    print_rich_table(table_df, headers=headers, show_index=True, title=title)


@log_start_end(log=logger)
def display_equal_weight(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    risk_measure="mv",
    risk_free_rate: float = 0,
    alpha: float = 0.05,
    value: float = 1,
    table: bool = False,
) -> Dict:
    """
    Equally weighted portfolio, where weight = 1/# of symbols

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False.
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    risk_measure: str, optional
        The risk measure used to optimize the portfolio.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'WR': Worst Realization.
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR.
    value : float, optional
        Amount to allocate to portfolio, by default 1.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Equally Weighted Portfolio\n"

    weights, stock_returns = optimizer_model.get_equal_weights(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        value=value,
    )

    if not weights:
        console.print("There is no solution with these parameters.")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_sim=beta_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_property_weighting(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    s_property: str = "marketCap",
    risk_measure: str = "mv",
    risk_free_rate: float = 0,
    alpha: float = 0.05,
    value: float = 1,
    table: bool = False,
) -> Dict[str, float]:
    """
    Builds a portfolio weighted by selected property

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    s_property : str
        Property to get weighted portfolio of
    risk_measure: str, optional
        The risk measure used to compute indicators.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'WR': Worst Realization.
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR.
    value : float, optional
        Amount to allocate to portfolio, by default 1.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Weighted Portfolio based on " + s_property + "\n"

    weights, stock_returns = optimizer_model.get_property_weights(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        s_property=s_property,
        value=value,
    )

    if not weights:
        console.print("There is no solution with these parameters.")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_sim=beta_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_mean_risk(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    risk_measure: str = "mv",
    objective: str = "sharpe",
    risk_free_rate: float = 0,
    risk_aversion: float = 1,
    alpha: float = 0.05,
    target_return: float = -1,
    target_risk: float = -1,
    mean: str = "hist",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    value_short: float = 0.0,
    table: bool = False,
) -> Dict:
    """
    Builds a mean risk optimal portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    risk_measure: str, optional
        The risk measure used to optimize the portfolio.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'WR': Worst Realization.
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns.

    objective: str
        Objective function of the optimization model.
        The default is 'Sharpe'. Possible values are:

        - 'MinRisk': Minimize the selected risk measure.
        - 'Utility': Maximize the risk averse utility function.
        - 'Sharpe': Maximize the risk adjusted return ratio based on the selected risk measure.
        - 'MaxRet': Maximize the expected return of the portfolio.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR
    target_return: float, optional
        Constraint on minimum level of portfolio's return.
    target_risk: float, optional
        Constraint on maximum level of portfolio's risk.
    mean: str, optional
        The method used to estimate the expected returns.
        The default value is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    if objective == "sharpe":
        s_title = f"{p} Maximal return/risk ratio portfolio using "
    elif objective == "minrisk":
        s_title = f"{p} Minimum risk portfolio using "
    elif objective == "maxret":
        s_title = f"{p} Maximal return portfolio using "
    elif objective == "utility":
        s_title = f"{p} Maximal risk averse utility function portfolio using "
    s_title += risk_names[risk_measure] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_mean_risk_portfolio(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        risk_measure=risk_choices[risk_measure],
        objective=objectives_choices[objective],
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        target_return=target_return,
        target_risk=target_risk,
        mean=mean,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
        value_short=value_short,
    )

    if not weights:
        console.print("There is no solution with these parameters.")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_sim=beta_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_max_sharpe(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    risk_measure: str = "MV",
    risk_free_rate: float = 0,
    risk_aversion: float = 1,
    alpha: float = 0.05,
    target_return: float = -1,
    target_risk: float = -1,
    mean: str = "hist",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    value_short: float = 0.0,
    table: bool = False,
) -> Dict:
    """
    Builds a maximal return/risk ratio portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    risk_measure: str, optional
        The risk measure used to optimize the portfolio.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'WR': Worst Realization.
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR
    target_return: float, optional
        Constraint on minimum level of portfolio's return.
    target_risk: float, optional
        Constraint on maximum level of portfolio's risk.
    mean: str, optional
        The method used to estimate the expected returns.
        The default value is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """

    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Maximal return/risk ratio portfolio using "
    s_title += risk_names[risk_measure.lower()] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_max_sharpe(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        risk_measure=risk_measure,
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        target_return=target_return,
        target_risk=target_risk,
        mean=mean,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
        value_short=value_short,
    )

    if not weights:
        console.print("There is no solution with these parameters.")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_sim=beta_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_min_risk(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    risk_measure: str = "MV",
    risk_free_rate: float = 0,
    risk_aversion: float = 1,
    alpha: float = 0.05,
    target_return: float = -1,
    target_risk: float = -1,
    mean: str = "hist",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    value_short: float = 0.0,
    table: bool = False,
) -> Dict:
    """
    Builds a minimum risk portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    risk_measure: str, optional
        The risk measure used to optimize the portfolio.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'WR': Worst Realization.
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR
    target_return: float, optional
        Constraint on minimum level of portfolio's return.
    target_risk: float, optional
        Constraint on maximum level of portfolio's risk.
    mean: str, optional
        The method used to estimate the expected returns.
        The default value is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Minimum risk portfolio using "
    s_title += risk_names[risk_measure.lower()] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_min_risk(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        risk_measure=risk_measure,
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        target_return=target_return,
        target_risk=target_risk,
        mean=mean,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
        value_short=value_short,
    )

    if not weights:
        console.print("There is no solution with these parameters.")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_sim=beta_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_max_util(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    risk_measure: str = "MV",
    risk_free_rate: float = 0,
    risk_aversion: float = 1,
    alpha: float = 0.05,
    target_return: float = -1,
    target_risk: float = -1,
    mean: str = "hist",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    value_short: float = 0.0,
    table: bool = False,
) -> Dict:
    """
    Builds a maximal risk averse utility portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    risk_measure: str, optional
        The risk measure used to optimize the portfolio.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'WR': Worst Realization.
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR
    target_return: float, optional
        Constraint on minimum level of portfolio's return.
    target_risk: float, optional
        Constraint on maximum level of portfolio's risk.
    mean: str, optional
        The method used to estimate the expected returns.
        The default value is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Maximal risk averse utility function portfolio using "
    s_title += risk_names[risk_measure.lower()] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_max_util(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        risk_measure=risk_measure,
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        target_return=target_return,
        target_risk=target_risk,
        mean=mean,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
        value_short=value_short,
    )

    if not weights:
        console.print("There is no solution with these parameters.")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_sim=beta_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_max_ret(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    risk_measure: str = "MV",
    risk_free_rate: float = 0,
    risk_aversion: float = 1,
    alpha: float = 0.05,
    target_return: float = -1,
    target_risk: float = -1,
    mean: str = "hist",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    value_short: float = 0.0,
    table: bool = False,
) -> Dict:
    """
    Builds a maximal return portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    risk_measure: str, optional
        The risk measure used to optimize the portfolio.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'WR': Worst Realization.
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR
    target_return: float, optional
        Constraint on minimum level of portfolio's return.
    target_risk: float, optional
        Constraint on maximum level of portfolio's risk.
    mean: str, optional
        The method used to estimate the expected returns.
        The default value is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Maximal risk averse utility function portfolio using "
    s_title += risk_names[risk_measure.lower()] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_max_ret(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        risk_measure=risk_measure,
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        target_return=target_return,
        target_risk=target_risk,
        mean=mean,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
        value_short=value_short,
    )

    if not weights:
        console.print("There is no solution with these parameters.")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_sim=beta_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_max_div(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    value_short: float = 0.0,
    table: bool = False,
) -> Dict:
    """
    Builds a maximal diversification portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Maximal diversification portfolio\n"

    weights, stock_returns = optimizer_model.get_max_diversification_portfolio(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
        value_short=value_short,
    )

    if not weights:
        console.print("There is no solution with these parameters.")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure="MV",
            risk_free_rate=0,
            # alpha=0.05,
            # a_sim=100,
            # beta=None,
            # b_sim=None,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_max_decorr(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    value_short: float = 0.0,
    table: bool = False,
) -> Dict:
    """
    Builds a maximal decorrelation portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Maximal decorrelation portfolio\n"

    weights, stock_returns = optimizer_model.get_max_decorrelation_portfolio(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
        value_short=value_short,
    )

    if not weights:
        console.print("There is no solution with this parameters")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure="MV",
            risk_free_rate=0,
            # alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_simb_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_black_litterman(
    symbols: List[str],
    p_views: List = None,
    q_views: List = None,
    benchmark: Dict = None,
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    objective: str = "Sharpe",
    risk_free_rate: float = 0,
    risk_aversion: float = 1,
    delta: float = None,
    equilibrium: bool = True,
    optimize: bool = True,
    value: float = 1.0,
    value_short: float = 0,
    table: bool = False,
) -> Dict:
    """
    Builds a black litterman portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    p_views: List
        Matrix P of views that shows relationships among assets and returns.
        Default value to None.
    q_views: List
        Matrix Q of expected returns of views. Default value is None.
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    benchmark : Dict
        Dict of portfolio weights
    objective: str
        Objective function of the optimization model.
        The default is 'Sharpe'. Possible values are:

        - 'MinRisk': Minimize the selected risk measure.
        - 'Utility': Maximize the risk averse utility function.
        - 'Sharpe': Maximize the risk adjusted return ratio based on the selected risk measure.
        - 'MaxRet': Maximize the expected return of the portfolio.

    risk_free_rate: float, optional
        Risk free rate, must be in annual frequency. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    delta: float, optional
        Risk aversion factor of Black Litterman model. Default value is None.
    equilibrium: bool, optional
        If True excess returns are based on equilibrium market portfolio, if False
        excess returns are calculated as historical returns minus risk free rate.
        Default value is True.
    optimize: bool, optional
        If True Black Litterman estimates are used as inputs of mean variance model,
        if False returns equilibrium weights from Black Litterman model
        Default value is True.
    value : float, optional
        Amount of money to allocate. The default is 1.
    value_short : float, optional
        Amount to allocate to portfolio in short positions. The default is 0.
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Black Litterman portfolio\n"
    weights, stock_returns = optimizer_model.get_black_litterman_portfolio(
        symbols=symbols,
        benchmark=benchmark,
        p_views=p_views,
        q_views=q_views,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        objective=objectives_choices[objective],
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        delta=delta,
        equilibrium=equilibrium,
        optimize=optimize,
        value=value,
        value_short=value_short,
    )

    if not weights:
        console.print("There is no solution with this parameters")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure="MV",
            risk_free_rate=0,
            # alpha=alpha,
            # a_sim=a_sim,
            # beta=beta,
            # b_simb_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_ef(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    risk_measure: str = "MV",
    risk_free_rate: float = 0,
    alpha: float = 0.05,
    value: float = 1.0,
    value_short: float = 0.0,
    n_portfolios: int = 100,
    seed: int = 123,
    tangency: bool = False,
    plot_tickers: bool = True,
    external_axes: Optional[List[plt.Axes]] = None,
):
    """
    Display efficient frontier

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    risk_measure: str, optional
        The risk measure used to optimize the portfolio.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'WR': Worst Realization.
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR
        The default is 0.05.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    n_portfolios: int, optional
        "Number of portfolios to simulate. The default value is 100.
    seed: int, optional
        Seed used to generate random portfolios. The default value is 123.
    tangency: bool, optional
        Adds the optimal line with the risk-free asset.
    external_axes: Optional[List[plt.Axes]]
        Optional axes to plot data on
    plot_tickers: bool
        Whether to plot the tickers for the assets
    """

    frontier, mu, cov, stock_returns, weights, X1, Y1, port = optimizer_model.get_ef(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        risk_measure=risk_measure,
        risk_free_rate=risk_free_rate,
        alpha=alpha,
        value=value,
        value_short=value_short,
        n_portfolios=n_portfolios,
        seed=seed,
    )

    try:
        risk_free_rate = risk_free_rate / time_factor[freq.upper()]

        if external_axes is None:
            _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
        else:
            ax = external_axes[0]

        ax = rp.plot_frontier(
            w_frontier=frontier,
            mu=mu,
            cov=cov,
            returns=stock_returns,
            rm=risk_choices[risk_measure.lower()],
            rf=risk_free_rate,
            alpha=alpha,
            cmap="RdYlBu",
            w=weights,
            label="",
            marker="*",
            s=16,
            c="r",
            t_factor=time_factor[freq.upper()],
            ax=ax,
        )

        # Add risk free line
        if tangency:
            ret_sharpe = (mu @ weights).to_numpy().item() * time_factor[freq.upper()]
            risk_sharpe = rp.Sharpe_Risk(
                weights,
                cov=cov,
                returns=stock_returns,
                rm=risk_choices[risk_measure.lower()],
                rf=risk_free_rate,
                alpha=alpha,
                # a_sim=a_sim,
                # beta=beta,
                # b_sim=b_sim,
            )
            if risk_choices[risk_measure.lower()] not in [
                "ADD",
                "MDD",
                "CDaR",
                "EDaR",
                "UCI",
            ]:
                risk_sharpe = risk_sharpe * time_factor[freq.upper()] ** 0.5

            y = ret_sharpe * 1.5
            b = risk_free_rate * time_factor[freq.upper()]
            m = (ret_sharpe - b) / risk_sharpe
            x2 = (y - b) / m
            x = [0, x2]
            y = [b, y]
            line = Line2D(x, y, label="Capital Allocation Line")
            ax.set_xlim(xmin=min(X1) * 0.8)
            ax.add_line(line)

        ax.plot(X1, Y1, color="b")

        plot_tickers = True
        if plot_tickers:
            ticker_plot = pd.DataFrame(columns=["ticker", "var"])
            for ticker in port.cov.columns:
                weight_df = pd.DataFrame({"weights": 1}, index=[ticker])
                risk = rp.Sharpe_Risk(
                    weight_df,
                    cov=port.cov[ticker][ticker],
                    returns=stock_returns.loc[:, [ticker]],
                    rm=risk_choices[risk_measure.lower()],
                    rf=risk_free_rate,
                    alpha=alpha,
                )

                if risk_choices[risk_measure.lower()] not in [
                    "MDD",
                    "ADD",
                    "CDaR",
                    "EDaR",
                    "UCI",
                ]:
                    risk = risk * time_factor[freq.upper()] ** 0.5

                ticker_plot = ticker_plot.append(
                    {"ticker": ticker, "var": risk}, ignore_index=True
                )
            ticker_plot = ticker_plot.set_index("ticker")
            ticker_plot = ticker_plot.merge(
                port.mu.T * time_factor[freq.upper()], right_index=True, left_index=True
            )
            ticker_plot = ticker_plot.rename(columns={0: "ret"})
            ax.scatter(ticker_plot["var"], ticker_plot["ret"])
            for row in ticker_plot.iterrows():
                ax.annotate(row[0], (row[1]["var"], row[1]["ret"]))
        ax.set_title(f"Efficient Frontier simulating {n_portfolios} portfolios")
        ax.legend(loc="best", scatterpoints=1)
        theme.style_primary_axis(ax)
        l, b, w, h = ax.get_position().bounds
        ax.set_position([l, b, w * 0.9, h])
        ax1 = ax.get_figure().axes
        ll, bb, ww, hh = ax1[-1].get_position().bounds
        ax1[-1].set_position([ll * 1.02, bb, ww, hh])
        if external_axes is None:
            theme.visualize_output(force_tight_layout=False)
    except Exception as _:
        console.print("[red]Error plotting efficient frontier.[/red]")


@log_start_end(log=logger)
def display_risk_parity(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    risk_measure: str = "mv",
    risk_cont: List[str] = None,
    risk_free_rate: float = 0,
    alpha: float = 0.05,
    target_return: float = -1,
    mean: str = "hist",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    table: bool = False,
) -> Dict:
    """
    Builds a risk parity portfolio using the risk budgeting approach

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str
        interval to look at returns from
    start_date: str
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.
        - X (integer days) for returns calculated every X days.

    maxnan: float
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float
        Value used to replace outliers that are higher to threshold.
    method: str
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    risk_measure: str
        The risk measure used to optimize the portfolio.
        The default is 'MV'. Possible 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': Entropic Value at Risk.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.

    risk_cont: List[str], optional
        The vector of risk contribution per asset. If empty, the default is
        1/n (number of assets).
    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns. Used for
        'FLPM' and 'SLPM' and Sharpe objective function. The default is 0.
    alpha: float, optional
        Significance level of CVaR, EVaR, CDaR and EDaR
    target_return: float, optional
        Constraint on minimum level of portfolio's return.
    mean: str, optional
        The method used to estimate the expected returns.
        The default value is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio, by default 1.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Risk parity portfolio based on risk budgeting approach\n"
    s_title += "using " + risk_names[risk_measure] + " as risk measure\n"
    weights, stock_returns = optimizer_model.get_risk_parity_portfolio(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        risk_measure=risk_choices[risk_measure],
        risk_cont=risk_cont,
        risk_free_rate=risk_free_rate,
        alpha=alpha,
        target_return=target_return,
        mean=mean,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
    )

    if not weights:
        console.print("There is no solution with this parameters")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_rel_risk_parity(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    version: str = "A",
    risk_cont: List[str] = None,
    penal_factor: float = 1,
    target_return: float = -1,
    mean: str = "hist",
    covariance: str = "hist",
    d_ewma: float = 0.94,
    value: float = 1.0,
    table: bool = False,
) -> Dict:
    """
    Builds a relaxed risk parity portfolio using the least squares approach

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str, optional
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.
        - X (integer days) for returns calculated every X days.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str, optional
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    version : str, optional
        Relaxed risk parity model version. The default is 'A'.
        Possible values are:

        - 'A': without regularization and penalization constraints.
        - 'B': with regularization constraint but without penalization constraint.
        - 'C': with regularization and penalization constraints.

    risk_cont: List[str], optional
        The vector of risk contribution per asset. If empty, the default is
        1/n (number of assets).
    penal_factor: float, optional
        The penalization factor of penalization constraints. Only used with
        version 'C'. The default is 1.
    target_return: float, optional
        Constraint on minimum level of portfolio's return.
    mean: str, optional
        The method used to estimate the expected returns.
        The default value is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `a-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `a-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `a-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `a-MLforAM`.

    d_ewma: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio, by default 1.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)
    s_title = f"{p} Relaxed risk parity portfolio based on least squares approach\n"
    weights, stock_returns = optimizer_model.get_rel_risk_parity_portfolio(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        version=version.upper(),
        risk_cont=risk_cont,
        penal_factor=penal_factor,
        target_return=target_return,
        mean=mean,
        covariance=covariance,
        d_ewma=d_ewma,
        value=value,
    )

    if not weights:
        console.print("There is no solution with this parameters")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices["mv"],
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_hcp(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    model: str = "HRP",
    codependence: str = "pearson",
    covariance: str = "hist",
    objective: str = "minrisk",
    risk_measure: str = "mv",
    risk_free_rate: float = 0.0,
    risk_aversion: float = 1.0,
    alpha: float = 0.05,
    a_sim: int = 100,
    beta: float = None,
    b_sim: int = None,
    linkage: str = "ward",
    k: int = None,
    max_k: int = 10,
    bins_info: str = "KN",
    alpha_tail: float = 0.05,
    leaf_order: bool = True,
    d_ewma: float = 0.94,
    value: float = 1.0,
    table: bool = False,
) -> Dict:
    """
    Builds a hierarchical clustering portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str, optional
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    model: str, optional
        The hierarchical cluster portfolio model used for optimize the
        portfolio. The default is 'HRP'. Possible values are:

        - 'HRP': Hierarchical Risk Parity.
        - 'HERC': Hierarchical Equal Risk Contribution.
        - 'NCO': Nested Clustered Optimization.

    codependence: str, optional
        The codependence or similarity matrix used to build the distance
        metric and clusters. The default is 'pearson'. Possible values are:

        - 'pearson': pearson correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{0.5(1-\\rho^{pearson}_{i,j})}.
        - 'spearman': spearman correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{0.5(1-\\rho^{spearman}_{i,j})}.
        - 'abs_pearson': absolute value pearson correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-|\\rho^{pearson}_{i,j}|)}.
        - 'abs_spearman': absolute value spearman correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-|\\rho^{spearman}_{i,j}|)}.
        - 'distance': distance correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-\\rho^{distance}_{i,j})}.
        - 'mutual_info': mutual information matrix. Distance used is variation information matrix.
        - 'tail': lower tail dependence index matrix. Dissimilarity formula:
            .. math:: D_{i,j} = -\\log{\\lambda_{i,j}}.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `c-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `c-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `c-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `c-MLforAM`.

    objective: str, optional
        Objective function used by the NCO model.
        The default is 'MinRisk'. Possible values are:

        - 'MinRisk': Minimize the selected risk measure.
        - 'Utility': Maximize the risk averse utility function.
        - 'Sharpe': Maximize the risk adjusted return ratio based on the selected risk measure.
        - 'ERC': Equally risk contribution portfolio of the selected risk measure.

    risk_measure: str, optional
        The risk measure used to optimize the portfolio. If model is 'NCO',
        the risk measures available depends on the objective function.
        The default is 'MV'. Possible values are:

        - 'MV': Variance.
        - 'MAD': Mean Absolute Deviation.
        - 'MSV': Semi Standard Deviation.
        - 'FLPM': First Lower Partial Moment (Omega Ratio).
        - 'SLPM': Second Lower Partial Moment (Sortino Ratio).
        - 'VaR': Value at Risk.
        - 'CVaR': Conditional Value at Risk.
        - 'TG': Tail Gini.
        - 'EVaR': Entropic Value at Risk.
        - 'WR': Worst Realization (Minimax).
        - 'RG': Range of returns.
        - 'CVRG': CVaR range of returns.
        - 'TGRG': Tail Gini range of returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio).
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'DaR': Drawdown at Risk of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio).
        - 'ADD_Rel': Average Drawdown of compounded cumulative returns.
        - 'DaR_Rel': Drawdown at Risk of compounded cumulative returns.
        - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns.
        - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns.
        - 'UCI_Rel': Ulcer Index of compounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns.
        Used for 'FLPM' and 'SLPM'. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of VaR, CVaR, EDaR, DaR, CDaR, EDaR, Tail Gini of losses.
        The default is 0.05.
    a_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of losses. The default is 100.
    beta: float, optional
        Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value.
        The default is None.
    b_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value.
        The default is None.
    linkage: str, optional
        Linkage method of hierarchical clustering. For more information see `linkage <https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html>`__.
        The default is 'single'. Possible values are:

        - 'single'.
        - 'complete'.
        - 'average'.
        - 'weighted'.
        - 'centroid'.
        - 'median'.
        - 'ward'.
        - 'dbht': Direct Bubble Hierarchical Tree.

    k: int, optional
        Number of clusters. This value is took instead of the optimal number
        of clusters calculated with the two difference gap statistic.
        The default is None.
    max_k: int, optional
        Max number of clusters used by the two difference gap statistic
        to find the optimal number of clusters. The default is 10.
    bins_info: str, optional
        Number of bins used to calculate variation of information. The default
        value is 'KN'. Possible values are:

        - 'KN': Knuth's choice method. For more information see `knuth_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.knuth_bin_width.html>`__.
        - 'FD': Freedman–Diaconis' choice method. For more information see `freedman_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.freedman_bin_width.html>`__.
        - 'SC': Scotts' choice method. For more information see `scott_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.scott_bin_width.html>`__.
        - 'HGR': Hacine-Gharbi and Ravier' choice method.

    alpha_tail: float, optional
        Significance level for lower tail dependence index. The default is 0.05.
    leaf_order: bool, optional
        Indicates if the cluster are ordered so that the distance between
        successive leaves is minimal. The default is True.
    d: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio, by default 1.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)

    if model == "HRP":
        s_title = f"{p} Hierarchical risk parity portfolio"
        s_title += " using " + codependence + " codependence,\n" + linkage
    elif model == "HERC":
        s_title = f"{p} Hierarchical equal risk contribution portfolio"
        s_title += " using " + codependence + "\ncodependence," + linkage
    elif model == "NCO":
        s_title = f"{p} Nested clustered optimization"
        s_title += " using " + codependence + " codependence,\n" + linkage
    s_title += " linkage and " + risk_names[risk_measure] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_hcp_portfolio(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        model=model,
        codependence=codependence,
        covariance=covariance,
        objective=objectives_choices[objective],
        risk_measure=risk_choices[risk_measure],
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        a_sim=a_sim,
        beta=beta,
        b_sim=b_sim,
        linkage=linkage,
        k=k,
        max_k=max_k,
        bins_info=bins_info,
        alpha_tail=alpha_tail,
        leaf_order=leaf_order,
        d_ewma=d_ewma,
        value=value,
    )

    if not weights:
        console.print("There is no solution with this parameters")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            a_sim=a_sim,
            beta=beta,
            b_sim=b_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_hrp(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    codependence: str = "pearson",
    covariance: str = "hist",
    risk_measure: str = "mv",
    risk_free_rate: float = 0.0,
    risk_aversion: float = 1.0,
    alpha: float = 0.05,
    a_sim: int = 100,
    beta: float = None,
    b_sim: int = None,
    linkage: str = "single",
    k: int = 0,
    max_k: int = 10,
    bins_info: str = "KN",
    alpha_tail: float = 0.05,
    leaf_order: bool = True,
    d_ewma: float = 0.94,
    value: float = 1.0,
    table: bool = False,
) -> Dict:
    """
    Builds a hierarchical risk parity portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str, optional
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    codependence: str, optional
        The codependence or similarity matrix used to build the distance
        metric and clusters. The default is 'pearson'. Possible values are:

        - 'pearson': pearson correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{0.5(1-\\rho^{pearson}_{i,j})}.
        - 'spearman': spearman correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{0.5(1-\\rho^{spearman}_{i,j})}.
        - 'abs_pearson': absolute value pearson correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-|\\rho^{pearson}_{i,j}|)}.
        - 'abs_spearman': absolute value spearman correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-|\\rho^{spearman}_{i,j}|)}.
        - 'distance': distance correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-\\rho^{distance}_{i,j})}.
        - 'mutual_info': mutual information matrix. Distance used is variation information matrix.
        - 'tail': lower tail dependence index matrix. Dissimilarity formula:
            .. math:: D_{i,j} = -\\log{\\lambda_{i,j}}.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `c-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `c-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `c-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `c-MLforAM`.

    risk_measure: str, optional
        The risk measure used to optimize the portfolio. If model is 'NCO',
        the risk measures available depends on the objective function.
        The default is 'MV'. Possible values are:

        - 'MV': Variance.
        - 'MAD': Mean Absolute Deviation.
        - 'MSV': Semi Standard Deviation.
        - 'FLPM': First Lower Partial Moment (Omega Ratio).
        - 'SLPM': Second Lower Partial Moment (Sortino Ratio).
        - 'VaR': Value at Risk.
        - 'CVaR': Conditional Value at Risk.
        - 'TG': Tail Gini.
        - 'EVaR': Entropic Value at Risk.
        - 'WR': Worst Realization (Minimax).
        - 'RG': Range of returns.
        - 'CVRG': CVaR range of returns.
        - 'TGRG': Tail Gini range of returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio).
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'DaR': Drawdown at Risk of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio).
        - 'ADD_Rel': Average Drawdown of compounded cumulative returns.
        - 'DaR_Rel': Drawdown at Risk of compounded cumulative returns.
        - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns.
        - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns.
        - 'UCI_Rel': Ulcer Index of compounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns.
        Used for 'FLPM' and 'SLPM'. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of VaR, CVaR, EDaR, DaR, CDaR, EDaR, Tail Gini of losses.
        The default is 0.05.
    a_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of losses. The default is 100.
    beta: float, optional
        Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value.
        The default is None.
    b_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value.
        The default is None.
    linkage: str, optional
        Linkage method of hierarchical clustering. For more information see `linkage <https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html>`__.
        The default is 'single'. Possible values are:

        - 'single'.
        - 'complete'.
        - 'average'.
        - 'weighted'.
        - 'centroid'.
        - 'median'.
        - 'ward'.
        - 'dbht': Direct Bubble Hierarchical Tree.

    k: int, optional
        Number of clusters. This value is took instead of the optimal number
        of clusters calculated with the two difference gap statistic.
        The default is None.
    max_k: int, optional
        Max number of clusters used by the two difference gap statistic
        to find the optimal number of clusters. The default is 10.
    bins_info: str, optional
        Number of bins used to calculate variation of information. The default
        value is 'KN'. Possible values are:

        - 'KN': Knuth's choice method. For more information see `knuth_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.knuth_bin_width.html>`__.
        - 'FD': Freedman–Diaconis' choice method. For more information see `freedman_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.freedman_bin_width.html>`__.
        - 'SC': Scotts' choice method. For more information see `scott_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.scott_bin_width.html>`__.
        - 'HGR': Hacine-Gharbi and Ravier' choice method.

    alpha_tail: float, optional
        Significance level for lower tail dependence index. The default is 0.05.
    leaf_order: bool, optional
        Indicates if the cluster are ordered so that the distance between
        successive leaves is minimal. The default is True.
    d: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)

    s_title = f"{p} Hierarchical risk parity portfolio"
    s_title += " using " + codependence + " codependence,\n" + linkage
    s_title += " linkage and " + risk_names[risk_measure] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_hrp(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        codependence=codependence,
        covariance=covariance,
        risk_measure=risk_choices[risk_measure],
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        a_sim=a_sim,
        beta=beta,
        b_sim=b_sim,
        linkage=linkage,
        k=k,
        max_k=max_k,
        bins_info=bins_info,
        alpha_tail=alpha_tail,
        leaf_order=leaf_order,
        d_ewma=d_ewma,
        value=value,
    )

    if not weights:
        console.print("There is no solution with this parameters")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            a_sim=a_sim,
            beta=beta,
            b_sim=b_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_herc(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    codependence: str = "pearson",
    covariance: str = "hist",
    risk_measure: str = "mv",
    risk_free_rate: float = 0.0,
    risk_aversion: float = 1.0,
    alpha: float = 0.05,
    a_sim: int = 100,
    beta: float = None,
    b_sim: int = None,
    linkage: str = "ward",
    k: int = 0,
    max_k: int = 10,
    bins_info: str = "KN",
    alpha_tail: float = 0.05,
    leaf_order: bool = True,
    d_ewma: float = 0.94,
    value: float = 1.0,
    table: bool = False,
) -> Dict:
    """
    Builds a hierarchical equal risk contribution portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str, optional
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    model: str, optional
        The hierarchical cluster portfolio model used for optimize the
        portfolio. The default is 'HRP'. Possible values are:

        - 'HRP': Hierarchical Risk Parity.
        - 'HERC': Hierarchical Equal Risk Contribution.
        - 'NCO': Nested Clustered Optimization.

    codependence: str, optional
        The codependence or similarity matrix used to build the distance
        metric and clusters. The default is 'pearson'. Possible values are:

        - 'pearson': pearson correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{0.5(1-\\rho^{pearson}_{i,j})}.
        - 'spearman': spearman correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{0.5(1-\\rho^{spearman}_{i,j})}.
        - 'abs_pearson': absolute value pearson correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-|\\rho^{pearson}_{i,j}|)}.
        - 'abs_spearman': absolute value spearman correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-|\\rho^{spearman}_{i,j}|)}.
        - 'distance': distance correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-\\rho^{distance}_{i,j})}.
        - 'mutual_info': mutual information matrix. Distance used is variation information matrix.
        - 'tail': lower tail dependence index matrix. Dissimilarity formula:
            .. math:: D_{i,j} = -\\log{\\lambda_{i,j}}.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `c-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `c-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `c-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `c-MLforAM`.

    risk_measure: str, optional
        The risk measure used to optimize the portfolio. If model is 'NCO',
        the risk measures available depends on the objective function.
        The default is 'MV'. Possible values are:

        - 'MV': Variance.
        - 'MAD': Mean Absolute Deviation.
        - 'MSV': Semi Standard Deviation.
        - 'FLPM': First Lower Partial Moment (Omega Ratio).
        - 'SLPM': Second Lower Partial Moment (Sortino Ratio).
        - 'VaR': Value at Risk.
        - 'CVaR': Conditional Value at Risk.
        - 'TG': Tail Gini.
        - 'EVaR': Entropic Value at Risk.
        - 'WR': Worst Realization (Minimax).
        - 'RG': Range of returns.
        - 'CVRG': CVaR range of returns.
        - 'TGRG': Tail Gini range of returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio).
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'DaR': Drawdown at Risk of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio).
        - 'ADD_Rel': Average Drawdown of compounded cumulative returns.
        - 'DaR_Rel': Drawdown at Risk of compounded cumulative returns.
        - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns.
        - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns.
        - 'UCI_Rel': Ulcer Index of compounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns.
        Used for 'FLPM' and 'SLPM'. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of VaR, CVaR, EDaR, DaR, CDaR, EDaR, Tail Gini of losses.
        The default is 0.05.
    a_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of losses. The default is 100.
    beta: float, optional
        Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value.
        The default is None.
    b_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value.
        The default is None.
    linkage: str, optional
        Linkage method of hierarchical clustering. For more information see `linkage <https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html>`__.
        The default is 'single'. Possible values are:

        - 'single'.
        - 'complete'.
        - 'average'.
        - 'weighted'.
        - 'centroid'.
        - 'median'.
        - 'ward'.
        - 'dbht': Direct Bubble Hierarchical Tree.

    k: int, optional
        Number of clusters. This value is took instead of the optimal number
        of clusters calculated with the two difference gap statistic.
        The default is None.
    max_k: int, optional
        Max number of clusters used by the two difference gap statistic
        to find the optimal number of clusters. The default is 10.
    bins_info: str, optional
        Number of bins used to calculate variation of information. The default
        value is 'KN'. Possible values are:

        - 'KN': Knuth's choice method. For more information see `knuth_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.knuth_bin_width.html>`__.
        - 'FD': Freedman–Diaconis' choice method. For more information see `freedman_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.freedman_bin_width.html>`__.
        - 'SC': Scotts' choice method. For more information see `scott_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.scott_bin_width.html>`__.
        - 'HGR': Hacine-Gharbi and Ravier' choice method.

    alpha_tail: float, optional
        Significance level for lower tail dependence index. The default is 0.05.
    leaf_order: bool, optional
        Indicates if the cluster are ordered so that the distance between
        successive leaves is minimal. The default is True.
    d: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)

    s_title = f"{p} Hierarchical equal risk contribution portfolio"
    s_title += " using " + codependence + "\ncodependence," + linkage
    s_title += " linkage and " + risk_names[risk_measure] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_herc(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        codependence=codependence,
        covariance=covariance,
        risk_measure=risk_choices[risk_measure],
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        a_sim=a_sim,
        beta=beta,
        b_sim=b_sim,
        linkage=linkage,
        k=k,
        max_k=max_k,
        bins_info=bins_info,
        alpha_tail=alpha_tail,
        leaf_order=leaf_order,
        d_ewma=d_ewma,
        value=value,
    )

    if not weights:
        console.print("There is no solution with this parameters")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            a_sim=a_sim,
            beta=beta,
            b_sim=b_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def display_nco(
    symbols: List[str],
    interval: str = "3y",
    start_date: str = "",
    end_date: str = "",
    log_returns: bool = False,
    freq: str = "D",
    maxnan: float = 0.05,
    threshold: float = 0,
    method: str = "time",
    codependence: str = "pearson",
    covariance: str = "hist",
    objective: str = "MinRisk",
    risk_measure: str = "mv",
    risk_free_rate: float = 0.0,
    risk_aversion: float = 1.0,
    alpha: float = 0.05,
    a_sim: int = 100,
    beta: float = None,
    b_sim: int = None,
    linkage: str = "ward",
    k: int = None,
    max_k: int = 10,
    bins_info: str = "KN",
    alpha_tail: float = 0.05,
    leaf_order: bool = True,
    d_ewma: float = 0.94,
    value: float = 1.0,
    table: bool = False,
) -> Dict:
    """
    Builds a hierarchical equal risk contribution portfolio

    Parameters
    ----------
    symbols : List[str]
        List of portfolio tickers
    interval : str
        interval to look at returns from
    start_date: str, optional
        If not using interval, start date string (YYYY-MM-DD)
    end_date: str, optional
        If not using interval, end date string (YYYY-MM-DD). If empty use last
        weekday.
    log_returns: bool, optional
        If True calculate log returns, else arithmetic returns. Default value
        is False
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    maxnan: float, optional
        Max percentage of nan values accepted per asset to be included in
        returns.
    threshold: float, optional
        Value used to replace outliers that are higher to threshold.
    method: str, optional
        Method used to fill nan values. Default value is 'time'. For more information see `interpolate <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html>`__.
    model: str, optional
        The hierarchical cluster portfolio model used for optimize the
        portfolio. The default is 'HRP'. Possible values are:

        - 'HRP': Hierarchical Risk Parity.
        - 'HERC': Hierarchical Equal Risk Contribution.
        - 'NCO': Nested Clustered Optimization.

    codependence: str, optional
        The codependence or similarity matrix used to build the distance
        metric and clusters. The default is 'pearson'. Possible values are:

        - 'pearson': pearson correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{0.5(1-\\rho^{pearson}_{i,j})}.
        - 'spearman': spearman correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{0.5(1-\\rho^{spearman}_{i,j})}.
        - 'abs_pearson': absolute value pearson correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-|\\rho^{pearson}_{i,j}|)}.
        - 'abs_spearman': absolute value spearman correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-|\\rho^{spearman}_{i,j}|)}.
        - 'distance': distance correlation matrix. Distance formula:
            .. math:: D_{i,j} = \\sqrt{(1-\\rho^{distance}_{i,j})}.
        - 'mutual_info': mutual information matrix. Distance used is variation information matrix.
        - 'tail': lower tail dependence index matrix. Dissimilarity formula:
            .. math:: D_{i,j} = -\\log{\\lambda_{i,j}}.

    covariance: str, optional
        The method used to estimate the covariance matrix:
        The default is 'hist'. Possible values are:

        - 'hist': use historical estimates.
        - 'ewma1': use ewma with adjust=True. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ewma2': use ewma with adjust=False. For more information see `EWM <https://pandas.pydata.org/pandas-docs/stable/user_guide/window.html#exponentially-weighted-window>`__.
        - 'ledoit': use the Ledoit and Wolf Shrinkage method.
        - 'oas': use the Oracle Approximation Shrinkage method.
        - 'shrunk': use the basic Shrunk Covariance method.
        - 'gl': use the basic Graphical Lasso Covariance method.
        - 'jlogo': use the j-LoGo Covariance method. For more information see: `c-jLogo`.
        - 'fixed': denoise using fixed method. For more information see chapter 2 of `c-MLforAM`.
        - 'spectral': denoise using spectral method. For more information see chapter 2 of `c-MLforAM`.
        - 'shrink': denoise using shrink method. For more information see chapter 2 of `c-MLforAM`.

    objective: str, optional
        Objective function used by the NCO model.
        The default is 'MinRisk'. Possible values are:

        - 'MinRisk': Minimize the selected risk measure.
        - 'Utility': Maximize the risk averse utility function.
        - 'Sharpe': Maximize the risk adjusted return ratio based on the selected risk measure.
        - 'ERC': Equally risk contribution portfolio of the selected risk measure.

    risk_measure: str, optional
        The risk measure used to optimize the portfolio. If model is 'NCO',
        the risk measures available depends on the objective function.
        The default is 'MV'. Possible values are:

        - 'MV': Variance.
        - 'MAD': Mean Absolute Deviation.
        - 'MSV': Semi Standard Deviation.
        - 'FLPM': First Lower Partial Moment (Omega Ratio).
        - 'SLPM': Second Lower Partial Moment (Sortino Ratio).
        - 'VaR': Value at Risk.
        - 'CVaR': Conditional Value at Risk.
        - 'TG': Tail Gini.
        - 'EVaR': Entropic Value at Risk.
        - 'WR': Worst Realization (Minimax).
        - 'RG': Range of returns.
        - 'CVRG': CVaR range of returns.
        - 'TGRG': Tail Gini range of returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio).
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'DaR': Drawdown at Risk of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio).
        - 'ADD_Rel': Average Drawdown of compounded cumulative returns.
        - 'DaR_Rel': Drawdown at Risk of compounded cumulative returns.
        - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns.
        - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns.
        - 'UCI_Rel': Ulcer Index of compounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns.
        Used for 'FLPM' and 'SLPM'. The default is 0.
    risk_aversion: float, optional
        Risk aversion factor of the 'Utility' objective function.
        The default is 1.
    alpha: float, optional
        Significance level of VaR, CVaR, EDaR, DaR, CDaR, EDaR, Tail Gini of losses.
        The default is 0.05.
    a_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of losses. The default is 100.
    beta: float, optional
        Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value.
        The default is None.
    b_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value.
        The default is None.
    linkage: str, optional
        Linkage method of hierarchical clustering. For more information see `linkage <https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html>`__.
        The default is 'single'. Possible values are:

        - 'single'.
        - 'complete'.
        - 'average'.
        - 'weighted'.
        - 'centroid'.
        - 'median'.
        - 'ward'.
        - 'dbht': Direct Bubble Hierarchical Tree.

    k: int, optional
        Number of clusters. This value is took instead of the optimal number
        of clusters calculated with the two difference gap statistic.
        The default is None.
    max_k: int, optional
        Max number of clusters used by the two difference gap statistic
        to find the optimal number of clusters. The default is 10.
    bins_info: str, optional
        Number of bins used to calculate variation of information. The default
        value is 'KN'. Possible values are:

        - 'KN': Knuth's choice method. For more information see `knuth_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.knuth_bin_width.html>`__.
        - 'FD': Freedman–Diaconis' choice method. For more information see `freedman_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.freedman_bin_width.html>`__.
        - 'SC': Scotts' choice method. For more information see `scott_bin_width <https://docs.astropy.org/en/stable/api/astropy.stats.scott_bin_width.html>`__.
        - 'HGR': Hacine-Gharbi and Ravier' choice method.

    alpha_tail: float, optional
        Significance level for lower tail dependence index. The default is 0.05.
    leaf_order: bool, optional
        Indicates if the cluster are ordered so that the distance between
        successive leaves is minimal. The default is True.
    d: float, optional
        The smoothing factor of ewma methods.
        The default is 0.94.
    value : float, optional
        Amount to allocate to portfolio in long positions, by default 1.0
    value_short : float, optional
        Amount to allocate to portfolio in short positions, by default 0.0
    table: bool, optional
        True if plot table weights, by default False
    """
    p = d_period(interval, start_date, end_date)

    s_title = f"{p} Nested clustered optimization"
    s_title += " using " + codependence + " codependence,\n" + linkage
    s_title += " linkage and " + risk_names[risk_measure] + " as risk measure\n"

    weights, stock_returns = optimizer_model.get_nco(
        symbols=symbols,
        interval=interval,
        start_date=start_date,
        end_date=end_date,
        log_returns=log_returns,
        freq=freq,
        maxnan=maxnan,
        threshold=threshold,
        method=method,
        codependence=codependence,
        covariance=covariance,
        objective=objectives_choices[objective.lower()],
        risk_measure=risk_choices[risk_measure],
        risk_free_rate=risk_free_rate,
        risk_aversion=risk_aversion,
        alpha=alpha,
        a_sim=a_sim,
        beta=beta,
        b_sim=b_sim,
        linkage=linkage,
        k=k,
        max_k=max_k,
        bins_info=bins_info,
        alpha_tail=alpha_tail,
        leaf_order=leaf_order,
        d_ewma=d_ewma,
        value=value,
    )

    if not weights:
        console.print("There is no solution with this parameters")
        return {}

    if table:
        console.print(s_title)
        display_weights(weights)
        portfolio_performance(
            weights=weights,
            data=stock_returns,
            risk_measure=risk_choices[risk_measure],
            risk_free_rate=risk_free_rate,
            alpha=alpha,
            a_sim=a_sim,
            beta=beta,
            b_sim=b_sim,
            freq=freq,
        )

    return weights


@log_start_end(log=logger)
def my_autopct(x):
    """Function for autopct of plt.pie.  This results in values not being printed in the pie if they are 'too small'"""
    if x > 4:
        return f"{x:.2f} %"

    return ""


@log_start_end(log=logger)
def pie_chart_weights(
    weights: dict, title_opt: str, external_axes: Optional[List[plt.Axes]]
):
    """Show a pie chart of holdings

    Parameters
    ----------
    weights: dict
        Weights to display, where keys are tickers, and values are either weights or values if -v specified
    title_opt: str
        Title to be used on the plot title
    external_axes:Optiona[List[plt.Axes]]
        Optional external axes to plot data on
    """
    if not weights:
        return

    init_stocks = list(weights.keys())
    init_sizes = list(weights.values())
    symbols = []
    sizes = []
    for stock, size in zip(init_stocks, init_sizes):
        if size > 0:
            symbols.append(stock)
            sizes.append(size)

    total_size = np.sum(sizes)
    colors = theme.get_colors()

    if external_axes is None:
        _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
    else:
        ax = external_axes[0]

    if math.isclose(sum(sizes), 1, rel_tol=0.1):
        _, _, autotexts = ax.pie(
            sizes,
            labels=symbols,
            autopct=my_autopct,
            colors=colors,
            textprops=dict(color="white"),
            wedgeprops={"linewidth": 0.5, "edgecolor": "white"},
            labeldistance=1.05,
            startangle=45,
            normalize=True,
        )
        plt.setp(autotexts, color="white", fontweight="bold")
    else:
        _, _, autotexts = ax.pie(
            sizes,
            labels=symbols,
            autopct="",
            colors=colors,
            textprops=dict(color="white"),
            wedgeprops={"linewidth": 0.5, "edgecolor": "white"},
            labeldistance=1.05,
            startangle=45,
            normalize=True,
        )
        plt.setp(autotexts, color="white", fontweight="bold")
        for i, a in enumerate(autotexts):
            if sizes[i] / total_size > 0.05:
                a.set_text(f"{sizes[i]:.2f}")
            else:
                a.set_text("")

    ax.axis("equal")

    # leg1 = ax.legend(
    #     wedges,
    #     [str(s) for s in stocks],
    #     title="  Ticker",
    #     loc="upper left",
    #     bbox_to_anchor=(0.80, 0, 0.5, 1),
    #     frameon=False,
    # )
    # leg2 = ax.legend(
    #     wedges,
    #     [
    #         f"{' ' if ((100*s/total_size) < 10) else ''}{100*s/total_size:.2f}%"
    #         for s in sizes
    #     ],
    #     title=" ",
    #     loc="upper left",
    #     handlelength=0,
    #     bbox_to_anchor=(0.91, 0, 0.5, 1),
    #     frameon=False,
    # )
    # ax.add_artist(leg1)
    # ax.add_artist(leg2)

    plt.setp(autotexts, size=8, weight="bold")

    title = "Portfolio - " + title_opt + "\n"
    title += "Portfolio Composition"
    ax.set_title(title)

    if external_axes is None:
        theme.visualize_output()


@log_start_end(log=logger)
def additional_plots(
    weights: Dict,
    data: pd.DataFrame,
    category_dict: Dict = None,
    category: str = "",
    portfolio_name: str = "",
    freq: str = "D",
    risk_measure: str = "MV",
    risk_free_rate: float = 0,
    alpha: float = 0.05,
    a_sim: float = 100,
    beta: float = None,
    b_sim: float = None,
    pie: bool = False,
    hist: bool = False,
    dd: bool = False,
    rc_chart: bool = False,
    heat: bool = False,
    external_axes: Optional[List[plt.Axes]] = None,
):
    """
    Plot additional charts

    Parameters
    ----------
    weights: Dict
        Dict of portfolio weights
    data: pd.DataFrame
        DataFrame of stock returns
    category_dict: Dict
        Dict of categories
    category: str
        Category to plot
    portfolio_name: str
        Portfolio name
    freq: str, optional
        The frequency used to calculate returns. Default value is 'D'. Possible
        values are:
        - 'D' for daily returns.
        - 'W' for weekly returns.
        - 'M' for monthly returns.

    risk_measure: str, optional
        The risk measure used to optimize the portfolio. If model is 'NCO',
        the risk measures available depends on the objective function.
        The default is 'MV'. Possible values are:

        - 'MV': Variance.
        - 'MAD': Mean Absolute Deviation.
        - 'MSV': Semi Standard Deviation.
        - 'FLPM': First Lower Partial Moment (Omega Ratio).
        - 'SLPM': Second Lower Partial Moment (Sortino Ratio).
        - 'VaR': Value at Risk.
        - 'CVaR': Conditional Value at Risk.
        - 'TG': Tail Gini.
        - 'EVaR': Entropic Value at Risk.
        - 'WR': Worst Realization (Minimax).
        - 'RG': Range of returns.
        - 'CVRG': CVaR range of returns.
        - 'TGRG': Tail Gini range of returns.
        - 'MDD': Maximum Drawdown of uncompounded cumulative returns (Calmar Ratio).
        - 'ADD': Average Drawdown of uncompounded cumulative returns.
        - 'DaR': Drawdown at Risk of uncompounded cumulative returns.
        - 'CDaR': Conditional Drawdown at Risk of uncompounded cumulative returns.
        - 'EDaR': Entropic Drawdown at Risk of uncompounded cumulative returns.
        - 'UCI': Ulcer Index of uncompounded cumulative returns.
        - 'MDD_Rel': Maximum Drawdown of compounded cumulative returns (Calmar Ratio).
        - 'ADD_Rel': Average Drawdown of compounded cumulative returns.
        - 'DaR_Rel': Drawdown at Risk of compounded cumulative returns.
        - 'CDaR_Rel': Conditional Drawdown at Risk of compounded cumulative returns.
        - 'EDaR_Rel': Entropic Drawdown at Risk of compounded cumulative returns.
        - 'UCI_Rel': Ulcer Index of compounded cumulative returns.

    risk_free_rate: float, optional
        Risk free rate, must be in the same interval of assets returns.
        Used for 'FLPM' and 'SLPM'. The default is 0.
    alpha: float, optional
        Significance level of VaR, CVaR, EDaR, DaR, CDaR, EDaR, Tail Gini of losses.
        The default is 0.05.
    a_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of losses. The default is 100.
    beta: float, optional
        Significance level of CVaR and Tail Gini of gains. If None it duplicates alpha value.
        The default is None.
    b_sim: float, optional
        Number of CVaRs used to approximate Tail Gini of gains. If None it duplicates a_sim value.
        The default is None.
    pie : bool, optional
        Display a pie chart of values, by default False
    hist : bool, optional
        Display a histogram with risk measures, by default False
    dd : bool, optional
        Display a drawdown chart with risk measures, by default False
    rc-chart : float, optional
        Display a risk contribution chart for assets, by default False
    heat : float, optional
        Display a heatmap of correlation matrix with dendrogram, by default False
    external_axes: Optional[List[plt.Axes]]
        Optional axes to plot data on
    """

    title_opt = category if not portfolio_name else category + " - " + portfolio_name

    if category_dict is not None:
        weights_df = pd.DataFrame.from_dict(
            data=weights, orient="index", columns=["value"], dtype=float
        )
        category_df = pd.DataFrame.from_dict(
            data=category_dict, orient="index", columns=["category"]
        )
        weights_df = weights_df.join(category_df, how="inner")
        weights_df.sort_index(inplace=True)

        # Calculating classes returns
        classes = list(set(weights_df["category"]))
        weights_classes = weights_df.groupby(["category"]).sum()
        matrix_classes = np.zeros((len(weights_df), len(classes)))
        labels = weights_df["category"].tolist()

        j_value = 0
        for i in classes:
            matrix_classes[:, j_value] = np.array(
                [1 if x == i else 0 for x in labels], dtype=float
            )
            matrix_classes[:, j_value] = (
                matrix_classes[:, j_value]
                * weights_df["value"]
                / weights_classes.loc[i, "value"]
            )
            j_value += 1

        matrix_classes = pd.DataFrame(
            matrix_classes, columns=classes, index=weights_df.index
        )
        data = data @ matrix_classes
        weights_df = weights_classes["value"].copy()
        weights_df.replace(0, np.nan, inplace=True)
        weights_df.dropna(inplace=True)
        weights_df.sort_values(ascending=True, inplace=True)
        data = data[weights_df.index.tolist()]
        data.columns = [i.title() for i in data.columns]
        weights_df.index = [i.title() for i in weights_df.index]
        weights = weights_df.to_dict()

    colors = theme.get_colors()
    if pie:
        pie_chart_weights(weights, title_opt, external_axes)

    if hist:
        if external_axes is None:
            _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
        else:
            ax = external_axes[0]

        ax = rp.plot_hist(data, w=pd.Series(weights).to_frame(), alpha=alpha, ax=ax)
        ax.legend(fontsize="x-small", loc="best")

        # Changing colors
        for i in ax.get_children()[:-1]:
            if isinstance(i, matplotlib.patches.Rectangle):
                i.set_color(colors[0])
                i.set_alpha(0.7)

        k = 1
        for i, j in zip(ax.get_legend().get_lines()[::-1], ax.get_lines()[::-1]):
            i.set_color(colors[k])
            j.set_color(colors[k])
            k += 1

        title = "Portfolio - " + title_opt + "\n"
        title += ax.get_title(loc="left")
        ax.set_title(title)

        if external_axes is None:
            theme.visualize_output()

    if dd:
        if external_axes is None:
            _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
        else:
            ax = external_axes[0]

        nav = data.cumsum()
        ax = rp.plot_drawdown(
            nav=nav, w=pd.Series(weights).to_frame(), alpha=alpha, ax=ax
        )

        ax[0].remove()
        ax = ax[1]
        fig = ax.get_figure()
        gs = GridSpec(1, 1, figure=fig)
        ax.set_position(gs[0].get_position(fig))
        ax.set_subplotspec(gs[0])

        # Changing colors
        ax.get_lines()[0].set_color(colors[0])
        k = 1
        for i, j in zip(ax.get_legend().get_lines()[::-1], ax.get_lines()[1:][::-1]):
            i.set_color(colors[k])
            j.set_color(colors[k])
            k += 1

        ax.get_children()[1].set_facecolor(colors[0])
        ax.get_children()[1].set_alpha(0.7)

        title = "Portfolio - " + title_opt + "\n"
        title += ax.get_title(loc="left")
        ax.set_title(title)

        if external_axes is None:
            theme.visualize_output()

    if rc_chart:
        if external_axes is None:
            _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
        else:
            ax = external_axes[0]

        ax = rp.plot_risk_con(
            w=pd.Series(weights).to_frame(),
            cov=data.cov(),
            returns=data,
            rm=risk_choices[risk_measure.lower()],
            rf=risk_free_rate,
            alpha=alpha,
            a_sim=a_sim,
            beta=beta,
            b_sim=b_sim,
            color=colors[1],
            t_factor=time_factor[freq.upper()],
            ax=ax,
        )

        # Changing colors
        for i in ax.get_children()[:-1]:
            if isinstance(i, matplotlib.patches.Rectangle):
                i.set_width(i.get_width())
                i.set_color(colors[0])

        title = "Portfolio - " + title_opt + "\n"
        title += ax.get_title(loc="left")
        ax.set_title(title)

        if external_axes is None:
            theme.visualize_output()

    if heat:

        if len(weights) == 1:
            single_key = list(weights.keys())[0].upper()
            console.print(
                f"[yellow]Heatmap needs at least two values for '{category}', only found '{single_key}'.[/yellow]"
            )
            return

        if external_axes is None:
            _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
        else:
            ax = external_axes[0]

        if len(weights) <= 3:
            number_of_clusters = len(weights)
        else:
            number_of_clusters = None

        ax = rp.plot_clusters(
            returns=data,
            codependence="pearson",
            linkage="ward",
            k=number_of_clusters,
            max_k=10,
            leaf_order=True,
            dendrogram=True,
            cmap="RdYlBu",
            # linecolor='tab:purple',
            ax=ax,
        )

        ax = ax.get_figure().axes
        ax[0].grid(False)
        ax[0].axis("off")

        if category_dict is None:
            # Vertical dendrogram
            l, b, w, h = ax[4].get_position().bounds
            l1 = l * 0.5
            w1 = w * 0.2
            b1 = h * 0.05
            ax[4].set_position([l - l1, b + b1, w * 0.8, h * 0.95])
            # Heatmap
            l, b, w, h = ax[1].get_position().bounds
            ax[1].set_position([l - l1 - w1, b + b1, w * 0.8, h * 0.95])
            w2 = w * 0.2
            # colorbar
            l, b, w, h = ax[2].get_position().bounds
            ax[2].set_position([l - l1 - w1 - w2, b, w, h])
            # Horizontal dendrogram
            l, b, w, h = ax[3].get_position().bounds
            ax[3].set_position([l - l1 - w1, b, w * 0.8, h])
        else:
            # Vertical dendrogram
            l, b, w, h = ax[4].get_position().bounds
            l1 = l * 0.5
            w1 = w * 0.4
            b1 = h * 0.2
            ax[4].set_position([l - l1, b + b1, w * 0.6, h * 0.8])
            # Heatmap
            l, b, w, h = ax[1].get_position().bounds
            ax[1].set_position([l - l1 - w1, b + b1, w * 0.6, h * 0.8])
            w2 = w * 0.05
            # colorbar
            l, b, w, h = ax[2].get_position().bounds
            ax[2].set_position([l - l1 - w1 - w2, b, w, h])
            # Horizontal dendrogram
            l, b, w, h = ax[3].get_position().bounds
            ax[3].set_position([l - l1 - w1, b, w * 0.6, h])

        title = "Portfolio - " + title_opt + "\n"
        title += ax[3].get_title(loc="left")
        ax[3].set_title(title)

        if external_axes is None:
            theme.visualize_output(force_tight_layout=True)


def display_show(weights: Dict, tables: List[str], categories_dict: Dict[Any, Any]):
    """Display the results of the optimization.

    Parameters
    ----------
    weights : Dict
        Dictionary of weights.
    tables : List[str]
        List of tables to display.
    categories_dict : Dict[Any, Any]
        Dictionary of categories.
    """

    display_weights(weights)

    for t in tables:
        console.print("")
        display_categories(
            weights=weights,
            categories=categories_dict,
            column=t,
            title="Category - " + t.title(),
        )
