diff --git a/gamestonk_terminal/common/technical_analysis/volume_model.py b/gamestonk_terminal/common/technical_analysis/volume_model.py index 0b42d377f45..6d598253063 100644 --- a/gamestonk_terminal/common/technical_analysis/volume_model.py +++ b/gamestonk_terminal/common/technical_analysis/volume_model.py @@ -39,6 +39,43 @@ def ad(df_stock: pd.DataFrame, use_open: bool) -> pd.DataFrame: return pd.DataFrame(df_ta) +def adosc(df_stock: pd.DataFrame, use_open: bool, fast: int, slow: int) -> pd.DataFrame: + """Calculate AD oscillator technical indicator + + Parameters + ---------- + df_stock : pd.DataFrame + Dataframe of prices + use_open : bool + Whether to use open prices + + Returns + ------- + pd.DataFrame + Dataframe with technical indicator + """ + if use_open: + df_ta = ta.adosc( + high=df_stock["High"], + low=df_stock["Low"], + close=df_stock["Close"], + volume=df_stock["Volume"], + open_=df_stock["Open"], + fast=fast, + slow=slow, + ).dropna() + else: + df_ta = ta.adosc( + high=df_stock["High"], + low=df_stock["Low"], + close=df_stock["Close"], + volume=df_stock["Volume"], + fast=fast, + slow=slow, + ).dropna() + return pd.DataFrame(df_ta) + + def obv(s_interval: str, df_stock: pd.DataFrame) -> pd.DataFrame: """On Balance Volume diff --git a/gamestonk_terminal/common/technical_analysis/volume_view.py b/gamestonk_terminal/common/technical_analysis/volume_view.py index 61d0090cbdd..5ba6ca384e2 100644 --- a/gamestonk_terminal/common/technical_analysis/volume_view.py +++ b/gamestonk_terminal/common/technical_analysis/volume_view.py @@ -3,11 +3,9 @@ __docformat__ = "numpy" import os from datetime import timedelta - import matplotlib.pyplot as plt import pandas as pd from pandas.plotting import register_matplotlib_converters - from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.common.technical_analysis import volume_model from gamestonk_terminal.config_plot import PLOT_DPI @@ -105,6 +103,109 @@ def plot_ad( ) +def plot_adosc( + s_ticker: str, + s_interval: str, + df_stock: pd.DataFrame, + fast: int, + slow: int, + use_open: bool, + export: str, +) -> pd.DataFrame: + """Display AD Osc Indicator + + Parameters + ---------- + s_ticker : str + Stock ticker + s_interval : str + Interval of stock data + df_stock : pd.DataFrame + Dataframe of prices + use_open : bool + Whether to use open prices in calculation + fast: int + Length of fast window + slow : int + Length of slow window + export : str + Format to export data + """ + bar_colors = ["r" if x[1].Open < x[1].Close else "g" for x in df_stock.iterrows()] + + if s_interval == "1440min": + bar_width = timedelta(days=1) + else: + bar_width = timedelta(minutes=int(s_interval.split("m")[0])) + + divisor = 1000000 + df_vol = df_stock["Volume"].dropna() + df_vol = df_vol.values / divisor + df_ta = volume_model.adosc(df_stock, use_open, fast, slow) + df_cal = df_ta.values + df_cal = df_cal / divisor + + fig, axes = plt.subplots( + 3, + 1, + figsize=plot_autoscale(), + dpi=PLOT_DPI, + ) + ax = axes[0] + ax.set_title(f"{s_ticker} AD Oscillator") + 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("Price") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + + ax1 = axes[1] + ax1.set_ylabel("Volume [M]") + + if s_interval == "1440min": + ax1.bar( + df_stock.index, + df_vol, + color=bar_colors, + alpha=0.8, + width=0.3, + ) + else: + ax1.bar( + df_stock.index, + df_vol, + color=bar_colors, + alpha=0.8, + width=bar_width, + ) + ax1.set_xlim(df_stock.index[0], df_stock.index[-1]) + + ax2 = axes[2] + ax2.set_ylabel("AD Osc [M]") + + ax2.plot(df_ta.index, df_cal, "b", lw=2, label="AD Osc") + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax2.grid(b=True, which="major", color="#666666", linestyle="-") + + if gtff.USE_ION: + plt.ion() + + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + plt.legend() + plt.show() + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), + "adosc", + df_ta, + ) + + def plot_obv(s_ticker: str, s_interval: str, df_stock: pd.DataFrame, export: str): """Plot OBV tecnhical inticator diff --git a/gamestonk_terminal/stocks/technical_analysis/ta_controller.py b/gamestonk_terminal/stocks/technical_analysis/ta_controller.py index d25580e5371..ebcc4f6d697 100644 --- a/gamestonk_terminal/stocks/technical_analysis/ta_controller.py +++ b/gamestonk_terminal/stocks/technical_analysis/ta_controller.py @@ -63,6 +63,7 @@ class TechnicalAnalysisController: "bbands", "donchian", "ad", + "adosc", "obv", "fib", ] @@ -131,7 +132,8 @@ Volatility: bbands bollinger bands donchian donchian channels Volume: - ad accumulation/distribution line values + ad accumulation/distribution line + adosc chaikin oscillator obv on balance volume Custom: fib fibonacci retracement @@ -1326,6 +1328,73 @@ Custom: except Exception as e: print(e, "\n") + def call_adosc(self, other_args: List[str]): + """Process adosc command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="adosc", + description=""" + Acummulation/Distribution Oscillator, also known as the Chaikin Oscillator + is essentially a momentum indicator, but of the Accumulation-Distribution line + rather than merely price. It looks at both the strength of price moves and the + underlying buying and selling pressure during a given time period. The oscillator + reading above zero indicates net buying pressure, while one below zero registers + net selling pressure. Divergence between the indicator and pure price moves are + the most common signals from the indicator, and often flag market turning points. + """, + ) + parser.add_argument( + "--open", + action="store_true", + default=False, + dest="b_use_open", + help="uses open value of stock", + ) + parser.add_argument( + "-f", + "--fast_length", + action="store", + dest="n_length_fast", + type=check_positive, + default=3, + help="fast length", + ) + parser.add_argument( + "-s", + "--slow_length", + action="store", + dest="n_length_slow", + type=check_positive, + default=10, + help="slow length", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + volume_view.plot_adosc( + s_ticker=self.ticker, + s_interval=self.interval, + df_stock=self.stock, + use_open=ns_parser.b_use_open, + fast=ns_parser.n_length_fast, + slow=ns_parser.n_length_slow, + export=ns_parser.export, + ) + except Exception as e: + print(e, "\n") + def call_obv(self, other_args: List[str]): """Process obv command""" parser = argparse.ArgumentParser(