"""Portfolio Controller"""
__docformat__ = "numpy"

import argparse
import logging
import os
from datetime import date
from typing import List, Optional

import pandas as pd

from openbb_terminal.common.quantitative_analysis import qa_view
from openbb_terminal.core.config.paths import MISCELLANEOUS_DIRECTORY
from openbb_terminal.core.session.current_user import get_current_user
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import (
    EXPORT_BOTH_RAW_DATA_AND_FIGURES,
    EXPORT_ONLY_FIGURES_ALLOWED,
    EXPORT_ONLY_RAW_DATA_ALLOWED,
)
from openbb_terminal.menu import session
from openbb_terminal.parent_classes import BaseController
from openbb_terminal.portfolio import (
    attribution_model,
    portfolio_helper,
    portfolio_view,
    statics,
)
from openbb_terminal.portfolio.portfolio_model import generate_portfolio
from openbb_terminal.rich_config import MenuText, console

try:
    from openbb_terminal.portfolio.portfolio_optimization import po_controller

    OPTIMIZATION_TOOLKIT_ENABLED = True
except ModuleNotFoundError:
    OPTIMIZATION_TOOLKIT_ENABLED = False
    console.print(
        "[yellow]"
        "Portfolio Optimization Toolkit is disabled. "
        "To use the Optimization features please install the toolkit following the "
        "instructions here: https://github.com/OpenBB-finance/OpenBBTerminal/"
        "blob/main/openbb_terminal/README.md#anaconda--python"
        "\n"
        "[/yellow]"
    )

# pylint: disable=R1710,E1101,C0415,W0212,too-many-function-args,C0302,too-many-instance-attributes

logger = logging.getLogger(__name__)


class PortfolioController(BaseController):
    """Portfolio Controller class"""

    CHOICES_COMMANDS = [
        "load",
        "show",
        "bench",
        "alloc",
        "attrib",
        "perf",
        "yret",
        "mret",
        "dret",
        "distr",
        "holdv",
        "holdp",
        "maxdd",
        "var",
        "es",
        "om",
        "rvol",
        "rsharpe",
        "rsort",
        "rbeta",
        "metric",
        "summary",
    ]
    CHOICES_MENUS = [
        "bro",
        "po",
    ]
    VALID_DISTRIBUTIONS = ["laplace", "student_t", "logistic", "normal"]
    AGGREGATION_METRICS = ["assets", "sectors", "countries", "regions"]
    VALID_METRICS = [
        "volatility",
        "sharpe",
        "sortino",
        "maxdrawdown",
        "rsquare",
        "skew",
        "kurtosis",
        "gaintopain",
        "trackerr",
        "information",
        "tail",
        "commonsense",
        "jensens",
        "calmar",
        "kelly",
        "payoff",
        "profitfactor",
    ]
    PERIODS = ["3y", "5y", "10y", "all"]
    PATH = "/portfolio/"
    CHOICES_GENERATION = True

    def __init__(self, queue: Optional[List[str]] = None):
        """Constructor"""
        super().__init__(queue)
        self.file_types = ["xlsx", "csv"]

        self.DEFAULT_HOLDINGS_PATH = portfolio_helper.DEFAULT_HOLDINGS_PATH

        self.DATA_HOLDINGS_FILES = {
            filepath.name: filepath
            for file_type in self.file_types
            for filepath in self.DEFAULT_HOLDINGS_PATH.rglob(f"*.{file_type}")
        }

        self.portfolio_df = pd.DataFrame(
            columns=[
                "Date",
                "Name",
                "Type",
                "Sector",
                "Industry",
                "Country",
                "Price",
                "Quantity",
                "Fees",
                "Premium",
                "Investment",
                "Side",
                "Currency",
            ]
        )

        self.portfolio_name: str = ""
        self.benchmark_name: str = ""
        self.original_benchmark_ticker = ""
        self.recalculate_alloc = False
        self.risk_free_rate = 0
        self.portlist: List[str] = os.listdir(self.DEFAULT_HOLDINGS_PATH)
        self.portfolio = None

        if session and get_current_user().preferences.USE_PROMPT_TOOLKIT:
            self.update_choices()
            choices: dict = self.choices_default
            self.choices = choices
            self.choices["bench"] = {
                "--benchmark": {c: None for c in statics.BENCHMARK_CHOICES},
                "-b": "--benchmark",
            }
            self.completer = NestedCompleter.from_nested_dict(choices)

    def update_choices(self):
        self.DEFAULT_HOLDINGS_PATH = portfolio_helper.DEFAULT_HOLDINGS_PATH

        self.DATA_HOLDINGS_FILES.update(
            {
                filepath.name: filepath
                for file_type in self.file_types
                for filepath in self.DEFAULT_HOLDINGS_PATH.rglob(f"*.{file_type}")
            }
        )

    def print_help(self):
        """Print help"""
        mt = MenuText("portfolio/")
        mt.add_menu("bro")
        mt.add_menu("po")
        mt.add_raw("\n")

        mt.add_cmd("load")
        mt.add_cmd("show")
        mt.add_cmd("bench")
        mt.add_raw("\n")
        mt.add_param("_loaded", self.portfolio_name)
        mt.add_param("_riskfreerate", f"{self.risk_free_rate}%")
        mt.add_param("_benchmark", self.benchmark_name)
        mt.add_raw("\n")

        mt.add_info("_graphs_")
        mt.add_cmd("holdv", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("holdp", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("yret", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("mret", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("dret", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("distr", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("maxdd", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("rvol", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("rsharpe", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("rsort", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("rbeta", self.portfolio_name and self.benchmark_name)

        mt.add_info("_metrics_")
        mt.add_cmd("summary", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("alloc", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("attrib", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("metric", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("perf", self.portfolio_name and self.benchmark_name)

        mt.add_info("_risk_")
        mt.add_cmd("var", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("es", self.portfolio_name and self.benchmark_name)
        mt.add_cmd("om", self.portfolio_name and self.benchmark_name)

        console.print(text=mt.menu_text, menu="Portfolio - Portfolio Optimization")
        self.update_choices()

    def custom_reset(self):
        """Class specific component of reset command"""
        objects_to_reload = ["portfolio"]
        if self.portfolio_name:
            objects_to_reload.append(f"load {self.portfolio_name}")
        if self.original_benchmark_ticker:
            objects_to_reload.append(f'bench "{self.original_benchmark_ticker}"')
        return objects_to_reload

    @log_start_end(log=logger)
    def call_bro(self, _):
        """Process bro command"""
        from openbb_terminal.portfolio.brokers.bro_controller import BrokersController

        self.queue = self.load_class(BrokersController, self.queue)

    @log_start_end(log=logger)
    def call_po(self, _):
        """Process po command"""
        if OPTIMIZATION_TOOLKIT_ENABLED:
            if self.portfolio is None:
                tickers = []
                categories = None
            else:
                tickers = self.portfolio.tickers_list
                transactions = self.portfolio.get_transactions()

                categories = {
                    "ASSET_CLASS": {},
                    "SECTOR": {},
                    "INDUSTRY": {},
                    "COUNTRY": {},
                    "CURRENT_INVESTED_AMOUNT": {},
                    "CURRENCY": {},
                }
                for _, transaction in transactions.iterrows():
                    if transaction["Ticker"] not in categories["ASSET_CLASS"]:
                        categories["ASSET_CLASS"][transaction["Ticker"]] = transaction[
                            "Type"
                        ]
                        categories["SECTOR"][transaction["Ticker"]] = transaction[
                            "Sector"
                        ]
                        categories["INDUSTRY"][transaction["Ticker"]] = transaction[
                            "Industry"
                        ]
                        categories["COUNTRY"][transaction["Ticker"]] = transaction[
                            "Country"
                        ]
                        categories["CURRENT_INVESTED_AMOUNT"][
                            transaction["Ticker"]
                        ] = transactions[
                            transactions["Ticker"] == transaction["Ticker"]
                        ][
                            "Investment"
                        ].sum()
                        categories["CURRENCY"][transaction["Ticker"]] = transaction[
                            "Currency"
                        ]

            self.queue = self.load_class(
                po_controller.PortfolioOptimizationController,
                tickers,
                None,
                categories,
                self.queue,
            )
        else:
            console.print("[yellow]Portfolio Optimization Toolkit is disabled[/yellow]")

    @log_start_end(log=logger)
    def call_load(self, other_args: List[str]):
        """Process load command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="load",
            description="Load your portfolio transactions.",
        )
        parser.add_argument(
            "-f",
            "--file",
            type=str,
            dest="file",
            help="The file to be loaded",
            choices={c: {} for c in self.DATA_HOLDINGS_FILES},
            metavar="FILE",
        )
        parser.add_argument(
            "-n",
            "--name",
            type=str,
            dest="name",
            help="The name that you wish to give to your portfolio",
        )
        parser.add_argument(
            "-r",
            "--rfr",
            type=float,
            default=0,
            dest="risk_free_rate",
            help="Set the risk free rate.",
        )
        parser.add_argument(
            "-e",
            "--example",
            help="Run an example holdings file to understand how the portfolio menu can be used.",
            dest="example",
            action="store_true",
            default=False,
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-f")
        ns_parser = self.parse_known_args_and_warn(parser, other_args)

        if ns_parser and ns_parser.file or ns_parser and ns_parser.example:
            if ns_parser.example:
                file_location = (
                    MISCELLANEOUS_DIRECTORY / "portfolio" / "holdings_example.xlsx"
                )
                console.print(
                    "[green]Loading an example, please type `about` "
                    "to learn how to create your own Portfolio Excel sheet.[/green]\n"
                )
            elif ns_parser.file in self.DATA_HOLDINGS_FILES:
                file_location = self.DATA_HOLDINGS_FILES[ns_parser.file]
            else:
                file_location = ns_parser.file  # type: ignore

            self.portfolio = generate_portfolio(
                transactions_file_path=str(file_location),
                benchmark_symbol="SPY",
                risk_free_rate=ns_parser.risk_free_rate / 100,
            )

            if ns_parser.name:
                self.portfolio_name = ns_parser.name
            elif ns_parser.example:
                self.portfolio_name = "OpenBB Example Portfolio"
            else:
                self.portfolio_name = ns_parser.file
            console.print(
                f"\n[bold][param]Portfolio:[/param][/bold] {self.portfolio_name}"
            )

            self.risk_free_rate = ns_parser.risk_free_rate
            console.print(
                f"[bold][param]Risk Free Rate:[/param][/bold] {self.risk_free_rate}%"
            )

            self.benchmark_name = "SPDR S&P 500 ETF Trust (SPY)"
            console.print(
                f"[bold][param]Benchmark:[/param][/bold] {self.benchmark_name}"
            )

    @log_start_end(log=logger)
    def call_show(self, other_args: List[str]):
        """Process show command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="show",
            description="Show transactions table",
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES,
            limit=10,
        )
        if ns_parser and self.portfolio is not None:
            portfolio_view.display_transactions(
                self.portfolio,
                show_index=False,
                limit=ns_parser.limit,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_bench(self, other_args: List[str]):
        """Process bench command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="bench",
            description="Load in a benchmark from a selected list or set your own based on the ticker.",
        )
        parser.add_argument(
            "-b",
            "--benchmark",
            type=str,
            default="SPY",
            dest="benchmark",
            required="-h" not in other_args,
            help="Set the benchmark for the portfolio. By default, this is SPDR S&P 500 ETF Trust (SPY).",
            metavar="BENCHMARK",
        )
        parser.add_argument(
            "-s",
            "--full_shares",
            action="store_true",
            default=False,
            dest="full_shares",
            help="Whether to only make a trade with the benchmark when a full share can be bought (no partial shares).",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-b")
        ns_parser = self.parse_known_args_and_warn(parser, other_args)

        if ns_parser:
            if self.portfolio is None:
                console.print(
                    "[red]Please first load transactions file using 'load'[/red]"
                )
            else:
                if self.portfolio.set_benchmark(
                    ns_parser.benchmark, ns_parser.full_shares
                ):
                    self.benchmark_name = statics.BENCHMARK_CHOICES.get(
                        ns_parser.benchmark, ns_parser.benchmark
                    )
                    self.original_benchmark_ticker = ns_parser.benchmark
                    self.recalculate_alloc = True

    @log_start_end(log=logger)
    def call_alloc(self, other_args: List[str]):
        """Process alloc command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="alloc",
            description="""
                Show your allocation to each asset or sector compared to the benchmark.
            """,
        )
        parser.add_argument(
            "-a",
            "--agg",
            default="assets",
            choices=self.AGGREGATION_METRICS,
            dest="agg",
            help="The type of allocation aggregation you wish to do",
            metavar="AGG",
        )
        parser.add_argument(
            "-t",
            "--tables",
            action="store_true",
            default=False,
            dest="tables",
            help="Whether to also include the assets/sectors tables of both the benchmark and the portfolio.",
        )
        if other_args and other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-a")

        ns_parser = self.parse_known_args_and_warn(parser, other_args, limit=10)

        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            if ns_parser.agg == "assets":
                portfolio_view.display_assets_allocation(
                    portfolio_engine=self.portfolio,
                    limit=ns_parser.limit,
                    tables=ns_parser.tables,
                    recalculate=self.recalculate_alloc,
                )
            elif ns_parser.agg == "sectors":
                portfolio_view.display_sectors_allocation(
                    portfolio_engine=self.portfolio,
                    limit=ns_parser.limit,
                    tables=ns_parser.tables,
                    recalculate=self.recalculate_alloc,
                )
            elif ns_parser.agg == "countries":
                portfolio_view.display_countries_allocation(
                    portfolio_engine=self.portfolio,
                    limit=ns_parser.limit,
                    tables=ns_parser.tables,
                    recalculate=self.recalculate_alloc,
                )
            elif ns_parser.agg == "regions":
                portfolio_view.display_regions_allocation(
                    portfolio_engine=self.portfolio,
                    limit=ns_parser.limit,
                    tables=ns_parser.tables,
                    recalculate=self.recalculate_alloc,
                )
            else:
                console.print(
                    f"{ns_parser.agg} is not an available option. The options "
                    f"are: {', '.join(self.AGGREGATION_METRICS)}"
                )

    @log_start_end(log=logger)
    def call_attrib(self, other_args: List[str]):
        """Process attrib command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="attrib",
            description="""
                Displays sector attribution of the portfolio compared to the S&P 500
                """,
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            choices=statics.PERIODS,
            dest="period",
            default="all",
            help="Period in which to calculate attribution",
        )
        parser.add_argument(
            "-t",
            "--type",
            type=str,
            choices=["relative", "absolute"],
            dest="type",
            default="relative",
            help="Select between relative or absolute attribution values",
        )
        parser.add_argument(
            "--raw",
            type=bool,
            dest="raw",
            default=False,
            const=True,
            nargs="?",
            help="View raw attribution values in a table",
        )

        if other_args and other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-a")

        ns_parser = self.parse_known_args_and_warn(parser, other_args, limit=10)

        if ns_parser and self.portfolio is not None:
            if check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            ):
                if self.benchmark_name != "SPDR S&P 500 ETF Trust (SPY)":
                    print(
                        "This feature uses S&P 500 as benchmark and will disregard selected benchmark if different."
                    )
                # sector contribution
                end_date = date.today()
                # set correct time period
                if ns_parser.period == "all":
                    start_date = self.portfolio.inception_date
                else:
                    start_date = portfolio_helper.get_start_date_from_period(
                        ns_parser.period
                    )

                # calculate benchmark and portfolio contribution values
                bench_result = attribution_model.get_spy_sector_contributions(
                    start_date, end_date
                )
                if bench_result.empty:
                    return
                portfolio_result = attribution_model.get_portfolio_sector_contributions(
                    start_date, self.portfolio.portfolio_trades
                )
                if portfolio_result.empty:
                    return

                # relative results - the proportions of return attribution
                if ns_parser.type == "relative":
                    categorization_result = (
                        attribution_model.percentage_attrib_categorizer(
                            bench_result, portfolio_result
                        )
                    )

                    portfolio_view.display_attribution_categorization(
                        display=categorization_result,
                        time_period=ns_parser.period,
                        attrib_type="Contributions as % of PF",
                        plot_fields=["S&P500 [%]", "Portfolio [%]"],
                        show_table=ns_parser.raw,
                    )

                # absolute - the raw values of return attribution
                if ns_parser.type == "absolute":
                    categorization_result = attribution_model.raw_attrib_categorizer(
                        bench_result, portfolio_result
                    )

                    portfolio_view.display_attribution_categorization(
                        display=categorization_result,
                        time_period=ns_parser.period,
                        attrib_type="Raw contributions (Return x PF Weight)",
                        plot_fields=["S&P500", "Portfolio"],
                        show_table=ns_parser.raw,
                    )

            console.print()

    @log_start_end(log=logger)
    def call_perf(self, other_args: List[str]):
        """Process performance command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="performance",
            description="""
                Shows performance of each trade and total performance of the portfolio versus the benchmark.
            """,
        )
        parser.add_argument(
            "-t",
            "--show_trades",
            action="store_true",
            default=False,
            dest="show_trades",
            help="Whether to show performance on all trades in comparison to the benchmark.",
        )

        ns_parser = self.parse_known_args_and_warn(parser, other_args)

        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_performance_vs_benchmark(
                self.portfolio,
                ns_parser.show_trades,
            )

    @log_start_end(log=logger)
    def call_holdv(self, other_args: List[str]):
        """Process holdv command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="holdv",
            description="Display holdings of assets (absolute value)",
        )
        parser.add_argument(
            "-u",
            "--unstack",
            action="store_true",
            default=False,
            dest="unstack",
            help="Sum all assets value over time",
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES,
            raw=True,
            limit=10,
        )
        if ns_parser and check_portfolio_benchmark_defined(
            self.portfolio_name, self.benchmark_name
        ):
            portfolio_view.display_holdings_value(
                self.portfolio,
                ns_parser.unstack,
                ns_parser.raw,
                ns_parser.limit,
                ns_parser.export,
                ns_parser.sheet_name,
            )

    @log_start_end(log=logger)
    def call_holdp(self, other_args: List[str]):
        """Process holdp command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="holdp",
            description="Display holdings of assets (in percentage)",
        )
        parser.add_argument(
            "-u",
            "--unstack",
            action="store_true",
            default=False,
            dest="unstack",
            help="Sum all assets percentage over time",
        )
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES,
            raw=True,
            limit=10,
        )
        if ns_parser and check_portfolio_benchmark_defined(
            self.portfolio_name, self.benchmark_name
        ):
            portfolio_view.display_holdings_percentage(
                self.portfolio,
                ns_parser.unstack,
                ns_parser.raw,
                ns_parser.limit,
                ns_parser.export,
                ns_parser.sheet_name,
            )

    @log_start_end(log=logger)
    def call_var(self, other_args: List[str]):
        """Process var command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="var",
            description="""
                Provides value at risk (short: VaR) of the selected portfolio.
            """,
        )
        parser.add_argument(
            "-m",
            "--mean",
            action="store_true",
            default=True,
            dest="use_mean",
            help="If one should use the mean of the portfolio return",
        )
        parser.add_argument(
            "-a",
            "--adjusted",
            action="store_true",
            default=False,
            dest="adjusted",
            help="""
                If the VaR should be adjusted for skew and kurtosis (Cornish-Fisher-Expansion)
            """,
        )
        parser.add_argument(
            "-s",
            "--student",
            action="store_true",
            default=False,
            dest="student_t",
            help="""
                If one should use the student-t distribution
            """,
        )
        parser.add_argument(
            "-p",
            "--percentile",
            action="store",
            dest="percentile",
            type=float,
            default=99.9,
            help="""
                Percentile used for VaR calculations, for example input 99.9 equals a 99.9 Percent VaR
            """,
        )

        ns_parser = self.parse_known_args_and_warn(parser, other_args)

        if ns_parser and self.portfolio is not None:
            if self.portfolio_name:
                if ns_parser.adjusted and ns_parser.student_t:
                    console.print(
                        "Select either the adjusted or the student_t parameter.\n"
                    )
                else:
                    portfolio_view.display_var(
                        portfolio_engine=self.portfolio,
                        use_mean=ns_parser.use_mean,
                        adjusted_var=ns_parser.adjusted,
                        student_t=ns_parser.student_t,
                        percentile=ns_parser.percentile,
                    )
            else:
                console.print(
                    "[red]Please first define the portfolio using 'load'[/red]\n"
                )

    @log_start_end(log=logger)
    def call_es(self, other_args: List[str]):
        """Process es command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="es",
            description="""
                Provides Expected Shortfall (short: ES) of the selected portfolio.
            """,
        )
        parser.add_argument(
            "-m",
            "--mean",
            action="store_true",
            default=True,
            dest="use_mean",
            help="If one should use the mean of the portfolios return",
        )
        parser.add_argument(
            "-d",
            "--dist",
            dest="distribution",
            type=str,
            choices=self.VALID_DISTRIBUTIONS,
            default="normal",
            help="Distribution used for the calculations",
            metavar="DIST",
        )
        parser.add_argument(
            "-p",
            "--percentile",
            action="store",
            dest="percentile",
            type=float,
            default=99.9,
            help="""
                Percentile used for ES calculations, for example input 99.9 equals a 99.9 Percent Expected Shortfall
            """,
        )

        ns_parser = self.parse_known_args_and_warn(parser, other_args)

        if ns_parser:
            if self.portfolio and self.portfolio_name:
                portfolio_view.display_es(
                    portfolio_engine=self.portfolio,
                    use_mean=ns_parser.use_mean,
                    distribution=ns_parser.distribution,
                    percentile=ns_parser.percentile,
                )
            else:
                console.print(
                    "[red]Please first define the portfolio using 'load'[/red]\n"
                )

    @log_start_end(log=logger)
    def call_om(self, other_args: List[str]):
        """Process om command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="om",
            description="""
                   Provides omega ratio of the selected portfolio.
               """,
        )
        parser.add_argument(
            "-s",
            "--start",
            action="store",
            dest="start",
            type=float,
            default=0,
            help="""
                   Start of the omega ratio threshold
               """,
        )
        parser.add_argument(
            "-e",
            "--end",
            action="store",
            dest="end",
            type=float,
            default=1.5,
            help="""
                   End of the omega ratio threshold
               """,
        )
        ns_parser = self.parse_known_args_and_warn(parser, other_args)
        if ns_parser and self.portfolio is not None:
            if self.portfolio_name:
                data = self.portfolio.portfolio_returns[1:]
                qa_view.display_omega(
                    data,
                    ns_parser.start,
                    ns_parser.end,
                )
            else:
                console.print(
                    "[red]Please define the portfolio first (via 'load')[/red]\n"
                )

    @log_start_end(log=logger)
    def call_yret(self, other_args: List[str]):
        """Process yret command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="yret",
            description="End of the year returns",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            dest="period",
            default="all",
            choices=self.PERIODS,
            help="Period to select start end of the year returns",
            metavar="PERIOD",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            raw=True,
            export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES,
        )

        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_yearly_returns(
                self.portfolio,
                ns_parser.period,
                ns_parser.raw,
                ns_parser.export,
                ns_parser.sheet_name,
            )

    @log_start_end(log=logger)
    def call_mret(self, other_args: List[str]):
        """Process mret command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="mret",
            description="Monthly returns",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            dest="period",
            default="all",
            choices=self.PERIODS,
            help="Period to select start end of the year returns",
            metavar="PERIOD",
        )
        parser.add_argument(
            "-i",
            "--instrument",
            type=str,
            dest="instrument",
            default="both",
            choices=["both", "portfolio", "benchmark"],
            help="Whether to show portfolio or benchmark monthly returns. By default both are shown in one table.",
        )
        parser.add_argument(
            "-g",
            "--graph",
            action="store_true",
            default=False,
            dest="graph",
            help="Plot the monthly returns on a heatmap",
        )
        parser.add_argument(
            "-s",
            "--show",
            action="store_true",
            default=False,
            dest="show_vals",
            help="Show monthly returns on heatmap",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            raw=True,
            export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED,
        )

        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_monthly_returns(
                self.portfolio,
                ns_parser.period,
                ns_parser.instrument,
                ns_parser.graph,
                ns_parser.show_vals,
                ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_dret(self, other_args: List[str]):
        """Process dret command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="dret",
            description="Daily returns",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            dest="period",
            default="all",
            choices=self.PERIODS,
            help="Period to select start end of the year returns",
            metavar="PERIOD",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")
        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            raw=True,
            limit=10,
            export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES,
        )

        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_daily_returns(
                self.portfolio,
                ns_parser.period,
                ns_parser.raw,
                ns_parser.limit,
                ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_maxdd(self, other_args: List[str]):
        """Process maxdd command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="maxdd",
            description="Show portfolio maximum drawdown",
        )
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
        )
        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_maximum_drawdown(
                self.portfolio,
                export=ns_parser.export,
            )

    @log_start_end(log=logger)
    def call_rvol(self, other_args: List[str]):
        """Process rolling volatility command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="rvol",
            description="Show rolling volatility portfolio vs benchmark",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            dest="period",
            default="1y",
            choices=statics.PERIODS,
            help="Period to apply rolling window",
            metavar="PERIOD",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
        )
        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_rolling_volatility(
                self.portfolio,
                window=ns_parser.period,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_rsharpe(self, other_args: List[str]):
        """Process rolling sharpe command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="rsharpe",
            description="Show rolling sharpe portfolio vs benchmark",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            dest="period",
            default="1y",
            choices=statics.PERIODS,
            help="Period to apply rolling window",
            metavar="PERIOD",
        )
        parser.add_argument(
            "-r",
            "--rfr",
            type=float,
            dest="risk_free_rate",
            default=self.risk_free_rate,
            help="Set risk free rate for calculations.",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
        )
        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_rolling_sharpe(
                self.portfolio,
                risk_free_rate=ns_parser.risk_free_rate / 100,
                window=ns_parser.period,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_rsort(self, other_args: List[str]):
        """Process rolling sortino command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="rsort",
            description="Show rolling sortino portfolio vs benchmark",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            dest="period",
            default="1y",
            choices=statics.PERIODS,
            help="Period to apply rolling window",
            metavar="PERIOD",
        )
        parser.add_argument(
            "-r",
            "--rfr",
            type=float,
            dest="risk_free_rate",
            default=self.risk_free_rate,
            help="Set risk free rate for calculations.",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
        )
        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_rolling_sortino(
                portfolio_engine=self.portfolio,
                risk_free_rate=ns_parser.risk_free_rate / 100,
                window=ns_parser.period,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_rbeta(self, other_args: List[str]):
        """Process rolling beta command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="rbeta",
            description="Show rolling beta portfolio vs benchmark",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            dest="period",
            default="1y",
            choices=statics.PERIODS,
            help="Period to apply rolling window",
            metavar="PERIOD",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
        )
        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_rolling_beta(
                self.portfolio,
                window=ns_parser.period,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_metric(self, other_args: List[str]):
        """Process metric command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="metric",
            description="Display metric of choice for different periods",
        )
        parser.add_argument(
            "-m",
            "--metric",
            type=str,
            dest="metric",
            default="-h" not in other_args,
            choices=self.VALID_METRICS,
            help="Set metric of choice",
            metavar="METRIC",
        )
        parser.add_argument(
            "-r",
            "--rfr",
            type=float,
            dest="risk_free_rate",
            default=self.risk_free_rate,
            help="Set risk free rate for calculations.",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-m")
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser and check_portfolio_benchmark_defined(
            self.portfolio_name, self.benchmark_name
        ):
            if ns_parser.metric == "skew":
                portfolio_view.display_skewness(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "kurtosis":
                portfolio_view.display_kurtosis(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "volatility":
                portfolio_view.display_volatility(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "sharpe":
                portfolio_view.display_sharpe_ratio(
                    self.portfolio,
                    ns_parser.risk_free_rate / 100,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "sortino":
                portfolio_view.display_sortino_ratio(
                    self.portfolio,
                    ns_parser.risk_free_rate / 100,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "maxdrawdown":
                portfolio_view.display_maximum_drawdown_ratio(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "rsquare":
                portfolio_view.display_rsquare(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "gaintopain":
                portfolio_view.display_gaintopain_ratio(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "trackerr":
                portfolio_view.display_tracking_error(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "information":
                portfolio_view.display_information_ratio(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "tail":
                portfolio_view.display_tail_ratio(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "commonsense":
                portfolio_view.display_common_sense_ratio(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "jensens":
                portfolio_view.display_jensens_alpha(
                    self.portfolio,
                    ns_parser.risk_free_rate / 100,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "calmar":
                portfolio_view.display_calmar_ratio(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "kelly":
                portfolio_view.display_kelly_criterion(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "payoff" and self.portfolio is not None:
                portfolio_view.display_payoff_ratio(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )
            elif ns_parser.metric == "profitfactor" and self.portfolio is not None:
                portfolio_view.display_profit_factor(
                    self.portfolio,
                    ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )

    @log_start_end(log=logger)
    def call_distr(self, other_args: List[str]):
        """Process distr command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="distr",
            description="Compute distribution of daily returns",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            choices=statics.PERIODS,
            dest="period",
            default="all",
            help="The file to be loaded",
            metavar="PERIOD",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")

        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            raw=True,
            export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES,
        )
        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_distribution_returns(
                self.portfolio,
                ns_parser.period,
                ns_parser.raw,
                ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_summary(self, other_args: List[str]):
        """Process summary command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="summary",
            description="Display summary of portfolio vs benchmark",
        )
        parser.add_argument(
            "-p",
            "--period",
            type=str,
            choices=statics.PERIODS,
            dest="period",
            default="all",
            help="The file to be loaded",
            metavar="PERIOD",
        )
        parser.add_argument(
            "-r",
            "--rfr",
            type=float,
            dest="risk_free_rate",
            default=self.risk_free_rate,
            help="Set risk free rate for calculations.",
        )
        if other_args and "-" not in other_args[0][0]:
            other_args.insert(0, "-p")

        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED,
        )
        if (
            ns_parser
            and self.portfolio is not None
            and check_portfolio_benchmark_defined(
                self.portfolio_name, self.benchmark_name
            )
        ):
            portfolio_view.display_summary(
                self.portfolio,
                ns_parser.period,
                ns_parser.risk_free_rate / 100,
                ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )


def check_portfolio_benchmark_defined(portfolio_name: str, benchmark_name: str) -> bool:
    """Check that portfolio and benchmark have been defined

    Parameters
    ----------
    portfolio_name: str
        Portfolio name, will be empty if not defined
    benchmark_name: str
        Benchmark name, will be empty if not defined

    Returns
    -------
    bool
        If both portfolio and benchmark have been defined
    """

    if not portfolio_name:
        console.print("[red]Please first define the portfolio (via 'load')[/red]\n")
        return False

    if not benchmark_name:
        console.print("[red]Please first define the benchmark (via 'bench')[/red]\n")
        return False

    return True
