diff --git a/gamestonk_terminal/common/exploratory_data_analysis/rolling.py b/gamestonk_terminal/common/exploratory_data_analysis/rolling.py new file mode 100644 index 00000000000..dafd1197e7f --- /dev/null +++ b/gamestonk_terminal/common/exploratory_data_analysis/rolling.py @@ -0,0 +1,365 @@ +"""Rolling Statistics""" +__docformat__ = "numpy" + +import argparse +from typing import List +import matplotlib.pyplot as plt +import pandas_ta as ta +import pandas as pd +from pandas.plotting import register_matplotlib_converters + +from gamestonk_terminal.helper_funcs import ( + check_positive, + check_proportion_range, + parse_known_args_and_warn, + plot_autoscale, +) +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal import feature_flags as gtff + +register_matplotlib_converters() + + +def spread( + other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame +): + """Standard Deviation & Variance + + Parameters + ---------- + other_args:List[str] + Argparse arguments + s_ticker: str + Ticker + s_interval: str + Stock time interval + df_stock: pd.DataFrame + Dataframe of prices + """ + + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="spread", + description=""" + + """, + ) + + parser.add_argument( + "-l", + "--length", + action="store", + dest="n_length", + type=check_positive, + default=14, + help="length", + ) + parser.add_argument( + "-o", + "--offset", + action="store", + dest="n_offset", + type=check_positive, + default=0, + help="offset", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + # Daily + if s_interval == "1440min": + df_ta = ta.stdev( + close=df_stock["Adj Close"], + length=ns_parser.n_length, + offset=ns_parser.n_offset, + ).dropna() + df_ta_ = ta.variance( + close=df_stock["Adj Close"], + length=ns_parser.n_length, + offset=ns_parser.n_offset, + ).dropna() + + # Intraday + else: + df_ta = ta.stdev( + close=df_stock["Close"], + length=ns_parser.n_length, + offset=ns_parser.n_offset, + ).dropna() + df_ta_ = ta.variance( + close=df_stock["Adj Close"], + length=ns_parser.n_length, + offset=ns_parser.n_offset, + ).dropna() + + fig, axes = plt.subplots(3, 1, figsize=plot_autoscale(), dpi=PLOT_DPI) + ax = axes[0] + ax.set_title(f"{s_ticker} Spread") + if s_interval == "1440min": + ax.plot(df_stock.index, df_stock["Adj Close"].values, "fuchsia", lw=1) + else: + ax.plot(df_stock.index, df_stock["Close"].values, "fuchsia", lw=1) + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_ylabel("Share Price ($)") + ax.yaxis.set_label_position("right") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + ax.minorticks_on() + ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax1 = axes[1] + ax1.plot(df_ta.index, df_ta.values, "b", lw=1, label="stdev") + ax1.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax1.set_ylabel("Stdev") + ax1.yaxis.set_label_position("right") + ax1.grid(b=True, which="major", color="#666666", linestyle="-") + ax1.minorticks_on() + ax1.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax2 = axes[2] + ax2.plot(df_ta_.index, df_ta_.values, "g", lw=1, label="variance") + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax2.set_ylabel("Variance") + ax2.yaxis.set_label_position("right") + ax2.grid(b=True, which="major", color="#666666", linestyle="-") + ax2.minorticks_on() + ax2.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + + if gtff.USE_ION: + plt.ion() + + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + + plt.show() + + print("") + + except Exception as e: + print(e, "\n") + + +def quantile( + other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame +): + """Overlay Median & Quantile + + Parameters + ---------- + other_args: List[str] + Argparse arguments + s_ticker: str + Ticker + s_interval: str + Data interval + df_stock: pd.DataFrame + Dataframe of dates and prices + """ + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="quantile", + description=""" + The quantiles are values which divide the distribution such that + there is a given proportion of observations below the quantile. + For example, the median is a quantile. The median is the central + value of the distribution, such that half the points are less than + or equal to it and half are greater than or equal to it. + + By default, q is set at 0.5, which effectively is median. Change q to + get the desired quantile (0 int: return ivalue +def check_proportion_range(num) -> float: + """Checks if float is between 0 and 1. If so, return it. + + Parameters + ---------- + num: float + Input float + Returns + ------- + num: float + Input number if conditions are met + Raises + ------- + argparse.ArgumentTypeError + Input number not between min and max values + """ + num = float(num) + maxi = 1.0 + mini = 0.0 + if num < mini or num > maxi: + raise argparse.ArgumentTypeError("Value must be between 0 and 1") + return num + + def valid_date(s: str) -> datetime: """Argparse type to check date is in valid format""" try: diff --git a/gamestonk_terminal/stocks/exploratory_data_analysis/eda_controller.py b/gamestonk_terminal/stocks/exploratory_data_analysis/eda_controller.py index e74f4846e1f..3cb62603675 100644 --- a/gamestonk_terminal/stocks/exploratory_data_analysis/eda_controller.py +++ b/gamestonk_terminal/stocks/exploratory_data_analysis/eda_controller.py @@ -8,7 +8,7 @@ from datetime import datetime import pandas as pd from matplotlib import pyplot as plt from prompt_toolkit.completion import NestedCompleter -from gamestonk_terminal.common.exploratory_data_analysis import eda_api +from gamestonk_terminal.common.exploratory_data_analysis import eda_api, rolling from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.helper_funcs import get_flair from gamestonk_terminal.menu import session @@ -33,6 +33,9 @@ class EdaController: "decompose", "cusum", "acf", + "spread", + "quantile", + "skew", ] def __init__( @@ -79,7 +82,13 @@ class EdaController: print(" cdf cumulative distribution function") print(" bwy box and whisker yearly plot") print(" bwm box and whisker monthly plot") + print("") + print("Rolling Metrics") print(" rolling rolling mean and std deviation") + print(" spread variance and std deviation") + print(" quantile median and quantile") + print(" skew skewness of distribution") + print("") print(" decompose decomposition in cyclic-trend, season, and residuals") print(" cusum detects abrupt changes using cumulative sum algorithm") print(" acf (partial) auto-correlation function differentials") @@ -168,6 +177,18 @@ class EdaController: """Process acf command""" eda_api.acf(other_args, self.ticker, self.stock, self.start) + def call_spread(self, other_args: List[str]): + """Process spread command""" + rolling.spread(other_args, self.ticker, self.interval, self.stock) + + def call_quantile(self, other_args: List[str]): + """Process quantile command""" + rolling.quantile(other_args, self.ticker, self.interval, self.stock) + + def call_skew(self, other_args: List[str]): + """Process skew command""" + rolling.skew(other_args, self.ticker, self.interval, self.stock) + def menu(ticker: str, start: datetime, interval: str, stock: pd.DataFrame): """Statistics Menu"""