diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md index 77e5200e1d9..a6038db8fa7 100644 --- a/gamestonk_terminal/README.md +++ b/gamestonk_terminal/README.md @@ -219,9 +219,10 @@ Command | Description | Sources [volatility](https://github.com/twopirllc/pandas-ta/tree/master/pandas_ta/volatility) | `bbands` |bollinger bands | [Wikipedia](https://en.wikipedia.org/wiki/Bollinger_Bands), [Investopedia](https://www.investopedia.com/terms/b/bollingerbands.asp) [volume](https://github.com/twopirllc/pandas-ta/tree/master/pandas_ta/volume) | -`ad` |chaikin accumulation/distribution line values | [Wikipedia](https://en.wikipedia.org/wiki/Accumulation/distribution_index), [Investopedia](https://www.investopedia.com/terms/a/accumulationdistribution.asp) +`ad` |accumulation/distribution line values | [Wikipedia](https://en.wikipedia.org/wiki/Accumulation/distribution_index), [Investopedia](https://www.investopedia.com/terms/a/accumulationdistribution.asp) `obv` |on balance volume | [Wikipedia](https://en.wikipedia.org/wiki/On-balance_volume), [Investopedia](https://www.investopedia.com/terms/o/onbalancevolume.asp) - +custom| +`fib` | Fibonocci levels | [Investopedia](https://www.investopedia.com/terms/f/fibonacciretracement.asp)   ## Due Diligence [ยป](due_diligence/README.md) diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 687de8f4356..6e0bc770a88 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -42,7 +42,13 @@ from gamestonk_terminal import thought_of_the_day as thought from gamestonk_terminal.technical_analysis import trendline_api as trend -def clear(other_args: List[str], s_ticker, s_start, s_interval, df_stock): +def clear( + other_args: List[str], + s_ticker: str, + s_start, + s_interval: str, + df_stock: pd.DataFrame, +): """Clears loaded stock and returns empty variables Parameters @@ -89,7 +95,13 @@ def clear(other_args: List[str], s_ticker, s_start, s_interval, df_stock): return s_ticker, s_start, s_interval, df_stock -def load(other_args: List[str], s_ticker, s_start, s_interval, df_stock): +def load( + other_args: List[str], + s_ticker: str, + s_start, + s_interval: str, + df_stock: pd.DataFrame, +): """Load selected ticker Parameters @@ -136,7 +148,7 @@ def load(other_args: List[str], s_ticker, s_start, s_interval, df_stock): "-s", "--start", type=valid_date, - default="2019-01-01", + default=(datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d"), dest="s_start_date", help="The starting date (format YYYY-MM-DD) of the stock", ) @@ -601,7 +613,7 @@ def quote(other_args: List[str], s_ticker: str): return -def view(other_args: List[str], s_ticker: str, s_interval, df_stock): +def view(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): """Plot loaded ticker Parameters diff --git a/gamestonk_terminal/technical_analysis/README.md b/gamestonk_terminal/technical_analysis/README.md index aa5ca8a0fe6..9eb21bd0891 100644 --- a/gamestonk_terminal/technical_analysis/README.md +++ b/gamestonk_terminal/technical_analysis/README.md @@ -41,10 +41,15 @@ This menu aims to perform a technical analysis on a pre-loaded ticker chart, and [VOLUME](#VOLUME) * [ad](#ad) - - chaikin accumulation/distribution line values + - Accumulation/distribution line values * [obv](#obv) - on balance volume +[CUSTOM](#CUSTOM) + * [fib](#fib) + - Shows fibonocci levels + + **S/O to https://github.com/twopirllc/pandas-ta** _Owing to this library, it is fairly easy to add other technical indicators. So, let us know if there's any other that you would like._ ## view @@ -111,10 +116,11 @@ usage: sma [-l L_LENGTH] [-o N_OFFSET] ## vwap +VWAP is available for intraday data. This plots the most recent day only. ``` usage: vwap [-o N_OFFSET] ``` -![vwap](https://user-images.githubusercontent.com/25267873/108602302-361d1c00-7399-11eb-87de-6026a2987ae0.png) +![vwap](https://user-images.githubusercontent.com/18151143/127217340-0f38bafd-fc16-4be4-a80a-6b89913559f5.png) # MOMENTUM @@ -178,11 +184,29 @@ usage: bbands [-h] [-l N_LENGTH] [-s N_STD] [-m S_MAMODE] [-o N_OFFSET] ``` usage: ad [-o N_OFFSET] [--open] ``` -![ad](https://user-images.githubusercontent.com/25267873/108603504-1f2df800-73a0-11eb-8d04-bcfd044601c1.png) +![ad](https://user-images.githubusercontent.com/18151143/127217265-73415705-3321-478f-b5a0-3ae1487c74ac.png) ## obv ``` usage: obv [-o N_OFFSET] ``` -![obv](https://user-images.githubusercontent.com/25267873/108603503-1dfccb00-73a0-11eb-8da5-e5e5419a94ac.png) +![obv](https://user-images.githubusercontent.com/18151143/127217286-d73c1079-1b84-4da9-a5d4-39f4eb2d6b1b.png) + +# CUSTOM + +## fib +Shows fibonocci levels +```python +usage: fib [-p PERIOD] [--start] [--end] +``` +* -p/--period: Number of days to lookback for retracement. Default = 120. +* --start: Allows for custom selection of min point. +* --end : Allows for custom selection of max point. + +The fibonocci retracement works by looking at a minimum and maximum point. Between these values, there is typically support/resistance at +23.5 %, 38.2%, 50% and 61.8%. Those values follow the golden ratio from the fibonocci sequence. + +The `--start` and `--end` flags allow the user to select dates for the min and max points as your retracement levels. Both flags are required if using. + +![Fib1](https://user-images.githubusercontent.com/18151143/127217240-0b4cd88b-8fd3-484f-941e-b02a0efe08dd.png) diff --git a/gamestonk_terminal/technical_analysis/custom_indicators_view.py b/gamestonk_terminal/technical_analysis/custom_indicators_view.py new file mode 100644 index 00000000000..a849e20d618 --- /dev/null +++ b/gamestonk_terminal/technical_analysis/custom_indicators_view.py @@ -0,0 +1,153 @@ +"""Custom TA indicators""" +__docformat__ = "numpy" + +import argparse +from typing import List +import pandas as pd +import matplotlib.pyplot as plt +from tabulate import tabulate + +from gamestonk_terminal.helper_funcs import ( + parse_known_args_and_warn, + plot_autoscale, + valid_date, +) + +from gamestonk_terminal import config_plot as cfp +from gamestonk_terminal import feature_flags as gtff + + +def fibinocci_retracement(other_args: List[str], data: pd.DataFrame, ticker: str): + """Calculate fibinocci retracement levels + + Parameters + ---------- + other_args:List[str] + Argparse arguments + data: pd.DataFrame + Stock data + ticker:str + Stock ticker + """ + + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="fib", + description="Calculates the fibinocci retracement levels", + ) + parser.add_argument( + "-p", + "--period", + dest="period", + type=int, + help="Days to lookback for retracement", + default=120, + ) + + parser.add_argument( + "--start", + dest="start", + type=valid_date, + help="Starting date to select", + required="--end" in other_args, + ) + + parser.add_argument( + "--end", + dest="end", + type=valid_date, + help="Ending date to select", + required="--start" in other_args, + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + if ns_parser.start and ns_parser.end: + + if ns_parser.start not in data.index: + date0 = data.index[ + data.index.get_loc(ns_parser.start, method="nearest") + ] + print(f"Start date not in data. Using nearest: {date0}") + else: + date0 = ns_parser.start + if ns_parser.end not in data.index: + date1 = data.index[data.index.get_loc(ns_parser.end, method="nearest")] + print(f"End date not in data. Using nearest: {date1}") + else: + date1 = ns_parser.end + + data0 = data.loc[date0, "Adj Close"] + data1 = data.loc[date1, "Adj Close"] + + min_pr = min(data0, data1) + max_pr = max(data0, data1) + + if min_pr == data0: + min_date = date0 + max_date = date1 + else: + min_date = date1 + max_date = date0 + + else: + data_to_use = data.iloc[-ns_parser.period :]["Adj Close"] + + min_pr = data_to_use.min() + min_date = data_to_use.idxmin() + max_pr = data_to_use.max() + max_date = data_to_use.idxmax() + + fib_levels = [0, 0.235, 0.382, 0.5, 0.618, 1] + price_dif = max_pr - min_pr + + levels = [round(max_pr - price_dif * f_lev, 2) for f_lev in fib_levels] + + df = pd.DataFrame() + df["Level"] = fib_levels + df["Level"] = df["Level"].apply(lambda x: str(x * 100) + "%") + df["Price"] = levels + + fig, ax = plt.subplots(figsize=(plot_autoscale()), dpi=cfp.PLOT_DPI) + + ax.plot(data["Adj Close"], "b") + ax.plot([min_date, max_date], [min_pr, max_pr], c="k") + + for i in levels: + ax.axhline(y=i, c="g", alpha=0.5) + + for i in range(5): + ax.fill_between(data.index, levels[i], levels[i + 1], alpha=0.6) + + ax.set_ylabel("Price") + ax.set_title(f"Fibonacci Support for {ticker.upper()}") + ax.set_xlim(data.index[0], data.index[-1]) + + ax1 = ax.twinx() + ax1.set_ylim(ax.get_ylim()) + ax1.set_yticks(levels) + ax1.set_yticklabels(fib_levels) + + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + + if gtff.USE_ION: + plt.ion() + plt.show() + + print( + tabulate( + df, + headers=["Fib Level", "Price"], + showindex=False, + tablefmt="fancy_grid", + ) + ) + print("") + + except Exception as e: + print(e, "\n") diff --git a/gamestonk_terminal/technical_analysis/momentum.py b/gamestonk_terminal/technical_analysis/momentum.py index 7fc6107fce6..65e003ff915 100644 --- a/gamestonk_terminal/technical_analysis/momentum.py +++ b/gamestonk_terminal/technical_analysis/momentum.py @@ -1,7 +1,14 @@ +"""Momentum Technical Analysis""" +__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, parse_known_args_and_warn, @@ -13,7 +20,21 @@ from gamestonk_terminal import feature_flags as gtff register_matplotlib_converters() -def cci(l_args, s_ticker, s_interval, df_stock): +def cci(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): + """Commodity channel index + + 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, @@ -56,7 +77,7 @@ def cci(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -82,46 +103,63 @@ def cci(l_args, s_ticker, s_interval, df_stock): offset=ns_parser.n_offset, ).dropna() - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) - plt.subplot(211) - plt.title(f"Commodity Channel Index (CCI) on {s_ticker}") + fig, axes = plt.subplots(2, 1, figsize=plot_autoscale(), dpi=PLOT_DPI) + ax = axes[0] + ax.set_title(f"{s_ticker} CCI") if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) + ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) else: - plt.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.ylabel("Share Price ($)") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - plt.subplot(212) - plt.plot(df_ta.index, df_ta.values, "b", lw=2) - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.axhspan(100, plt.gca().get_ylim()[1], facecolor="r", alpha=0.2) - plt.axhspan(plt.gca().get_ylim()[0], -100, facecolor="g", alpha=0.2) - plt.axhline(100, linewidth=3, color="r", ls="--") - plt.axhline(-100, linewidth=3, color="g", ls="--") - plt.xlabel("Time") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - plt.gca().twinx() - plt.ylim(plt.gca().get_ylim()) - plt.yticks([0.2, 0.8], ("OVERSOLD", "OVERBOUGHT")) + ax.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_ylabel("Share Price ($)") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + ax.minorticks_on() + ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax2 = axes[1] + ax2.plot(df_ta.index, df_ta.values, "b", lw=2) + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax2.axhspan(100, plt.gca().get_ylim()[1], facecolor="r", alpha=0.2) + ax2.axhspan(plt.gca().get_ylim()[0], -100, facecolor="g", alpha=0.2) + ax2.axhline(100, linewidth=3, color="r", ls="--") + ax2.axhline(-100, linewidth=3, color="g", ls="--") + ax2.grid(b=True, which="major", color="#666666", linestyle="-") + ax2.minorticks_on() + ax2.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + + ax3 = ax2.twinx() + ax3.set_ylim(ax2.get_ylim()) + ax3.set_yticks([-100, 100]) + ax3.set_yticklabels(["OVERSOLD", "OVERBOUGHT"]) if gtff.USE_ION: plt.ion() + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + plt.show() print("") except Exception as e: - print(e) - print("") + print(e, "\n") -def macd(l_args, s_ticker, s_interval, df_stock): +def macd(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): + """Moving average convergence divergence + + 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, @@ -176,7 +214,7 @@ def macd(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -200,47 +238,63 @@ def macd(l_args, s_ticker, s_interval, df_stock): offset=ns_parser.n_offset, ).dropna() - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) - plt.subplot(211) - plt.title(f"Moving Average Convergence Divergence (MACD) on {s_ticker}") + fig, axes = plt.subplots(2, 1, figsize=plot_autoscale(), dpi=PLOT_DPI) + ax = axes[0] + ax.set_title(f"{s_ticker} MACD") if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) + ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) else: - plt.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.ylabel("Share Price ($)") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - plt.subplot(212) - plt.plot(df_ta.index, df_ta.iloc[:, 0].values, "b", lw=2) - plt.plot(df_ta.index, df_ta.iloc[:, 2].values, "r", lw=2) - plt.bar(df_ta.index, df_ta.iloc[:, 1].values, color="g") - plt.legend( + ax.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_ylabel("Share Price ($)") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + ax.minorticks_on() + ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax2 = axes[1] + ax2.plot(df_ta.index, df_ta.iloc[:, 0].values, "b", lw=2) + ax2.plot(df_ta.index, df_ta.iloc[:, 2].values, "r", lw=2) + ax2.bar(df_ta.index, df_ta.iloc[:, 1].values, color="g") + ax2.legend( [ f"MACD Line {df_ta.columns[0]}", f"Signal Line {df_ta.columns[2]}", f"Histogram {df_ta.columns[1]}", - ] + ], + loc="upper left", ) - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - plt.xlabel("Time") + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + 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) - print("") + print(e, "\n") -def rsi(l_args, s_ticker, s_interval, df_stock): +def rsi(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): + """Relative strength index + + 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, @@ -292,7 +346,7 @@ def rsi(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -316,47 +370,65 @@ def rsi(l_args, s_ticker, s_interval, df_stock): offset=ns_parser.n_offset, ).dropna() - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) - plt.subplot(211) + fig, axes = plt.subplots(2, 1, figsize=plot_autoscale(), dpi=PLOT_DPI) + ax = axes[0] if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) + ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) else: - plt.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) - plt.title(f"Relative Strength Index (RSI) on {s_ticker}") - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.ylabel("Share Price ($)") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - plt.subplot(212) - plt.plot(df_ta.index, df_ta.values, "b", lw=2) - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.axhspan(70, 100, facecolor="r", alpha=0.2) - plt.axhspan(0, 30, facecolor="g", alpha=0.2) - plt.axhline(70, linewidth=3, color="r", ls="--") - plt.axhline(30, linewidth=3, color="g", ls="--") - plt.xlabel("Time") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - plt.ylim([0, 100]) - plt.gca().twinx() - plt.ylim(plt.gca().get_ylim()) - plt.yticks([0.15, 0.85], ("OVERSOLD", "OVERBOUGHT")) + ax.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) + ax.set_title(f" {s_ticker} RSI{ns_parser.n_length} ") + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_ylabel("Share Price ($)") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + ax.minorticks_on() + ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax2 = axes[1] + ax2.plot(df_ta.index, df_ta.values, "b", lw=2) + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax2.axhspan(70, 100, facecolor="r", alpha=0.2) + ax2.axhspan(0, 30, facecolor="g", alpha=0.2) + ax2.axhline(70, linewidth=3, color="r", ls="--") + ax2.axhline(30, linewidth=3, color="g", ls="--") + ax2.grid(b=True, which="major", color="#666666", linestyle="-") + ax2.minorticks_on() + ax2.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax2.set_ylim([0, 100]) + ax3 = ax2.twinx() + ax3.set_ylim(ax2.get_ylim()) + ax3.set_yticks([30, 70]) + ax3.set_yticklabels(["OVERSOLD", "OVERBOUGHT"]) if gtff.USE_ION: plt.ion() + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + plt.show() print("") except Exception as e: - print(e) - print("") + print(e, "\n") -def stoch(l_args, s_ticker, s_interval, df_stock): +def stoch( + other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame +): + """Stochastic oscillator + + 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, @@ -408,7 +480,7 @@ def stoch(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -436,43 +508,51 @@ def stoch(l_args, s_ticker, s_interval, df_stock): offset=ns_parser.n_offset, ).dropna() - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) - plt.subplot(211) + fig, axes = plt.subplots(2, 1, figsize=plot_autoscale(), dpi=PLOT_DPI) + ax = axes[0] if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) + ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) else: - plt.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) - plt.title(f"Stochastic Relative Strength Index (STOCH RSI) on {s_ticker}") - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.ylabel("Share Price ($)") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - plt.subplot(212) - plt.plot(df_ta.index, df_ta.iloc[:, 0].values, "k", lw=2) - plt.plot(df_ta.index, df_ta.iloc[:, 1].values, "b", lw=2, ls="--") - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.axhspan(80, 100, facecolor="r", alpha=0.2) - plt.axhspan(0, 20, facecolor="g", alpha=0.2) - plt.axhline(80, linewidth=3, color="r", ls="--") - plt.axhline(20, linewidth=3, color="g", ls="--") - plt.legend([f"%K {df_ta.columns[0]}", f"%D {df_ta.columns[1]}"]) - plt.xlabel("Time") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - plt.ylim([0, 100]) - plt.gca().twinx() - plt.ylim(plt.gca().get_ylim()) - plt.yticks([0.1, 0.9], ("OVERSOLD", "OVERBOUGHT")) + ax.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) + ax.set_title(f"Stochastic Relative Strength Index (STOCH RSI) on {s_ticker}") + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_xticklabels([]) + ax.set_ylabel("Share Price ($)") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + ax.minorticks_on() + ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + + ax2 = axes[1] + ax2.plot(df_ta.index, df_ta.iloc[:, 0].values, "k", lw=2) + ax2.plot(df_ta.index, df_ta.iloc[:, 1].values, "b", lw=2, ls="--") + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + + ax2.axhspan(80, 100, facecolor="r", alpha=0.2) + ax2.axhspan(0, 20, facecolor="g", alpha=0.2) + ax2.axhline(80, linewidth=3, color="r", ls="--") + ax2.axhline(20, linewidth=3, color="g", ls="--") + + ax2.grid(b=True, which="major", color="#666666", linestyle="-") + ax2.minorticks_on() + ax2.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax2.set_ylim([0, 100]) + + ax3 = ax2.twinx() + ax3.set_ylim(ax2.get_ylim()) + ax3.set_yticks([20, 80]) + ax3.set_yticklabels(["OVERSOLD", "OVERBOUGHT"]) + ax2.legend( + [f"%K {df_ta.columns[0]}", f"%D {df_ta.columns[1]}"], loc="lower left" + ) if gtff.USE_ION: plt.ion() + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) plt.show() print("") except Exception as e: - print(e) - print("") + print(e, "\n") diff --git a/gamestonk_terminal/technical_analysis/overlap.py b/gamestonk_terminal/technical_analysis/overlap.py index bfc52c08c49..dd1e8eb28f5 100644 --- a/gamestonk_terminal/technical_analysis/overlap.py +++ b/gamestonk_terminal/technical_analysis/overlap.py @@ -1,7 +1,15 @@ +"""Overlap Technical Analysis""" +___docformat__ = "numpy" + import argparse +from typing import List + import matplotlib.pyplot as plt +import pandas as pd import pandas_ta as ta from pandas.plotting import register_matplotlib_converters +import mplfinance as mpf + from gamestonk_terminal.helper_funcs import ( check_positive, parse_known_args_and_warn, @@ -13,7 +21,21 @@ from gamestonk_terminal import feature_flags as gtff register_matplotlib_converters() -def ema(l_args, s_ticker, s_interval, df_stock): +def ema(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): + """Plots exponential moving average (EMA) over stock + + 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, @@ -50,7 +72,7 @@ def ema(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -70,8 +92,9 @@ def ema(l_args, s_ticker, s_interval, df_stock): offset=ns_parser.n_offset, ).dropna() - _, _ = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - plt.title(f"{ns_parser.n_length} EMA on {s_ticker}") + fig, _ = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + plt.title(f"{ns_parser.n_length} Day EMA on {s_ticker}") + if s_interval == "1440min": plt.plot( df_stock["Adj Close"].index, @@ -86,17 +109,19 @@ def ema(l_args, s_ticker, s_interval, df_stock): else: plt.plot(df_stock["Close"].index, df_stock["Close"].values, "k", lw=3) plt.xlim(df_stock["Close"].index[0], df_stock["Close"].index[-1]) + plt.xlabel("Time") - plt.ylabel(f"Share Price of {s_ticker} ($)") + plt.ylabel(f"{s_ticker} Price($)") plt.plot(df_ta.index, df_ta.values, c="tab:blue") l_legend = list() l_legend.append(s_ticker) # Pandas series if len(df_ta.shape) == 1: - l_legend.append(f"{ns_parser.n_length} EMA") + l_legend.append(f"EMA {ns_parser.n_length}") # Pandas dataframe else: l_legend.append(df_ta.columns.tolist()) + plt.legend(l_legend) plt.grid(b=True, which="major", color="#666666", linestyle="-") plt.minorticks_on() @@ -105,15 +130,30 @@ def ema(l_args, s_ticker, s_interval, df_stock): if gtff.USE_ION: plt.ion() + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + plt.show() print("") except Exception as e: - print(e) - print("") + print(e, "\n") -def sma(l_args, s_ticker, s_interval, df_stock): +def sma(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): + """Plots simple moving average (SMA) over stock + + 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, @@ -148,11 +188,11 @@ def sma(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + fig, _ = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) if s_interval == "1440min": plt.plot(df_stock.index, df_stock["Adj Close"].values, color="k") else: @@ -171,11 +211,12 @@ def sma(l_args, s_ticker, s_interval, df_stock): df_stock["Close"], length=length, offset=ns_parser.n_offset ).dropna() plt.plot(df_ta.index, df_ta.values) - l_legend.append(f"{length} SMA") + l_legend.append(f"SMA{length} ") + plt.title(f"SMA on {s_ticker}") plt.xlim(df_stock.index[0], df_stock.index[-1]) plt.xlabel("Time") - plt.ylabel("Share Price ($)") + plt.ylabel(f"{s_ticker} Price ($)") plt.legend(l_legend) plt.grid(b=True, which="major", color="#666666", linestyle="-") plt.minorticks_on() @@ -184,15 +225,30 @@ def sma(l_args, s_ticker, s_interval, df_stock): if gtff.USE_ION: plt.ion() + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + plt.show() print("") except Exception as e: - print(e) - print("") + print(e, "\n") -def vwap(l_args, s_ticker, s_interval, df_stock): +def vwap(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): + """Plots volume weighted average price (VWAP) + + 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, @@ -214,63 +270,41 @@ def vwap(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return # Daily if s_interval == "1440min": - df_ta = ta.vwap( - high=df_stock["High"], - low=df_stock["Low"], - close=df_stock["Adj Close"], - volume=df_stock["Volume"], - offset=ns_parser.n_offset, - ) + print("VWAP should be used with intraday data. \n") + return - # Intraday - else: - df_ta = ta.vwap( - high=df_stock["High"], - low=df_stock["Low"], - close=df_stock["Close"], - volume=df_stock["Volume"], - offset=ns_parser.n_offset, - ) + df_stock.index = df_stock.index.tz_localize(None) + df_stock["Day"] = [idx.date() for idx in df_stock.index] - _, axPrice = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["Adj Close"].values, color="k") - else: - plt.plot(df_stock.index, df_stock["Close"].values, color="k") - plt.plot(df_ta.index, df_ta.values) - plt.title(f"VWAP on {s_ticker}") - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.xlabel("Time") - plt.ylabel("Share Price ($)") - plt.legend([s_ticker, "VWAP"]) - _ = axPrice.twinx() + day_df = df_stock[df_stock.Day == df_stock.Day[-1]] - if s_interval == "1440min": - plt.bar( - df_stock.index, - df_stock["Volume"].values, - color="k", - alpha=0.8, - width=0.3, - ) - else: - plt.bar( - df_stock.index, - df_stock["Volume"].values, - color="k", - alpha=0.8, - width=0.3, - ) - plt.ylabel("Volume") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + df_vwap = ta.vwap( + high=day_df["High"], + low=day_df["Low"], + close=day_df["Close"], + volume=day_df["Volume"], + ) + + mc = mpf.make_marketcolors( + up="green", down="red", edge="black", wick="black", volume="in", ohlc="i" + ) + + s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True) + apdict = mpf.make_addplot(df_vwap, color="k") + mpf.plot( + day_df, + style=s, + type="candle", + addplot=apdict, + volume=True, + title=f"\n{s_ticker} {s_interval} VWAP", + ) if gtff.USE_ION: plt.ion() @@ -279,5 +313,4 @@ def vwap(l_args, s_ticker, s_interval, df_stock): print("") except Exception as e: - print(e) - print("") + print(e, "\n") diff --git a/gamestonk_terminal/technical_analysis/ta_controller.py b/gamestonk_terminal/technical_analysis/ta_controller.py index 6554a1dd278..3190cd791fe 100644 --- a/gamestonk_terminal/technical_analysis/ta_controller.py +++ b/gamestonk_terminal/technical_analysis/ta_controller.py @@ -21,6 +21,7 @@ from gamestonk_terminal.technical_analysis import finbrain_view from gamestonk_terminal.technical_analysis import tradingview_view from gamestonk_terminal.technical_analysis import finviz_view from gamestonk_terminal.technical_analysis import finnhub_view +from gamestonk_terminal.technical_analysis import custom_indicators_view class TechnicalAnalysisController: @@ -49,6 +50,7 @@ class TechnicalAnalysisController: "bbands", "ad", "obv", + "fib", ] def __init__( @@ -112,8 +114,10 @@ class TechnicalAnalysisController: print("volatility:") print(" bbands bollinger bands") print("volume:") - print(" ad chaikin accumulation/distribution line values") + print(" ad accumulation/distribution line values") print(" obv on balance volume") + print("custom:") + print(" fib fibonocci retracement") print("") def switch(self, an_input: str): @@ -229,6 +233,12 @@ class TechnicalAnalysisController: """Process obv command""" ta_volume.obv(other_args, self.ticker, self.interval, self.stock) + def call_fib(self, other_args: List[str]): + """Process fib command""" + custom_indicators_view.fibinocci_retracement( + other_args, self.stock, self.ticker + ) + def menu( ticker: str, start: datetime, interval: str, stock: pd.DataFrame, context: str = "" diff --git a/gamestonk_terminal/technical_analysis/volatility.py b/gamestonk_terminal/technical_analysis/volatility.py index 4d1e0eafdb4..27197a2a129 100644 --- a/gamestonk_terminal/technical_analysis/volatility.py +++ b/gamestonk_terminal/technical_analysis/volatility.py @@ -1,7 +1,14 @@ +"""Volatility Technical Indicators""" +__docformat__ = "numpy" + import argparse +from typing import List + import matplotlib.pyplot as plt import pandas_ta as ta from pandas.plotting import register_matplotlib_converters +import pandas as pd + from gamestonk_terminal.helper_funcs import ( check_positive, parse_known_args_and_warn, @@ -13,7 +20,23 @@ from gamestonk_terminal import feature_flags as gtff register_matplotlib_converters() -def bbands(l_args, s_ticker, s_interval, df_stock): +def bbands( + other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame +): + """Plot Bollinger Bands + + Parameters + ---------- + other_args: List[str] + Argparse arguments + s_ticker: str + Ticker + s_interval: str + Interval + df_stock: pd.DataFrame + Stock dataframe + """ + parser = argparse.ArgumentParser( add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, @@ -64,7 +87,7 @@ def bbands(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -88,33 +111,37 @@ def bbands(l_args, s_ticker, s_interval, df_stock): offset=ns_parser.n_offset, ).dropna() - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) if s_ticker == "1440min": - plt.plot(df_stock.index, df_stock["Adj Close"].values, color="k", lw=3) + ax.plot(df_stock.index, df_stock["Adj Close"].values, color="k", lw=3) else: - plt.plot(df_stock.index, df_stock["Close"].values, color="k", lw=3) + ax.plot(df_stock.index, df_stock["Close"].values, color="k", lw=3) plt.plot(df_ta.index, df_ta.iloc[:, 0].values, "r", lw=2) - plt.plot(df_ta.index, df_ta.iloc[:, 1].values, "b", lw=1.5, ls="--") - plt.plot(df_ta.index, df_ta.iloc[:, 2].values, "g", lw=2) - plt.title(f"Bollinger Band (BBands) on {s_ticker}") - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.xlabel("Time") - plt.ylabel("Share Price ($)") + ax.plot(df_ta.index, df_ta.iloc[:, 1].values, "b", lw=1.5, ls="--") + ax.plot(df_ta.index, df_ta.iloc[:, 2].values, "g", lw=2) + ax.set_title(f"{s_ticker} Bollinger Bands") + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_xlabel("Time") + ax.set_ylabel("Share Price ($)") + plt.legend([s_ticker, df_ta.columns[0], df_ta.columns[1], df_ta.columns[2]]) - plt.gca().fill_between( + ax.fill_between( df_ta.index, df_ta.iloc[:, 0].values, df_ta.iloc[:, 2].values, alpha=0.1, color="b", ) - plt.grid(b=True, which="major", color="#666666", linestyle="-") + ax.grid(b=True, which="major", color="#666666", linestyle="-") plt.minorticks_on() plt.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("") diff --git a/gamestonk_terminal/technical_analysis/volume.py b/gamestonk_terminal/technical_analysis/volume.py index 441790225dc..ff44f326049 100644 --- a/gamestonk_terminal/technical_analysis/volume.py +++ b/gamestonk_terminal/technical_analysis/volume.py @@ -1,6 +1,14 @@ +"""Volume Technical Analysis""" +__docformat__ = "numpy" + import argparse +from typing import List +from datetime import timedelta + 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, @@ -13,7 +21,21 @@ from gamestonk_terminal import feature_flags as gtff register_matplotlib_converters() -def ad(l_args, s_ticker, s_interval, df_stock): +def ad(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): + """Accumulation Dictribution Line + + Parameters + ---------- + other_args: List[str] + Argparse arguments + s_ticker: str + Ticker + s_interval: str + Stock data interval + df_stock: pd.DataFrame + Dataframe of stock prices + """ + parser = argparse.ArgumentParser( add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, @@ -50,107 +72,115 @@ def ad(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - # Daily - if s_interval == "1440min": - # Use open stock values - if ns_parser.b_use_open: - df_ta = ta.ad( - high=df_stock["High"], - low=df_stock["Low"], - close=df_stock["Adj Close"], - volume=df_stock["Volume"], - offset=ns_parser.n_offset, - open_=df_stock["Open"], - ).dropna() - # Do not use open stock values - else: - df_ta = ta.ad( - high=df_stock["High"], - low=df_stock["Low"], - close=df_stock["Adj Close"], - volume=df_stock["Volume"], - offset=ns_parser.n_offset, - ).dropna() + bar_colors = [ + "r" if x[1].Open < x[1].Close else "g" for x in df_stock.iterrows() + ] - # Intraday + if s_interval == "1440min": + bar_width = timedelta(days=1) else: - # Use open stock values - if ns_parser.b_use_open: - df_ta = ta.ad( - high=df_stock["High"], - low=df_stock["Low"], - close=df_stock["Close"], - volume=df_stock["Volume"], - offset=ns_parser.n_offset, - open_=df_stock["Open"], - ).dropna() - # Do not use open stock values - else: - df_ta = ta.ad( - high=df_stock["High"], - low=df_stock["Low"], - close=df_stock["Close"], - volume=df_stock["Volume"], - offset=ns_parser.n_offset, - ).dropna() + bar_width = timedelta(minutes=int(s_interval.split("m")[0])) - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) - axPrice = plt.subplot(211) - if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) + if ns_parser.b_use_open: + df_ta = ta.ad( + high=df_stock["High"], + low=df_stock["Low"], + close=df_stock["Adj Close"], + volume=df_stock["Volume"], + offset=ns_parser.n_offset, + open_=df_stock["Open"], + ).dropna() + # Do not use open stock values else: - plt.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) - plt.title(f"Accumulation/Distribution Line (AD) on {s_ticker}") - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.ylabel("Share Price ($)") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - _ = axPrice.twinx() + # Note this should always be Close not Adj Close + df_ta = ta.ad( + high=df_stock["High"], + low=df_stock["Low"], + close=df_stock["Close"], + volume=df_stock["Volume"], + offset=ns_parser.n_offset, + ).dropna() + + fig, axes = plt.subplots( + 3, + 1, + gridspec_kw={"height_ratios": [2, 1, 1]}, + figsize=plot_autoscale(), + dpi=PLOT_DPI, + ) + ax = axes[0] if s_interval == "1440min": - plt.bar( + ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) + else: + ax.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) + ax.set_title(f"{s_ticker} AD") + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_ylabel("Share Price ($)") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + ax.minorticks_on() + ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + + ax2 = axes[1] + ax2.set_ylabel("Volume") + if s_interval == "1440min": + ax2.bar( df_stock.index, df_stock["Volume"].values, - color="k", + color=bar_colors, alpha=0.8, width=0.3, ) else: - plt.bar( + ax2.bar( df_stock.index, df_stock["Volume"].values, - color="k", + color=bar_colors, alpha=0.8, - width=0.3, + width=bar_width, ) - plt.subplot(212) - plt.plot(df_ta.index, df_ta.values, "b", lw=1) - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.axhline(0, linewidth=2, color="k", ls="--") - plt.legend(["Chaikin Oscillator"]) - plt.xlabel("Time") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + + ax3 = axes[2] + ax3.plot(df_ta.index, df_ta.values, "b", lw=1) + ax3.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax3.axhline(0, linewidth=2, color="k", ls="--") + ax3.set_ylabel("A/D") + ax3.set_xlabel("Time") + ax3.grid(b=True, which="major", color="#666666", linestyle="-") + ax3.minorticks_on() + ax3.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) - print("") - return + print(e, "\n") -def obv(l_args, s_ticker, s_interval, df_stock): - """Technical Analysis with On-Balance Volume indicator""" +def obv(other_args: List[str], s_ticker: str, s_interval: str, df_stock: pd.DataFrame): + """On Balance Volume + + Parameters + ---------- + other_args: List[str] + Argparse arguments + s_ticker: str + Ticker + s_interval: str + Stock data interval + df_stock: pd.DataFrame + Dataframe of stock prices + """ parser = argparse.ArgumentParser( add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, @@ -176,10 +206,19 @@ def obv(l_args, s_ticker, s_interval, df_stock): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return + 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])) + # Daily if s_interval == "1440min": df_ta = ta.obv( @@ -196,47 +235,58 @@ def obv(l_args, s_ticker, s_interval, df_stock): offset=ns_parser.n_offset, ).dropna() - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) - axPrice = plt.subplot(211) + fig, axes = plt.subplots( + 3, + 1, + gridspec_kw={"height_ratios": [2, 1, 1]}, + figsize=plot_autoscale(), + dpi=PLOT_DPI, + ) + ax = axes[0] if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) + ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=2) else: - plt.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) - plt.title(f"On-Balance Volume (OBV) on {s_ticker}") - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.ylabel("Share Price ($)") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - _ = axPrice.twinx() + ax.plot(df_stock.index, df_stock["Close"].values, "k", lw=2) + + ax.set_title(f"{s_ticker} OBV") + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_ylabel("Share Price ($)") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + ax.minorticks_on() + ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax2 = axes[1] + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + if s_interval == "1440min": - plt.bar( + ax2.bar( df_stock.index, df_stock["Volume"].values, - color="k", + color=bar_colors, alpha=0.8, - width=0.3, + width=bar_width, ) else: - plt.bar( + ax2.bar( df_stock.index, df_stock["Volume"].values, - color="k", + color=bar_colors, alpha=0.8, - width=0.3, + width=bar_width, ) - plt.subplot(212) - plt.plot(df_ta.index, df_ta.values, "b", lw=1) - plt.xlim(df_stock.index[0], df_stock.index[-1]) - plt.legend(["OBV"]) - plt.xlabel("Time") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax3 = axes[2] + ax3.plot(df_ta.index, df_ta.values, "b", lw=1) + ax3.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax3.set_xlabel("Time") + ax3.grid(b=True, which="major", color="#666666", linestyle="-") + ax3.minorticks_on() + ax3.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("")