"""Portfolio Model"""
__docformat__ = "numpy"

import logging
from typing import Tuple, Union

import numpy as np
import scipy
import pandas as pd
from sklearn.metrics import r2_score
from openbb_terminal.common.quantitative_analysis import qa_model
from openbb_terminal.decorators import log_start_end
from openbb_terminal.portfolio.statics import PERIODS
from openbb_terminal.portfolio import portfolio_helper, metrics_model
from openbb_terminal.portfolio.portfolio_engine import PortfolioEngine


# pylint: disable=E1136,W0201,R0902,C0302
# pylint: disable=unsupported-assignment-operation,redefined-outer-name,too-many-public-methods, consider-using-f-string

logger = logging.getLogger(__name__)

pd.options.mode.chained_assignment = None


@log_start_end(log=logger)
def generate_portfolio(
    transactions_file_path: str,
    benchmark_symbol: str = "SPY",
    full_shares: bool = False,
    risk_free_rate: float = 0,
) -> PortfolioEngine:
    """Get PortfolioEngine object

    Parameters
    ----------
    transactions_file_path : str
        Path to transactions file
    benchmark_symbol : str
        Benchmark ticker to download data
    full_shares : bool
        Whether to mimic the portfolio trades exactly (partial shares) or round down the
        quantity to the nearest number
    risk_free_rate : float
        Risk free rate in float format

    Returns
    -------
    PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    """

    transactions = PortfolioEngine.read_transactions(transactions_file_path)
    portfolio_engine = PortfolioEngine(transactions)
    portfolio_engine.generate_portfolio_data()
    portfolio_engine.set_benchmark(symbol=benchmark_symbol, full_shares=full_shares)
    portfolio_engine.set_risk_free_rate(risk_free_rate)

    return portfolio_engine


@log_start_end(log=logger)
def get_transactions(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get portfolio transactions

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        Portfolio transactions

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.show(p)
    """

    return portfolio_engine.get_transactions()


@log_start_end(log=logger)
def set_benchmark(
    portfolio_engine: PortfolioEngine, symbol: str, full_shares: bool = False
):
    """Load benchmark into portfolio

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    symbol: str
        Benchmark symbol to download data
    full_shares: bool
        Whether to mimic the portfolio trades exactly (partial shares) or round down the
        quantity to the nearest number

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.bench(p, symbol="SPY")
    """

    portfolio_engine.set_benchmark(symbol=symbol, full_shares=full_shares)


@log_start_end(log=logger)
def set_risk_free_rate(portfolio_engine: PortfolioEngine, risk_free_rate: float):
    """Set risk-free rate

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    risk_free_rate: float
        Risk free rate in float format
    """

    portfolio_engine.set_risk_free_rate(risk_free_rate=risk_free_rate)


def get_holdings_value(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get holdings of assets (absolute value)

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame of holdings value

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.holdv(p)
    """

    all_holdings = portfolio_engine.historical_trade_data["End Value"][
        portfolio_engine.tickers_list
    ]

    all_holdings["Total Value"] = all_holdings.sum(axis=1)
    # No need to account for time since this is daily data
    all_holdings.index = all_holdings.index.date

    return all_holdings


def get_holdings_percentage(
    portfolio_engine: PortfolioEngine,
) -> pd.DataFrame:
    """Get holdings of assets (in percentage)

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame of holdings percentage

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.holdp(p)
    """

    all_holdings = portfolio_engine.historical_trade_data["End Value"][
        portfolio_engine.tickers_list
    ]

    all_holdings = all_holdings.divide(all_holdings.sum(axis=1), axis=0) * 100

    # order it a bit more in terms of magnitude
    all_holdings = all_holdings[all_holdings.sum().sort_values(ascending=False).index]

    return all_holdings


@log_start_end(log=logger)
def get_yearly_returns(
    portfolio_engine: PortfolioEngine,
    window: str = "all",
) -> pd.DataFrame:
    """Get yearly returns

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window : str
        interval to compare cumulative returns and benchmark

    Returns
    -------
    pd.DataFrame
        DataFrame with yearly returns

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.yret(p)
    """

    portfolio_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.returns, window
    )
    benchmark_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.benchmark_returns, window
    )

    creturns_year_val = list()
    breturns_year_val = list()

    for year in sorted(set(portfolio_returns.index.year)):
        creturns_year = portfolio_returns[portfolio_returns.index.year == year]
        cumulative_returns = 100 * metrics_model.cumulative_returns(creturns_year)
        creturns_year_val.append(cumulative_returns.values[-1])

        breturns_year = benchmark_returns[benchmark_returns.index.year == year]
        benchmark_c_returns = 100 * metrics_model.cumulative_returns(breturns_year)
        breturns_year_val.append(benchmark_c_returns.values[-1])

    df = pd.DataFrame(
        {
            "Portfolio": pd.Series(
                creturns_year_val, index=list(set(portfolio_returns.index.year))
            ),
            "Benchmark": pd.Series(
                breturns_year_val, index=list(set(portfolio_returns.index.year))
            ),
            "Difference": pd.Series(
                np.array(creturns_year_val) - np.array(breturns_year_val),
                index=list(set(portfolio_returns.index.year)),
            ),
        }
    )

    return df


@log_start_end(log=logger)
def get_monthly_returns(
    portfolio_engine: PortfolioEngine,
    window: str = "all",
) -> pd.DataFrame:
    """Get monthly returns

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window : str
        interval to compare cumulative returns and benchmark

    Returns
    -------
    pd.DataFrame
        DataFrame with monthly returns

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.mret(p)
    """

    portfolio_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.returns, window
    )
    benchmark_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.benchmark_returns, window
    )

    creturns_month_val = list()
    breturns_month_val = list()

    for year in sorted(list(set(portfolio_returns.index.year))):
        creturns_year = portfolio_returns[portfolio_returns.index.year == year]
        creturns_val = list()
        for i in range(1, 13):
            creturns_year_month = creturns_year[creturns_year.index.month == i]
            creturns_year_month_val = 100 * metrics_model.cumulative_returns(
                creturns_year_month
            )

            if creturns_year_month.empty:
                creturns_val.append(0)
            else:
                creturns_val.append(creturns_year_month_val.values[-1])
        creturns_month_val.append(creturns_val)

        breturns_year = benchmark_returns[benchmark_returns.index.year == year]
        breturns_val = list()
        for i in range(1, 13):
            breturns_year_month = breturns_year[breturns_year.index.month == i]
            breturns_year_month_val = 100 * metrics_model.cumulative_returns(
                breturns_year_month
            )

            if breturns_year_month.empty:
                breturns_val.append(0)
            else:
                breturns_val.append(breturns_year_month_val.values[-1])
        breturns_month_val.append(breturns_val)

    monthly_returns = pd.DataFrame(
        creturns_month_val,
        index=sorted(list(set(portfolio_returns.index.year))),
        columns=[
            "Jan",
            "Feb",
            "Mar",
            "Apr",
            "May",
            "Jun",
            "Jul",
            "Aug",
            "Sep",
            "Oct",
            "Nov",
            "Dec",
        ],
    )
    bench_monthly_returns = pd.DataFrame(
        breturns_month_val,
        index=sorted(list(set(benchmark_returns.index.year))),
        columns=[
            "Jan",
            "Feb",
            "Mar",
            "Apr",
            "May",
            "Jun",
            "Jul",
            "Aug",
            "Sep",
            "Oct",
            "Nov",
            "Dec",
        ],
    )

    return monthly_returns, bench_monthly_returns


@log_start_end(log=logger)
def get_daily_returns(
    portfolio_engine: PortfolioEngine,
    window: str = "all",
) -> pd.DataFrame:
    """Get daily returns

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window : str
        interval to compare cumulative returns and benchmark

    Returns
    -------
    pd.DataFrame
        DataFrame with daily returns

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.dret(p)
    """

    portfolio_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.returns, window
    )
    benchmark_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.benchmark_returns, window
    )

    df = portfolio_returns.to_frame()
    df = df.join(benchmark_returns)
    df.index = df.index.date
    df.columns = ["portfolio", "benchmark"]

    return df


def join_allocation(
    portfolio: pd.DataFrame, benchmark: pd.DataFrame, column: str
) -> pd.DataFrame:
    """Help method to join portfolio and benchmark allocation by column

    Parameters
    ----------
    portfolio: pd.DataFrame
        Portfolio allocation
    benchmark: pd.DataFrame
        Benchmark allocation
    column: str
        Column to join DataFrames

    Returns
    -------
    pd.DataFrame
        DataFrame with portfolio and benchmark allocations
    """
    combined = pd.merge(portfolio, benchmark, on=column, how="left")
    combined["Difference"] = combined["Portfolio"] - combined["Benchmark"]
    combined = combined.replace(np.nan, "-")
    combined = combined.replace(0, "-")

    return combined


def get_distribution_returns(
    portfolio_engine: PortfolioEngine,
    window: str = "all",
) -> pd.DataFrame:
    """Display daily returns

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window : str
        interval to compare cumulative returns and benchmark

    Returns
    -------
    pd.DataFrame
        DataFrame of returns distribution

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.distr(p)
    """

    portfolio_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.returns, window
    )
    benchmark_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.benchmark_returns, window
    )

    df = pd.DataFrame(portfolio_returns).join(pd.DataFrame(benchmark_returns))
    df.columns.values[0] = "portfolio"
    df.columns.values[1] = "benchmark"

    return df


@log_start_end(log=logger)
def get_maximum_drawdown(
    portfolio_engine: PortfolioEngine, is_returns: bool = False
) -> Tuple[pd.DataFrame, pd.Series]:
    """Calculate the drawdown (MDD) of historical series.  Note that the calculation is done
     on cumulative returns (or prices).  The definition of drawdown is

     DD = (current value - rolling maximum) / rolling maximum

    Parameters
    ----------
    data: pd.Series
        Series of input values
    is_returns: bool
        Flag to indicate inputs are returns

    Returns
    -------
    pd.Series
        Holdings series
    pd.Series
        Drawdown series

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.maxdd(p)
    """

    holdings: pd.Series = portfolio_engine.portfolio_value
    if is_returns:
        holdings = (1 + holdings).cumprod()

    rolling_max = holdings.cummax()
    drawdown = (holdings - rolling_max) / rolling_max

    return holdings, drawdown


def get_rolling_volatility(
    portfolio_engine: PortfolioEngine, window: str = "1y"
) -> pd.DataFrame:
    """Get rolling volatility

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window : str
        Rolling window size to use
        Possible options: mtd, qtd, ytd, 1d, 5d, 10d, 1m, 3m, 6m, 1y, 3y, 5y, 10y

    Returns
    -------
    pd.DataFrame
        Rolling volatility DataFrame

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.rvol(p)
    """

    portfolio_rvol = metrics_model.rolling_volatility(portfolio_engine.returns, window)
    if portfolio_rvol.empty:
        return pd.DataFrame()

    benchmark_rvol = metrics_model.rolling_volatility(
        portfolio_engine.benchmark_returns, window
    )
    if benchmark_rvol.empty:
        return pd.DataFrame()

    df = pd.DataFrame(portfolio_rvol).join(pd.DataFrame(benchmark_rvol))
    df.columns.values[0] = "portfolio"
    df.columns.values[1] = "benchmark"

    return df


def get_rolling_sharpe(
    portfolio_engine: pd.DataFrame, risk_free_rate: float = 0, window: str = "1y"
) -> pd.DataFrame:
    """Get rolling sharpe ratio

    Parameters
    ----------
    portfolio_returns : pd.Series
        Series of portfolio returns
    risk_free_rate : float
        Risk free rate
    window : str
        Rolling window to use
        Possible options: mtd, qtd, ytd, 1d, 5d, 10d, 1m, 3m, 6m, 1y, 3y, 5y, 10y

    Returns
    -------
    pd.DataFrame
        Rolling sharpe ratio DataFrame

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.rsharpe(p)
    """

    portfolio_rsharpe = metrics_model.rolling_sharpe(
        portfolio_engine.returns, risk_free_rate, window
    )
    if portfolio_rsharpe.empty:
        return pd.DataFrame()

    benchmark_rsharpe = metrics_model.rolling_sharpe(
        portfolio_engine.benchmark_returns, risk_free_rate, window
    )
    if benchmark_rsharpe.empty:
        return pd.DataFrame()

    df = pd.DataFrame(portfolio_rsharpe).join(pd.DataFrame(benchmark_rsharpe))
    df.columns.values[0] = "portfolio"
    df.columns.values[1] = "benchmark"

    return df


def get_rolling_sortino(
    portfolio_engine: PortfolioEngine,
    risk_free_rate: float = 0,
    window: str = "1y",
) -> pd.DataFrame:
    """Get rolling sortino

    Parameters
    ----------
    portfolio : PortfolioEngine
        PortfolioEngine object
    window: str
        interval for window to consider
        Possible options: mtd, qtd, ytd, 1d, 5d, 10d, 1m, 3m, 6m, 1y, 3y, 5y, 10y
    risk_free_rate: float
        Value to use for risk free rate in sharpe/other calculations

    Returns
    -------
    pd.DataFrame
        Rolling sortino ratio DataFrame

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.rsort(p)
    """

    portfolio_rsortino = metrics_model.rolling_sortino(
        portfolio_engine.returns, risk_free_rate, window
    )
    if portfolio_rsortino.empty:
        return pd.DataFrame()

    benchmark_rsortino = metrics_model.rolling_sortino(
        portfolio_engine.benchmark_returns, risk_free_rate, window
    )
    if benchmark_rsortino.empty:
        return pd.DataFrame()

    df = pd.DataFrame(portfolio_rsortino).join(pd.DataFrame(benchmark_rsortino))
    df.columns.values[0] = "portfolio"
    df.columns.values[1] = "benchmark"

    return df


@log_start_end(log=logger)
def get_rolling_beta(
    portfolio_engine: PortfolioEngine,
    window: str = "1y",
) -> pd.DataFrame:
    """Get rolling beta using portfolio and benchmark returns

    Parameters
    ----------
    portfolio : PortfolioEngine
        PortfolioEngine object
    window: string
        Interval used for rolling values.
        Possible options: mtd, qtd, ytd, 1d, 5d, 10d, 1m, 3m, 6m, 1y, 3y, 5y, 10y.

    Returns
    -------
    pd.DataFrame
        DataFrame of the portfolio's rolling beta

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.rbeta(p)
    """

    df = metrics_model.rolling_beta(
        portfolio_engine.returns, portfolio_engine.benchmark_returns, window
    )

    return df


def get_summary(
    portfolio_engine: PortfolioEngine,
    window: str = "all",
    risk_free_rate: float = 0,
) -> pd.DataFrame:
    """Get portfolio and benchmark returns summary

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window : str
        interval to compare cumulative returns and benchmark
    risk_free_rate : float
        Risk free rate for calculations

    Returns
    -------
    pd.DataFrame
        DataFrame with portfolio and benchmark returns summary

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.summary(p)
    """

    portfolio_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.returns, window
    )
    benchmark_returns = portfolio_helper.filter_df_by_period(
        portfolio_engine.benchmark_returns, window
    )

    metrics = {
        "Volatility": [portfolio_returns.std(), benchmark_returns.std()],
        "Skew": [
            scipy.stats.skew(portfolio_returns),
            scipy.stats.skew(benchmark_returns),
        ],
        "Kurtosis": [
            scipy.stats.kurtosis(portfolio_returns),
            scipy.stats.kurtosis(benchmark_returns),
        ],
        "Maximum Drawdown": [
            metrics_model.maximum_drawdown(portfolio_returns),
            metrics_model.maximum_drawdown(benchmark_returns),
        ],
        "Sharpe ratio": [
            metrics_model.sharpe_ratio(portfolio_returns, risk_free_rate),
            metrics_model.sharpe_ratio(benchmark_returns, risk_free_rate),
        ],
        "Sortino ratio": [
            metrics_model.sortino_ratio(portfolio_returns, risk_free_rate),
            metrics_model.sortino_ratio(benchmark_returns, risk_free_rate),
        ],
        "R2 Score": [
            r2_score(portfolio_returns, benchmark_returns),
            r2_score(portfolio_returns, benchmark_returns),
        ],
    }

    summary = pd.DataFrame(
        metrics.values(), index=metrics.keys(), columns=["Portfolio", "Benchmark"]
    )
    summary["Difference"] = summary["Portfolio"] - summary["Benchmark"]
    summary.loc["Volatility"] = summary.loc["Volatility"].apply("{:.2%}".format)
    summary.loc["Maximum Drawdown"] = summary.loc["Maximum Drawdown"].apply(
        "{:.2%}".format
    )
    summary.loc["R2 Score"] = summary.loc["R2 Score"].apply("{:.2%}".format)

    return summary


@log_start_end(log=logger)
def get_assets_allocation(
    portfolio_engine: PortfolioEngine,
    tables: bool = False,
    limit: int = 10,
    recalculate: bool = False,
) -> Union[pd.DataFrame, Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]]:
    """Display portfolio asset allocation compared to the benchmark

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    tables: bool
        Whether to include separate allocation tables
    limit: int
        The amount of assets you wish to show, by default this is set to 10
    recalculate: bool
        Flag to force recalculate allocation if already exists

    Returns
    -------
    Union[pd.DataFrame, Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]]
        DataFrame with combined allocation plus individual allocation if tables is `True`.

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.alloc.assets(p)
    """

    portfolio_engine.calculate_allocation(category="Asset", recalculate=recalculate)

    benchmark_allocation = portfolio_engine.benchmark_assets_allocation.iloc[:limit]
    portfolio_allocation = portfolio_engine.portfolio_assets_allocation.iloc[:limit]

    combined = join_allocation(portfolio_allocation, benchmark_allocation, "Symbol")

    if tables:
        return combined, portfolio_allocation, benchmark_allocation
    return combined


def get_sectors_allocation(
    portfolio_engine: PortfolioEngine,
    limit: int = 10,
    tables: bool = False,
    recalculate: bool = False,
):
    """Display portfolio sector allocation compared to the benchmark

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    tables: bool
        Whether to include separate allocation tables
    limit: int
        The amount of assets you wish to show, by default this is set to 10
    recalculate: bool
        Flag to force recalculate allocation if already exists

    Returns
    -------
    Union[pd.DataFrame, Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]]
        DataFrame with combined allocation plus individual allocation if tables is `True`.

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.alloc.sectors(p)
    """

    portfolio_engine.calculate_allocation(category="Sector", recalculate=recalculate)

    benchmark_allocation = portfolio_engine.benchmark_sectors_allocation.iloc[:limit]
    portfolio_allocation = portfolio_engine.portfolio_sectors_allocation.iloc[:limit]

    combined = join_allocation(portfolio_allocation, benchmark_allocation, "Sector")

    if tables:
        return combined, portfolio_allocation, benchmark_allocation
    return combined


def get_countries_allocation(
    portfolio_engine: PortfolioEngine,
    limit: int = 10,
    tables: bool = False,
    recalculate: bool = False,
):
    """Display portfolio country allocation compared to the benchmark

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    tables: bool
        Whether to include separate allocation tables
    limit: int
        The amount of assets you wish to show, by default this is set to 10
    recalculate: bool
        Flag to force recalculate allocation if already exists

    Returns
    -------
    Union[pd.DataFrame, Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]]
        DataFrame with combined allocation plus individual allocation if tables is `True`.

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.alloc.countries(p)
    """

    portfolio_engine.calculate_allocation(category="Country", recalculate=recalculate)

    benchmark_allocation = portfolio_engine.benchmark_countries_allocation.iloc[:limit]
    portfolio_allocation = portfolio_engine.portfolio_countries_allocation.iloc[:limit]

    combined = join_allocation(portfolio_allocation, benchmark_allocation, "Country")

    if tables:
        return combined, portfolio_allocation, benchmark_allocation
    return combined


def get_regions_allocation(
    portfolio_engine: PortfolioEngine,
    limit: int = 10,
    tables: bool = False,
    recalculate: bool = False,
):
    """Display portfolio region allocation compared to the benchmark

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    tables: bool
        Whether to include separate allocation tables
    limit: int
        The amount of assets you wish to show, by default this is set to 10
    recalculate: bool
        Flag to force recalculate allocation if already exists

    Returns
    -------
    Union[pd.DataFrame, Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]]
        DataFrame with combined allocation plus individual allocation if tables is `True`.

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.alloc.regions(p)
    """

    portfolio_engine.calculate_allocation(category="Region", recalculate=recalculate)

    benchmark_allocation = portfolio_engine.benchmark_regions_allocation.iloc[:limit]
    portfolio_allocation = portfolio_engine.portfolio_regions_allocation.iloc[:limit]

    combined = join_allocation(portfolio_allocation, benchmark_allocation, "Region")

    if tables:
        return combined, portfolio_allocation, benchmark_allocation
    return combined


@log_start_end(log=logger)
def get_r2_score(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get R2 Score for portfolio and benchmark selected

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame with R2 Score between portfolio and benchmark for different periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.rsquare(p)
    """

    vals = list()
    for period in PERIODS:
        vals.append(
            round(
                r2_score(
                    portfolio_helper.filter_df_by_period(
                        portfolio_engine.returns, period
                    ),
                    portfolio_helper.filter_df_by_period(
                        portfolio_engine.benchmark_returns, period
                    ),
                ),
                3,
            )
        )
    return pd.DataFrame(vals, index=PERIODS, columns=["R2 Score"])


@log_start_end(log=logger)
def get_skewness(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get skewness for portfolio and benchmark selected

    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame with skewness for portfolio and benchmark for different periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.skew(p)
    """

    vals = list()
    for period in PERIODS:
        vals.append(
            [
                round(
                    scipy.stats.skew(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.returns, period
                        )
                    ),
                    3,
                ),
                round(
                    scipy.stats.skew(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.benchmark_returns, period
                        )
                    ),
                    3,
                ),
            ]
        )
    return pd.DataFrame(vals, index=PERIODS, columns=["Portfolio", "Benchmark"])


@log_start_end(log=logger)
def get_kurtosis(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get kurtosis for portfolio and benchmark selected

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame with kurtosis for portfolio and benchmark for different periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.kurtosis(p)
    """

    vals = list()
    for period in PERIODS:
        vals.append(
            [
                round(
                    scipy.stats.kurtosis(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.returns, period
                        )
                    ),
                    3,
                ),
                round(
                    scipy.stats.skew(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.benchmark_returns, period
                        )
                    ),
                    3,
                ),
            ]
        )
    return pd.DataFrame(vals, index=PERIODS, columns=["Portfolio", "Benchmark"])


@log_start_end(log=logger)
def get_stats(portfolio_engine: PortfolioEngine, window: str = "all") -> pd.DataFrame:
    """Get stats for portfolio and benchmark selected based on a certain interval

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window : str
        interval to consider. Choices are: mtd, qtd, ytd, 3m, 6m, 1y, 3y, 5y, 10y, all

    Returns
    -------
    pd.DataFrame
        DataFrame with overall stats for portfolio and benchmark for a certain period
    """

    df = (
        portfolio_helper.filter_df_by_period(portfolio_engine.returns, window)
        .describe()
        .to_frame()
        .join(
            portfolio_helper.filter_df_by_period(
                portfolio_engine.benchmark_returns, window
            ).describe()
        )
    )
    df.columns = ["Portfolio", "Benchmark"]
    return df


@log_start_end(log=logger)
def get_volatility(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get volatility for portfolio and benchmark selected

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame with volatility for portfolio and benchmark for different periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.volatility(p)
    """

    vals = list()
    for period in PERIODS:
        port_rets = portfolio_helper.filter_df_by_period(
            portfolio_engine.returns, period
        )
        bench_rets = portfolio_helper.filter_df_by_period(
            portfolio_engine.benchmark_returns, period
        )
        vals.append(
            [
                round(
                    port_rets.std() * (len(port_rets) ** 0.5),
                    3,
                ),
                round(
                    bench_rets.std() * (len(bench_rets) ** 0.5),
                    3,
                ),
            ]
        )
    return pd.DataFrame(
        vals,
        index=PERIODS,
        columns=["Portfolio [%]", "Benchmark [%]"],
    )


@log_start_end(log=logger)
def get_sharpe_ratio(
    portfolio_engine: PortfolioEngine, risk_free_rate: float = 0
) -> pd.DataFrame:
    """Get sharpe ratio for portfolio and benchmark selected

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    risk_free_rate: float
        Risk free rate value

    Returns
    -------
    pd.DataFrame
        DataFrame with sharpe ratio for portfolio and benchmark for different periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.sharpe(p)
    """

    vals = list()
    for period in PERIODS:
        vals.append(
            [
                round(
                    metrics_model.sharpe_ratio(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.returns, period
                        ),
                        risk_free_rate,
                    ),
                    3,
                ),
                round(
                    metrics_model.sharpe_ratio(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.benchmark_returns, period
                        ),
                        risk_free_rate,
                    ),
                    3,
                ),
            ]
        )
    return pd.DataFrame(vals, index=PERIODS, columns=["Portfolio", "Benchmark"])


@log_start_end(log=logger)
def get_sortino_ratio(
    portfolio_engine: PortfolioEngine, risk_free_rate: float = 0
) -> pd.DataFrame:
    """Get sortino ratio for portfolio and benchmark selected

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    risk_free_rate: float
        Risk free rate value

    Returns
    -------
    pd.DataFrame
        DataFrame with sortino ratio for portfolio and benchmark for different periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.sortino(p)
    """

    vals = list()
    for period in PERIODS:
        vals.append(
            [
                round(
                    metrics_model.sortino_ratio(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.returns, period
                        ),
                        risk_free_rate,
                    ),
                    3,
                ),
                round(
                    metrics_model.sortino_ratio(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.benchmark_returns, period
                        ),
                        risk_free_rate,
                    ),
                    3,
                ),
            ]
        )
    return pd.DataFrame(vals, index=PERIODS, columns=["Portfolio", "Benchmark"])


@log_start_end(log=logger)
def get_maximum_drawdown_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get maximum drawdown ratio for portfolio and benchmark selected

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame with maximum drawdown for portfolio and benchmark for different periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.maxdrawdown(p)
    """

    vals = list()
    for period in PERIODS:
        vals.append(
            [
                round(
                    metrics_model.maximum_drawdown(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.returns, period
                        )
                    ),
                    3,
                ),
                round(
                    metrics_model.maximum_drawdown(
                        portfolio_helper.filter_df_by_period(
                            portfolio_engine.benchmark_returns, period
                        )
                    ),
                    3,
                ),
            ]
        )
    return pd.DataFrame(vals, index=PERIODS, columns=["Portfolio", "Benchmark"])


@log_start_end(log=logger)
def get_gaintopain_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get Pain-to-Gain ratio based on historical data

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame of the portfolio's gain-to-pain ratio

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.gaintopain(p)
    """

    gtp_period_df = metrics_model.get_gaintopain_ratio(
        portfolio_engine.historical_trade_data,
        portfolio_engine.benchmark_trades,
        portfolio_engine.benchmark_returns,
    )

    return gtp_period_df


@log_start_end(log=logger)
def get_tracking_error(
    portfolio_engine: PortfolioEngine, window: int = 252
) -> Tuple[pd.DataFrame, pd.Series]:
    """Get tracking error

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window: int
        Interval used for rolling values

    Returns
    -------
    pd.DataFrame
        DataFrame of tracking errors during different time windows
    pd.Series
        Series of rolling tracking error

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.trackerr(p)
    """

    trackr_period_df, trackr_rolling = metrics_model.get_tracking_error(
        portfolio_engine.returns, portfolio_engine.benchmark_returns, window
    )

    return trackr_period_df, trackr_rolling


@log_start_end(log=logger)
def get_information_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get information ratio

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame of the information ratio during different time periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.information(p)
    """

    ir_period_df = metrics_model.get_information_ratio(
        portfolio_engine.returns,
        portfolio_engine.historical_trade_data,
        portfolio_engine.benchmark_trades,
        portfolio_engine.benchmark_returns,
    )

    return ir_period_df


@log_start_end(log=logger)
def get_tail_ratio(
    portfolio_engine: PortfolioEngine, window: int = 252
) -> Tuple[pd.DataFrame, pd.Series, pd.Series]:
    """Get tail ratio

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window: int
        Interval used for rolling values

    Returns
    -------
    pd.DataFrame
        DataFrame of the portfolios and the benchmarks tail ratio during different time windows
    pd.Series
        Series of the portfolios rolling tail ratio
    pd.Series
        Series of the benchmarks rolling tail ratio

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.tail(p)
    """

    tailr_period_df, portfolio_tr, benchmark_tr = metrics_model.get_tail_ratio(
        portfolio_engine.returns, portfolio_engine.benchmark_returns, window
    )

    return tailr_period_df, portfolio_tr, benchmark_tr


@log_start_end(log=logger)
def get_common_sense_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get common sense ratio

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame of the portfolios and the benchmarks common sense ratio during different time periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.commonsense(p)
    """

    csr_period_df = metrics_model.get_common_sense_ratio(
        portfolio_engine.returns,
        portfolio_engine.historical_trade_data,
        portfolio_engine.benchmark_trades,
        portfolio_engine.benchmark_returns,
    )

    return csr_period_df


@log_start_end(log=logger)
def get_jensens_alpha(
    portfolio_engine: PortfolioEngine, risk_free_rate: float = 0, window: str = "1y"
) -> Tuple[pd.DataFrame, pd.Series]:
    """Get jensen's alpha

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window: str
        Interval used for rolling values
    risk_free_rate: float
        Risk free rate

    Returns
    -------
    pd.DataFrame
        DataFrame of jensens's alpha during different time windows
    pd.Series
        Series of jensens's alpha data

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.jensens(p)
    """

    ja_period_df, ja_rolling = metrics_model.jensens_alpha(
        portfolio_engine.returns,
        portfolio_engine.historical_trade_data,
        portfolio_engine.benchmark_trades,
        portfolio_engine.benchmark_returns,
        risk_free_rate,
        window,
    )

    return ja_period_df, ja_rolling


@log_start_end(log=logger)
def get_calmar_ratio(
    portfolio_engine: PortfolioEngine, window: int = 756
) -> Tuple[pd.DataFrame, pd.Series]:
    """Get calmar ratio

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    window: int
        Interval used for rolling values

    Returns
    -------
    pd.DataFrame
        DataFrame of calmar ratio of the benchmark and portfolio during different time periods
    pd.Series
        Series of calmar ratio data

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.calmar(p)
    """

    cr_period_df, cr_rolling = metrics_model.get_calmar_ratio(
        portfolio_engine.returns,
        portfolio_engine.historical_trade_data,
        portfolio_engine.benchmark_trades,
        portfolio_engine.benchmark_returns,
        window,
    )

    return cr_period_df, cr_rolling


@log_start_end(log=logger)
def get_kelly_criterion(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get kelly criterion

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame of kelly criterion of the portfolio during different time periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.kelly(p)
    """

    kc_period_df = metrics_model.get_kelly_criterion(
        portfolio_engine.returns, portfolio_engine.portfolio_trades
    )

    return kc_period_df


@log_start_end(log=logger)
def get_payoff_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get payoff ratio

    Returns
    -------
    pd.DataFrame
        DataFrame of payoff ratio of the portfolio during different time periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.payoff(p)
    During some time periods there were no losing trades. Thus some values could not be calculated.
    """

    pr_period_ratio = metrics_model.get_payoff_ratio(portfolio_engine.portfolio_trades)

    return pr_period_ratio


@log_start_end(log=logger)
def get_profit_factor(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
    """Get profit factor

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.

    Returns
    -------
    pd.DataFrame
        DataFrame of profit factor of the portfolio during different time periods

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.metric.profitfactor(p)
    During some time periods there were no losing trades. Thus some values could not be calculated.
    """

    pf_period_df = metrics_model.get_profit_factor(portfolio_engine.portfolio_trades)

    return pf_period_df


@log_start_end(log=logger)
def get_performance_vs_benchmark(
    portfolio_engine: PortfolioEngine,
    show_all_trades: bool = False,
) -> pd.DataFrame:
    """Get portfolio performance vs the benchmark

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    show_all_trades: bool
        Whether to also show all trades made and their performance (default is False)

    Returns
    -------
    pd.DataFrame
        DataFrame with portfolio performance vs the benchmark

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.perf(p)
    """

    portfolio_trades = portfolio_engine.portfolio_trades
    benchmark_trades = portfolio_engine.benchmark_trades

    portfolio_trades.index = pd.to_datetime(portfolio_trades["Date"].values)
    benchmark_trades.index = pd.to_datetime(benchmark_trades["Date"].values)

    if show_all_trades:
        # Combine DataFrames
        combined = pd.concat(
            [
                portfolio_trades[
                    ["Date", "Ticker", "Portfolio Value", "Portfolio % Return"]
                ],
                benchmark_trades[["Benchmark Value", "Benchmark % Return"]],
            ],
            axis=1,
        )

        # Calculate alpha
        combined["Alpha"] = (
            combined["Portfolio % Return"] - combined["Benchmark % Return"]
        )

        combined["Date"] = pd.to_datetime(combined["Date"]).dt.date

        return combined

    # Calculate total value and return
    total_investment_difference = (
        portfolio_trades["Portfolio Investment"].sum()
        - benchmark_trades["Benchmark Investment"].sum()
    )
    total_value_difference = (
        portfolio_trades["Portfolio Value"].sum()
        - benchmark_trades["Benchmark Value"].sum()
    )
    total_portfolio_return = (
        portfolio_trades["Portfolio Value"].sum()
        / portfolio_trades["Portfolio Investment"].sum()
    ) - 1
    total_benchmark_return = (
        benchmark_trades["Benchmark Value"].sum()
        / benchmark_trades["Benchmark Investment"].sum()
    ) - 1
    total_abs_return_difference = (
        portfolio_trades["Portfolio Value"].sum()
        - portfolio_trades["Portfolio Investment"].sum()
    ) - (
        benchmark_trades["Benchmark Value"].sum()
        - benchmark_trades["Benchmark Investment"].sum()
    )

    totals = pd.DataFrame.from_dict(
        {
            "Total Investment": [
                portfolio_trades["Portfolio Investment"].sum(),
                benchmark_trades["Benchmark Investment"].sum(),
                total_investment_difference,
            ],
            "Total Value": [
                portfolio_trades["Portfolio Value"].sum(),
                benchmark_trades["Benchmark Value"].sum(),
                total_value_difference,
            ],
            "Total % Return": [
                f"{total_portfolio_return:.2%}",
                f"{total_benchmark_return:.2%}",
                f"{total_portfolio_return - total_benchmark_return:.2%}",
            ],
            "Total Abs Return": [
                portfolio_trades["Portfolio Value"].sum()
                - portfolio_trades["Portfolio Investment"].sum(),
                benchmark_trades["Benchmark Value"].sum()
                - benchmark_trades["Benchmark Investment"].sum(),
                total_abs_return_difference,
            ],
        },
        orient="index",
        columns=["Portfolio", "Benchmark", "Difference"],
    )

    return totals.replace(0, "-")


@log_start_end(log=logger)
def get_var(
    portfolio_engine: PortfolioEngine,
    use_mean: bool = False,
    adjusted_var: bool = False,
    student_t: bool = False,
    percentile: float = 99.9,
) -> pd.DataFrame:
    """Get portfolio VaR

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    use_mean: bool
        if one should use the data mean return
    adjusted_var: bool
        if one should have VaR adjusted for skew and kurtosis (Cornish-Fisher-Expansion)
    student_t: bool
        If one should use the student-t distribution
    percentile: float
        var percentile (%)

    Returns
    -------
    pd.DataFrame
        DataFrame with portfolio VaR

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.var(p)
    """

    return qa_model.get_var(
        data=portfolio_engine.returns,
        use_mean=use_mean,
        adjusted_var=adjusted_var,
        student_t=student_t,
        percentile=percentile,
        portfolio=True,
    )


@log_start_end(log=logger)
def get_es(
    portfolio_engine: PortfolioEngine,
    use_mean: bool = False,
    distribution: str = "normal",
    percentile: float = 99.9,
) -> pd.DataFrame:
    """Get portfolio expected shortfall

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    use_mean:
        if one should use the data mean return
    distribution: str
        choose distribution to use: logistic, laplace, normal
    percentile: float
        es percentile (%)

    Returns
    -------
    pd.DataFrame
        DataFrame with portfolio expected shortfall

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.es(p)
    """

    return qa_model.get_es(
        data=portfolio_engine.returns,
        use_mean=use_mean,
        distribution=distribution,
        percentile=percentile,
        portfolio=True,
    )


@log_start_end(log=logger)
def get_omega(
    portfolio_engine: PortfolioEngine,
    threshold_start: float = 0,
    threshold_end: float = 1.5,
) -> pd.DataFrame:
    """Get omega ratio

    Parameters
    ----------
    portfolio_engine: PortfolioEngine
        PortfolioEngine class instance, this will hold transactions and perform calculations.
        Use `portfolio.load` to create a PortfolioEngine.
    threshold_start: float
        annualized target return threshold start of plotted threshold range
    threshold_end: float
        annualized target return threshold end of plotted threshold range

    Returns
    -------
    pd.DataFrame
        DataFrame with portfolio omega ratio

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
    >>> output = openbb.portfolio.om(p)
    """

    return qa_model.get_omega(
        data=portfolio_engine.returns,
        threshold_start=threshold_start,
        threshold_end=threshold_end,
    )
