"""Quantitative Analysis View"""
__docformat__ = "numpy"

# pylint: disable=too-many-lines


import logging
import os
import warnings
from datetime import datetime
from typing import Any, Optional, List

import matplotlib
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import numpy as np
import pandas as pd
import seaborn as sns
import statsmodels.api as sm
from scipy import stats
from detecta import detect_cusum
from statsmodels.graphics.gofplots import qqplot
from pandas.plotting import register_matplotlib_converters

from openbb_terminal.config_terminal import theme
from openbb_terminal.common.quantitative_analysis import qa_model
from openbb_terminal.config_plot import PLOT_DPI
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import (
    export_data,
    plot_autoscale,
    print_rich_table,
    reindex_dates,
    lambda_long_number_format,
    is_valid_axes_count,
)
from openbb_terminal.rich_config import console

logger = logging.getLogger(__name__)

register_matplotlib_converters()


def lambda_color_red(val: Any) -> str:
    """Adds red to dataframe value"""
    if val > 0.05:
        return f"[red]{round(val,4)}[/red]"
    return round(val, 4)


@log_start_end(log=logger)
def display_summary(data: pd.DataFrame, export: str = "") -> None:
    """Prints table showing summary statistics

    Parameters
    ----------
    data : pd.DataFrame
        DataFrame to get statistics of
    export : str
        Format to export data
    """
    summary = qa_model.get_summary(data)

    print_rich_table(
        summary,
        headers=list(summary.columns),
        floatfmt=".3f",
        show_index=True,
        title="[bold]Summary Statistics[/bold]",
    )

    export_data(
        export,
        os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"),
        "summary",
        summary,
    )


@log_start_end(log=logger)
def display_hist(
    data: pd.DataFrame,
    target: str,
    symbol: str = "",
    bins: int = 15,
    external_axes: Optional[List[plt.Axes]] = None,
) -> None:
    """Plots histogram of data

    Parameters
    ----------
    data : pd.DataFrame
        Dataframe to look at
    target : str
        Data column to get histogram of the dataframe
    symbol : str
        Name of dataset
    bins : int
        Number of bins in histogram
    external_axes : Optional[List[plt.Axes]], optional
        External axes (1 axis is expected in the list), by default None

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> df = openbb.stocks.load("AAPL")
    >>> openbb.qa.hist(data=df, target="Adj Close")
    """
    data = data[target]

    # This plot has 1 axis
    if external_axes is None:
        _, ax = plt.subplots(
            figsize=plot_autoscale(),
            dpi=PLOT_DPI,
        )
    elif is_valid_axes_count(external_axes, 1):
        (ax,) = external_axes
    else:
        return

    sns.histplot(
        data,
        color=theme.up_color,
        bins=bins,
        kde=True,
        ax=ax,
        stat="proportion",
        legend=True,
    )
    sns.rugplot(data, color=theme.down_color, ax=ax, legend=True)

    if isinstance(data.index[0], datetime):
        start = data.index[0]
        ax.set_title(
            f"Histogram of {symbol} {target} from {start.strftime('%Y-%m-%d')}"
        )
    else:
        ax.set_title(f"Histogram of {symbol} {target}")

    ax.set_xlabel("Value")
    theme.style_primary_axis(ax)

    # Manually construct the chart legend
    proportion_legend = mpatches.Patch(
        color=theme.up_color, label="Univariate distribution"
    )
    marginal_legend = mpatches.Patch(
        color=theme.down_color, label="Marginal distributions"
    )
    ax.legend(handles=[proportion_legend, marginal_legend])

    if external_axes is None:
        theme.visualize_output()


@log_start_end(log=logger)
def display_cdf(
    data: pd.DataFrame,
    target: str,
    symbol: str = "",
    export: str = "",
    external_axes: Optional[List[plt.Axes]] = None,
):
    """Plots Cumulative Distribution Function

    Parameters
    ----------
    data : pd.DataFrame
        Dataframe to look at
    target : str
        Data column
    symbol : str
        Name of dataset
    export : str
        Format to export data
    external_axes : Optional[List[plt.Axes]], optional
        External axes (1 axis is expected in the list), by default None

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> df = openbb.stocks.load("AAPL")
    >>> openbb.qa.cdf(data=df, target="Adj Close")
    """
    data = data[target]
    start = data.index[0]
    cdf = data.value_counts().sort_index().div(len(data)).cumsum()

    # This plot has 1 axis
    if external_axes is None:
        _, ax = plt.subplots(
            figsize=plot_autoscale(),
            dpi=PLOT_DPI,
        )
    elif is_valid_axes_count(external_axes, 1):
        (ax,) = external_axes
    else:
        return

    cdf.plot(ax=ax)
    ax.set_title(
        f"Cumulative Distribution Function of {symbol} {target}\nfrom {start.strftime('%Y-%m-%d')}"
    )
    ax.set_ylabel("Probability")
    ax.set_xlabel(target)

    minVal = data.values.min()
    q25 = np.quantile(data.values, 0.25)
    medianVal = np.quantile(data.values, 0.5)
    q75 = np.quantile(data.values, 0.75)
    labels = [
        (minVal, q25),
        (0.25, 0.25),
        theme.down_color,
        (q25, q25),
        (0, 0.25),
        theme.down_color,
        (minVal, medianVal),
        (0.5, 0.5),
        theme.down_color,
        (medianVal, medianVal),
        (0, 0.5),
        theme.down_color,
        (minVal, q75),
        (0.75, 0.75),
        theme.down_color,
        (q75, q75),
        (0, 0.75),
        theme.down_color,
    ]
    ax.plot(*labels, ls="--")
    ax.text(
        minVal + (q25 - minVal) / 2,
        0.27,
        "Q1",
        color=theme.down_color,
        fontweight="bold",
    )
    ax.text(
        minVal + (medianVal - minVal) / 2,
        0.52,
        "Median",
        color=theme.down_color,
        fontweight="bold",
    )
    ax.text(
        minVal + (q75 - minVal) / 2,
        0.77,
        "Q3",
        color=theme.down_color,
        fontweight="bold",
    )
    ax.set_xlim(cdf.index[0], cdf.index[-1])
    theme.style_primary_axis(ax)

    if external_axes is None:
        theme.visualize_output()

    export_data(
        export,
        os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"),
        "cdf",
        pd.DataFrame(cdf),
    )


@log_start_end(log=logger)
def display_bw(
    data: pd.DataFrame,
    target: str,
    symbol: str = "",
    yearly: bool = True,
    external_axes: Optional[List[plt.Axes]] = None,
) -> None:
    """Plots box and whisker plots

    Parameters
    ----------
    symbol : str
        Name of dataset
    data : pd.DataFrame
        Dataframe to look at
    target : str
        Data column to look at
    yearly : bool
        Flag to indicate yearly accumulation
    external_axes : Optional[List[plt.Axes]], optional
        External axes (1 axis is expected in the list), by default None

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> df = openbb.stocks.load("AAPL")
    >>> openbb.qa.bw(data=df, target="Adj Close")
    """
    data = data[target]
    start = data.index[0]

    # This plot has 1 axis
    if external_axes is None:
        _, ax = plt.subplots(
            figsize=plot_autoscale(),
            dpi=PLOT_DPI,
        )
    elif is_valid_axes_count(external_axes, 1):
        (ax,) = external_axes
    else:
        return

    theme.style_primary_axis(ax)
    color = theme.get_colors()[0]
    if yearly:
        x_data = data.index.year
    else:
        x_data = data.index.month
    box_plot = sns.boxplot(
        x=x_data,
        y=data,
        ax=ax,
        zorder=3,
        boxprops=dict(edgecolor=color),
        flierprops=dict(
            linestyle="--",
            color=color,
            markerfacecolor=theme.up_color,
            markeredgecolor=theme.up_color,
        ),
        whiskerprops=dict(color=color),
        capprops=dict(color=color),
    )

    box_plot.set(
        xlabel=["Monthly", "Yearly"][yearly],
        ylabel=target,
        title=f"{['Monthly','Yearly'][yearly]} box plot of {symbol} {target} from {start.strftime('%Y-%m-%d')}",
    )
    l_months = [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    ]
    l_ticks = list()
    if not yearly:
        for val in box_plot.get_xticklabels():
            l_ticks.append(l_months[int(val.get_text()) - 1])
        box_plot.set_xticklabels(l_ticks)

    # remove the scientific notion on the left hand side
    ax.ticklabel_format(style="plain", axis="y")
    ax.get_yaxis().set_major_formatter(
        matplotlib.ticker.FuncFormatter(lambda x, _: lambda_long_number_format(x))
    )

    theme.style_primary_axis(ax)

    if external_axes is None:
        theme.visualize_output()


@log_start_end(log=logger)
def display_acf(
    data: pd.DataFrame,
    target: str,
    symbol: str = "",
    lags: int = 15,
    external_axes: Optional[List[plt.Axes]] = None,
) -> None:
    """Plots Auto and Partial Auto Correlation of returns and change in returns

    Parameters
    ----------
    data : pd.DataFrame
        Dataframe to look at
    target : str
        Data column to look at
    symbol : str
        Name of dataset
    lags : int
        Max number of lags to look at
    external_axes : Optional[List[plt.Axes]], optional
        External axes (4 axes are expected in the list), by default None

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> df = openbb.stocks.load("AAPL")
    >>> openbb.qa.acf(data=df, target="Adj Close")
    """
    data = data[target]
    start = data.index[0]

    # This plot has 4 axes
    if external_axes is None:
        fig, axes = plt.subplots(
            nrows=2,
            ncols=2,
            figsize=plot_autoscale(),
            dpi=PLOT_DPI,
        )
        (ax1, ax2), (ax3, ax4) = axes
    elif is_valid_axes_count(external_axes, 4):
        (ax1, ax2, ax3, ax4) = external_axes
    else:
        return

    # Diff Auto - correlation function for original time series
    sm.graphics.tsa.plot_acf(np.diff(np.diff(data.values)), lags=lags, ax=ax1)
    ax1.set_title(f"{symbol} Returns Auto-Correlation", fontsize=9)
    # Diff Partial auto - correlation function for original time series
    sm.graphics.tsa.plot_pacf(
        np.diff(np.diff(data.values)), lags=lags, ax=ax2, method="ywm"
    )
    ax2.set_title(
        f"{symbol} Returns Partial Auto-Correlation",
        fontsize=9,
    )

    # Diff Diff Auto-correlation function for original time series
    sm.graphics.tsa.plot_acf(np.diff(np.diff(data.values)), lags=lags, ax=ax3)
    ax3.set_title(
        f"Change in {symbol} Returns Auto-Correlation",
        fontsize=9,
    )
    # Diff Diff Partial auto-correlation function for original time series
    sm.graphics.tsa.plot_pacf(
        np.diff(np.diff(data.values)), lags=lags, ax=ax4, method="ywm"
    )
    ax4.set_title(
        f"Change in {symbol} Returns Partial Auto-Correlation",
        fontsize=9,
    )

    fig.suptitle(
        f"ACF differentials starting from {start.strftime('%Y-%m-%d')}",
        fontsize=15,
        x=0.042,
        y=0.95,
        horizontalalignment="left",
        verticalalignment="top",
    )
    theme.style_primary_axis(ax1)
    theme.style_primary_axis(ax2)
    theme.style_primary_axis(ax3)
    theme.style_primary_axis(ax4)

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


@log_start_end(log=logger)
def display_qqplot(
    data: pd.DataFrame,
    target: str,
    symbol: str = "",
    external_axes: Optional[List[plt.Axes]] = None,
) -> None:
    """Plots QQ plot for data against normal quantiles

    Parameters
    ----------
    data : pd.DataFrame
        Dataframe
    target : str
        Column in data to look at
    symbol : str
        Stock ticker
    external_axes : Optional[List[plt.Axes]], optional
        External axes (1 axis is expected in the list), by default None

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> df = openbb.stocks.load("AAPL")
    >>> openbb.qa.qqplot(data=df, target="Adj Close")
    """
    # Statsmodels has a UserWarning for marker kwarg-- which we don't use
    warnings.filterwarnings(category=UserWarning, action="ignore")
    data = data[target]

    # This plot has 1 axis
    if external_axes is None:
        _, ax = plt.subplots(
            figsize=plot_autoscale(),
            dpi=PLOT_DPI,
        )
    elif is_valid_axes_count(external_axes, 1):
        (ax,) = external_axes
    else:
        return

    qqplot(
        data,
        stats.distributions.norm,
        fit=True,
        line="45",
        color=theme.down_color,
        ax=ax,
    )
    ax.get_lines()[1].set_color(theme.up_color)

    ax.set_title(f"Q-Q plot for {symbol} {target}")
    ax.set_ylabel("Sample quantiles")
    ax.set_xlabel("Theoretical quantiles")

    theme.style_primary_axis(ax)

    if external_axes is None:
        theme.visualize_output()


@log_start_end(log=logger)
def display_cusum(
    data: pd.DataFrame,
    target: str,
    threshold: float = 5,
    drift: float = 2.1,
    external_axes: Optional[List[plt.Axes]] = None,
):
    """Plots Cumulative sum algorithm (CUSUM) to detect abrupt changes in data

    Parameters
    ----------
    data : pd.DataFrame
        Dataframe
    target : str
        Column of data to look at
    threshold : float
        Threshold value
    drift : float
        Drift parameter
    external_axes : Optional[List[plt.Axes]], optional
        External axes (2 axes are expected in the list), by default None

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> df = openbb.stocks.load("AAPL")
    >>> openbb.qa.cusum(data=df, target="Adj Close")
    """
    target_series = data[target].values

    # The code for this plot was adapted from detecta's sources because at the
    # time of writing this detect_cusum had a bug related to external axes support.
    # see https:  // github.com/demotu/detecta/pull/3
    tap, tan = 0, 0
    ta, tai, taf, _ = detect_cusum(
        x=target_series,
        threshold=threshold,
        drift=drift,
        ending=True,
        show=False,
    )
    # Thus some variable names are left unchanged and unreadable...
    gp, gn = np.zeros(target_series.size), np.zeros(target_series.size)
    for i in range(1, target_series.size):
        s = target_series[i] - target_series[i - 1]
        gp[i] = gp[i - 1] + s - drift  # cumulative sum for + change
        gn[i] = gn[i - 1] - s - drift  # cumulative sum for - change
        if gp[i] < 0:
            gp[i], tap = 0, i
        if gn[i] < 0:
            gn[i], tan = 0, i
        if gp[i] > threshold or gn[i] > threshold:  # change detected!
            ta = np.append(ta, i)  # alarm index
            tai = np.append(tai, tap if gp[i] > threshold else tan)  # start
            gp[i], gn[i] = 0, 0  # reset alarm

    if external_axes is None:
        _, axes = plt.subplots(
            2,
            1,
            sharex=True,
            figsize=plot_autoscale(),
            dpi=PLOT_DPI,
        )
        (ax1, ax2) = axes
    elif is_valid_axes_count(external_axes, 2):
        (ax1, ax2) = external_axes
    else:
        return

    target_series_indexes = range(data[target].size)
    ax1.plot(target_series_indexes, target_series)
    if len(ta):
        ax1.plot(
            tai,
            target_series[tai],
            ">",
            markerfacecolor=theme.up_color,
            markersize=5,
            label="Start",
        )
        ax1.plot(
            taf,
            target_series[taf],
            "<",
            markerfacecolor=theme.down_color,
            markersize=5,
            label="Ending",
        )
        ax1.plot(
            ta,
            target_series[ta],
            "o",
            markerfacecolor=theme.get_colors()[-1],
            markeredgecolor=theme.get_colors()[-2],
            markeredgewidth=1,
            markersize=3,
            label="Alarm",
        )
        ax1.legend()
    ax1.set_xlim(-0.01 * target_series.size, target_series.size * 1.01 - 1)
    ax1.set_ylabel("Amplitude")
    ymin, ymax = (
        target_series[np.isfinite(target_series)].min(),
        target_series[np.isfinite(target_series)].max(),
    )
    y_range = ymax - ymin if ymax > ymin else 1
    ax1.set_ylim(ymin - 0.1 * y_range, ymax + 0.1 * y_range)
    ax1.set_title(
        "Time series and detected changes "
        + f"(threshold= {threshold:.3g}, drift= {drift:.3g}): N changes = {len(tai)}",
        fontsize=10,
    )
    theme.style_primary_axis(ax1)

    ax2.plot(target_series_indexes, gp, label="+")
    ax2.plot(target_series_indexes, gn, label="-")
    ax2.set_xlim(-0.01 * target_series.size, target_series.size * 1.01 - 1)
    ax2.set_xlabel("Data points")
    ax2.set_ylim(-0.01 * threshold, 1.1 * threshold)
    ax2.axhline(threshold)
    theme.style_primary_axis(ax2)

    ax2.set_title(
        "Time series of the cumulative sums of positive and negative changes",
        fontsize=10,
    )
    ax2.legend()

    if external_axes is None:
        theme.visualize_output()


@log_start_end(log=logger)
def display_seasonal(
    symbol: str,
    data: pd.DataFrame,
    target: str,
    multiplicative: bool = False,
    export: str = "",
    external_axes: Optional[List[plt.Axes]] = None,
) -> None:
    """Plots seasonal decomposition data

    Parameters
    ----------
    symbol : str
        Name of dataset
    data : pd.DataFrame
        DataFrame
    target : str
        Column of data to look at
    multiplicative : bool
        Boolean to indicate multiplication instead of addition
    export : str
        Format to export trend and cycle data
    external_axes : Optional[List[plt.Axes]], optional
        External axes (6 axes are expected in the list), by default None
    """
    data = data[target]
    result, cycle, trend = qa_model.get_seasonal_decomposition(data, multiplicative)

    plot_data = pd.merge(
        data,
        result.trend,
        how="outer",
        left_index=True,
        right_index=True,
        suffixes=("", "_result.trend"),
    )
    plot_data = pd.merge(
        plot_data,
        result.seasonal,
        how="outer",
        left_index=True,
        right_index=True,
        suffixes=("", "_result.seasonal"),
    )
    plot_data = pd.merge(
        plot_data,
        result.resid,
        how="outer",
        left_index=True,
        right_index=True,
        suffixes=("", "_result.resid"),
    )
    plot_data = pd.merge(
        plot_data,
        cycle,
        how="outer",
        left_index=True,
        right_index=True,
        suffixes=("", "_cycle"),
    )
    plot_data = pd.merge(
        plot_data,
        trend,
        how="outer",
        left_index=True,
        right_index=True,
        suffixes=("", "_trend"),
    )
    plot_data = reindex_dates(plot_data)

    # This plot has 1 axis
    if external_axes is None:
        fig, axes = plt.subplots(
            4,
            1,
            sharex=True,
            figsize=plot_autoscale(),
            dpi=PLOT_DPI,
        )
        (ax1, ax2, ax3, ax4) = axes
    elif is_valid_axes_count(external_axes, 4):
        (ax1, ax2, ax3, ax4) = external_axes
    else:
        return

    colors = iter(theme.get_colors())

    ax1.set_title(f"{symbol} (Time-Series) {target} seasonal decomposition")
    ax1.plot(
        plot_data.index, plot_data[target].values, color=next(colors), label="Values"
    )
    ax1.set_xlim([plot_data.index[0], plot_data.index[-1]])
    ax1.legend()

    # Multiplicative model
    ax2.plot(plot_data["trend"], color=theme.down_color, label="Cyclic-Trend")
    ax2.plot(
        plot_data["trend_cycle"],
        color=theme.up_color,
        linestyle="--",
        label="Cycle component",
    )
    ax2.legend()

    ax3.plot(plot_data["trend_trend"], color=next(colors), label="Trend component")
    ax3.plot(plot_data["seasonal"], color=next(colors), label="Seasonal effect")
    ax3.legend()

    ax4.plot(plot_data["resid"], color=next(colors), label="Residuals")
    ax4.legend()

    theme.style_primary_axis(ax1)
    theme.style_primary_axis(ax2)
    theme.style_primary_axis(ax3)
    theme.style_primary_axis(
        ax4,
        data_index=plot_data.index.to_list(),
        tick_labels=plot_data["date"].to_list(),
    )

    if external_axes is None:
        fig.tight_layout(pad=theme.tight_layout_padding)
        fig.subplots_adjust(
            hspace=0.1,
        )
        theme.visualize_output(force_tight_layout=False)

    # From #https:  // otexts.com/fpp2/seasonal-strength.html
    console.print("Time-Series Level is " + str(round(data.mean(), 2)))

    Ft = max(0, 1 - np.var(result.resid)) / np.var(result.trend + result.resid)
    console.print(f"Strength of Trend: {Ft:.4f}")

    Fs = max(
        0,
        1 - np.var(result.resid) / np.var(result.seasonal + result.resid),
    )
    console.print(f"Strength of Seasonality: {Fs:.4f}\n")

    export_data(
        export,
        os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"),
        "summary",
        cycle.join(trend),
    )


@log_start_end(log=logger)
def display_normality(data: pd.DataFrame, target: str, export: str = "") -> None:
    """Prints table showing normality statistics

    Parameters
    ----------
    data : pd.DataFrame
        DataFrame
    target : str
        Column in data to look at
    export : str
        Format to export data
    """
    data = data[target]
    normal = qa_model.get_normality(data)
    stats1 = normal.copy().T
    stats1.iloc[:, 1] = stats1.iloc[:, 1].apply(lambda x: lambda_color_red(x))

    print_rich_table(
        stats1,
        show_index=True,
        headers=["Statistic", "p-value"],
        floatfmt=".4f",
        title="[bold]Normality Statistics[/bold]",
    )

    export_data(
        export,
        os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"),
        "normality",
        normal,
    )


@log_start_end(log=logger)
def display_unitroot(
    data: pd.DataFrame,
    target: str,
    fuller_reg: str = "c",
    kpss_reg: str = "c",
    export: str = "",
):
    """Prints table showing unit root test calculations

    Parameters
    ----------
    data : pd.DataFrame
        DataFrame
    target : str
        Column of data to look at
    fuller_reg : str
        Type of regression of ADF test. Can be ‘c’,’ct’,’ctt’,’nc’ 'c' - Constant and t - trend order
    kpss_reg : str
        Type of regression for KPSS test. Can be ‘c’,’ct'
    export : str
        Format for exporting data
    """
    data = data[target]
    data = qa_model.get_unitroot(data, fuller_reg, kpss_reg)
    print_rich_table(
        data,
        show_index=True,
        headers=list(data.columns),
        title="[bold]Unit Root Calculation[/bold]",
        floatfmt=".4f",
    )

    export_data(
        export,
        os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"),
        "unitroot",
        data,
    )


@log_start_end(log=logger)
def display_raw(
    data: pd.DataFrame,
    sortby: str = "",
    ascend: bool = False,
    limit: int = 20,
    export: str = "",
) -> None:
    """Prints table showing raw stock data

    Parameters
    ----------
    data : DataFrame
        DataFrame with historical information
    sortby : str
        The column to sort by
    ascend : bool
        Whether to sort descending
    limit : int
        Number of rows to show
    export : str
        Export data as CSV, JSON, XLSX
    """

    if isinstance(data, pd.Series):
        df1 = pd.DataFrame(data)
    else:
        df1 = data.copy()

    if sortby:
        try:
            sort_col = [x.lower().replace(" ", "") for x in df1.columns].index(
                sortby.lower().replace(" ", "")
            )
        except ValueError:
            console.print("[red]The provided column is not a valid option[/red]\n")
            return
        df1 = df1.sort_values(by=data.columns[sort_col], ascending=ascend)
    else:
        df1 = df1.sort_index(ascending=ascend)
    df1.index = [x.strftime("%Y-%m-%d") for x in df1.index]

    print_rich_table(
        df1.head(limit),
        headers=[x.title() if x != "" else "Date" for x in df1.columns],
        title="[bold]Raw Data[/bold]",
        show_index=True,
        floatfmt=".3f",
    )

    export_data(
        export,
        os.path.dirname(os.path.abspath(__file__)),
        "raw",
        data,
    )


@log_start_end(log=logger)
def display_line(
    data: pd.Series,
    title: str = "",
    log_y: bool = True,
    markers_lines: Optional[List[datetime]] = None,
    markers_scatter: Optional[List[datetime]] = None,
    export: str = "",
    external_axes: Optional[List[plt.Axes]] = None,
) -> None:
    """Display line plot of data

    Parameters
    ----------
    data: pd.Series
        Data to plot
    title: str
        Title for plot
    log_y: bool
        Flag for showing y on log scale
    markers_lines: Optional[List[datetime]]
        List of dates to highlight using vertical lines
    markers_scatter: Optional[List[datetime]]
        List of dates to highlight using scatter
    export: str
        Format to export data
    external_axes : Optional[List[plt.Axes]], optional
        External axes (1 axis is expected in the list), by default None

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> df = openbb.stocks.load("AAPL")
    >>> openbb.qa.line(data=df["Adj Close"])
    """
    # This plot has 1 axis
    if external_axes is None:
        _, ax = plt.subplots(
            figsize=plot_autoscale(),
            dpi=PLOT_DPI,
        )
    elif is_valid_axes_count(external_axes, 1):
        (ax,) = external_axes
    else:
        return

    if log_y:
        ax.semilogy(data.index, data.values)
        ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
        ax.yaxis.set_major_locator(
            matplotlib.ticker.LogLocator(base=100, subs=[1.0, 2.0, 5.0, 10.0])
        )
        ax.ticklabel_format(style="plain", axis="y")

    else:
        ax.plot(data.index, data.values)

        if markers_lines:
            ymin, ymax = ax.get_ylim()
            ax.vlines(markers_lines, ymin, ymax, color="#00AAFF")

        if markers_scatter:
            for n, marker_date in enumerate(markers_scatter):
                price_location_idx = data.index.get_loc(marker_date, method="nearest")
                # algo to improve text placement of highlight event number
                if (
                    0 < price_location_idx < (len(data) - 1)
                    and data.iloc[price_location_idx - 1]
                    > data.iloc[price_location_idx]
                    and data.iloc[price_location_idx + 1]
                    > data.iloc[price_location_idx]
                ):
                    text_loc = (0, -20)
                else:
                    text_loc = (0, 10)
                ax.annotate(
                    str(n + 1),
                    (mdates.date2num(marker_date), data.iloc[price_location_idx]),
                    xytext=text_loc,
                    textcoords="offset points",
                )
                ax.scatter(
                    marker_date,
                    data.iloc[price_location_idx],
                    color="#00AAFF",
                    s=100,
                )

    data_type = data.name
    ax.set_ylabel(data_type)
    ax.set_xlim(data.index[0], data.index[-1])
    ax.ticklabel_format(style="plain", axis="y")
    ax.get_yaxis().set_major_formatter(
        matplotlib.ticker.FuncFormatter(lambda x, _: lambda_long_number_format(x))
    )

    if title:
        ax.set_title(title)

    theme.style_primary_axis(ax)

    if external_axes is None:
        theme.visualize_output()

    export_data(
        export,
        os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"),
        "line",
    )


def display_var(
    data: pd.DataFrame,
    symbol: str = "",
    use_mean: bool = False,
    adjusted_var: bool = False,
    student_t: bool = False,
    percentile: float = 99.9,
    data_range: int = 0,
    portfolio: bool = False,
) -> None:
    """Prints table showing VaR of dataframe.

    Parameters
    ----------
    data: pd.Dataframe
        Data dataframe
    use_mean: bool
        if one should use the data mean return
    symbol: str
        name of the data
    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: int
        var percentile
    data_range: int
        Number of rows you want to use VaR over
    portfolio: bool
        If the data is a portfolio
    """

    if data_range > 0:
        df = qa_model.get_var(
            data[-data_range:], use_mean, adjusted_var, student_t, percentile, portfolio
        )
    else:
        df = qa_model.get_var(
            data, use_mean, adjusted_var, student_t, percentile, portfolio
        )

    if adjusted_var:
        str_title = "Adjusted "
    elif student_t:
        str_title = "Student-t "
    else:
        str_title = ""

    if symbol != "":
        symbol += " "

    print_rich_table(
        df,
        show_index=True,
        headers=list(df.columns),
        title=f"[bold]{symbol}{str_title}Value at Risk[/bold]",
        floatfmt=".2f",
    )


def display_es(
    data: pd.DataFrame,
    symbol: str = "",
    use_mean: bool = False,
    distribution: str = "normal",
    percentile: float = 99.9,
    portfolio: bool = False,
) -> None:
    """Prints table showing expected shortfall.

    Parameters
    ----------
    data: pd.DataFrame
        Data dataframe
    use_mean:
        if one should use the data mean return
    symbol: str
        name of the data
    distribution: str
        choose distribution to use: logistic, laplace, normal
    percentile: int
        es percentile
    portfolio: bool
        If the data is a portfolio
    """
    df = qa_model.get_es(data, use_mean, distribution, percentile, portfolio)

    if distribution == "laplace":
        str_title = "Laplace "
    elif distribution == "student_t":
        str_title = "Student-t "
    elif distribution == "logistic":
        str_title = "Logistic "
    else:
        str_title = ""

    if symbol != "":
        symbol += " "

    print_rich_table(
        df,
        show_index=True,
        headers=list(df.columns),
        title=f"[bold]{symbol}{str_title}Expected Shortfall[/bold]",
        floatfmt=".2f",
    )


def display_sharpe(data: pd.DataFrame, rfr: float = 0, window: float = 252) -> None:
    """Plots Calculated the sharpe ratio

    Parameters
    ----------
    data: pd.DataFrame
        selected dataframe column
    rfr: float
        risk free rate
    window: float
        length of the rolling window
    """
    sharpe_ratio = qa_model.get_sharpe(data, rfr, window)

    fig, ax = plt.subplots()
    ax.plot(sharpe_ratio[int(window - 1) :])
    ax.set_title(f"Sharpe Ratio - over a {window} day window")
    ax.set_ylabel("Sharpe ratio")
    ax.set_xlabel("Date")
    fig.legend()

    theme.style_primary_axis(ax)
    theme.visualize_output()


def display_sortino(
    data: pd.DataFrame, target_return: float, window: float, adjusted: bool
) -> None:
    """Plots the sortino ratio

    Parameters
    ----------
    data: pd.DataFrame
        selected dataframe
    target_return: float
        target return of the asset
    window: float
        length of the rolling window
    adjusted: bool
        adjust the sortino ratio
    """
    sortino_ratio = qa_model.get_sortino(data, target_return, window, adjusted)
    if adjusted:
        str_adjusted = "Adjusted "
    else:
        str_adjusted = ""

    fig, ax = plt.subplots()
    ax.plot(sortino_ratio[int(window - 1) :])
    ax.set_title(f"{str_adjusted}Sortino Ratio - over a {window} day window")
    ax.set_ylabel("Sortino ratio")
    ax.set_xlabel("Date")
    fig.legend()

    theme.style_primary_axis(ax)
    theme.visualize_output()


def display_omega(
    data: pd.DataFrame, threshold_start: float = 0, threshold_end: float = 1.5
) -> None:
    """Plots the omega ratio

    Parameters
    ----------
    data: pd.DataFrame
        stock dataframe
    threshold_start: float
        annualized target return threshold start of plotted threshold range
    threshold_end: float
        annualized target return threshold end of plotted threshold range
    """
    df = qa_model.get_omega(data, threshold_start, threshold_end)

    # Plotting
    fig, ax = plt.subplots()
    ax.plot(df["threshold"], df["omega"])
    ax.set_title(f"Omega Curve - over last {len(data)}'s period")
    ax.set_ylabel("Omega Ratio")
    ax.set_xlabel("Threshold (%)")
    fig.legend()
    ax.set_ylim(threshold_start, threshold_end)

    theme.style_primary_axis(ax)
    theme.visualize_output()
