diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md index 8b00d3cb36b..07162e46ddf 100644 --- a/gamestonk_terminal/README.md +++ b/gamestonk_terminal/README.md @@ -49,18 +49,14 @@ usage: clear * Clear previously loaded stock ticker. ``` -usage: view [-t S_TICKER] [-s S_START_DATE] [-i {1,5,15,30,60}] [--type TYPE] [--raw] [--export {csv,json,xlsx}] +view ``` -* Visualise historical data of a stock. An alpha_vantage key is necessary. - * -t : Stock ticker - * -s : The starting date (format YYYY-MM-DD) of the stock - * -i : Intraday stock minutes - * --type : 1234 corresponds to types: 1. open; 2. high; 3.low; 4. close; while 14 corresponds to types: 1.open; 4. close - * --raw : Print raw data. - * --export : Export dataframe data to csv,json,xlsx file +* Visualise historical data of a stock. +![AAPL](https://user-images.githubusercontent.com/18151143/125211301-69424800-e273-11eb-853d-0b7a0db8ffb7.png) -![GNUS](https://user-images.githubusercontent.com/25267873/108925137-f2920e80-7633-11eb-8274-6e3bb6a19592.png) + +Line plot color is configurable in config_plot.py ``` quote -t S_TICKER diff --git a/gamestonk_terminal/config_plot.py b/gamestonk_terminal/config_plot.py index a38c987f559..5339382b5d8 100644 --- a/gamestonk_terminal/config_plot.py +++ b/gamestonk_terminal/config_plot.py @@ -20,3 +20,7 @@ PLOT_WIDTH_PERCENTAGE = 70 # When autoscaling is True, choose which monitor to scale to # Primary monitor = 0, secondary monitor use 1 MONITOR = 0 + +# Color for `view` command data. All pyplot colors listed at: +# https://matplotlib.org/stable/gallery/color/named_colors.html +VIEW_COLOR = "tab:green" diff --git a/gamestonk_terminal/helper_funcs.py b/gamestonk_terminal/helper_funcs.py index e28ca71efe1..23197765506 100644 --- a/gamestonk_terminal/helper_funcs.py +++ b/gamestonk_terminal/helper_funcs.py @@ -93,9 +93,16 @@ def plot_view_stock(df: pd.DataFrame, symbol: str, interval: str): """ df.sort_index(ascending=True, inplace=True) + bar_colors = ["r" if x[1].Open < x[1].Close else "g" for x in df.iterrows()] try: - _, axVolume = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) + fig, ax = plt.subplots( + 2, + 1, + gridspec_kw={"height_ratios": [3, 1]}, + figsize=plot_autoscale(), + dpi=cfgPlot.PLOT_DPI, + ) except Exception as e: print(e) print( @@ -106,24 +113,47 @@ def plot_view_stock(df: pd.DataFrame, symbol: str, interval: str): # In order to make nice Volume plot, make the bar width = interval if interval == "1440min": bar_width = timedelta(days=1) + title_string = "Daily" else: bar_width = timedelta(minutes=int(interval.split("m")[0])) + title_string = f"{int(interval.split('m')[0])} min" - plt.bar(df.index, df.iloc[:, -1], color="k", alpha=0.8, width=bar_width) - plt.ylabel("Volume") - _ = axVolume.twinx() - plt.plot(df.index, df.iloc[:, :-1]) - plt.title(symbol.upper() + " (Time Series)") - plt.xlim(df.index[0], df.index[-1]) - plt.xlabel("Time") - plt.ylabel("Share Price ($)") - plt.legend(df.columns) - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + ax[0].yaxis.tick_right() + if "Adj Close" in df.columns: + ax[0].plot(df.index, df["Adj Close"], c=cfgPlot.VIEW_COLOR) + else: + ax[0].plot(df.index, df["Close"], c=cfgPlot.VIEW_COLOR) + ax[0].set_xlim(df.index[0], df.index[-1]) + ax[0].set_xticks([]) + ax[0].yaxis.set_label_position("right") + ax[0].set_ylabel("Share Price ($)") + ax[0].grid(axis="y", color="gainsboro", linestyle="-", linewidth=0.5) + ax[0].spines["top"].set_visible(False) + ax[0].spines["left"].set_visible(False) + ax[1].bar( + df.index, df.Volume / 1_000_000, color=bar_colors, alpha=0.8, width=bar_width + ) + ax[1].set_xlim(df.index[0], df.index[-1]) + ax[1].yaxis.tick_right() + ax[1].yaxis.set_label_position("right") + ax[1].set_ylabel("Volume (1M)") + ax[1].grid(axis="y", color="gainsboro", linestyle="-", linewidth=0.5) + ax[1].spines["top"].set_visible(False) + ax[1].spines["left"].set_visible(False) + ax[1].set_xlabel("Time") + fig.suptitle( + symbol + " " + title_string, + size=20, + x=0.15, + y=0.95, + fontfamily="serif", + fontstyle="italic", + ) if gtff.USE_ION: plt.ion() + fig.tight_layout(pad=2) + plt.setp(ax[1].get_xticklabels(), rotation=20, horizontalalignment="right") plt.show() print("") diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 98944fe44ab..3b4f25a06d6 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -27,8 +27,6 @@ from gamestonk_terminal.helper_funcs import ( valid_date, plot_view_stock, parse_known_args_and_warn, - check_ohlc, - lett_to_num, plot_autoscale, export_data, ) @@ -526,7 +524,7 @@ def quote(other_args: List[str], s_ticker: str): return -def view(other_args: List[str], s_ticker: str, s_start, s_interval, df_stock): +def view(other_args: List[str], s_ticker: str, s_interval, df_stock): """ Plot loaded ticker or load ticker and plot Parameters @@ -535,154 +533,63 @@ def view(other_args: List[str], s_ticker: str, s_start, s_interval, df_stock): Argparse arguments s_ticker: str Ticker to load - s_start: str - Start date s_interval: str Interval tto get data for df_stock: pd.Dataframe Preloaded dataframe to plot """ + parser = argparse.ArgumentParser( - add_help=False, - prog="view", - description="Visualize historical data of a stock.", - ) - if s_ticker: - parser.add_argument( - "-t", - "--ticker", - action="store", - dest="s_ticker", - default=s_ticker, - help="Stock ticker", - ) - else: - parser.add_argument( - "-t", - "--ticker", - action="store", - dest="s_ticker", - required="-h" not in other_args, - help="Stock ticker", - ) - parser.add_argument( - "-s", - "--start", - type=valid_date, - dest="s_start_date", - default=s_start if s_start else "2019-01-01", - help="The starting date (format YYYY-MM-DD) of the stock", - ) - parser.add_argument( - "-i", - "--interval", - action="store", - dest="n_interval", - type=int, - default=0, - choices=[1, 5, 15, 30, 60], - help="Intraday stock minutes", - ) - parser.add_argument( - "--type", - action="store", - dest="type", - type=check_ohlc, - default="a", # in case it's adjusted close - help=( - "ohlc corresponds to types: open; high; low; close; " - "while oc corresponds to types: open; close" - ), - ) - parser.add_argument( - "--raw", - action="store_true", - default=False, - dest="b_raw", - help="Print raw data.", - ) - parser.add_argument( - "--export", - choices=["csv", "json", "xlsx"], - default="", - dest="export", - help="Export dataframe data to csv,json,xlsx file", + add_help=False, prog="view", description="Visualize historical data of a stock." ) try: ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - - # Update values: - if ns_parser.s_ticker != s_ticker: - if ns_parser.n_interval > 0: - s_ticker, s_start, s_interval, df_stock = load( - [ - "-t", - ns_parser.s_ticker, - "-s", - ns_parser.s_start_date.strftime("%Y-%m-%d"), - "-i", - ns_parser.n_interval, - ], - s_ticker, - s_start, - s_interval, - df_stock, - ) - else: - s_ticker, s_start, s_interval, df_stock = load( - [ - "-t", - ns_parser.s_ticker, - "-s", - ns_parser.s_start_date.strftime("%Y-%m-%d"), - ], - s_ticker, - s_start, - s_interval, - df_stock, - ) - - # A new interval intraday period was given - if ns_parser.n_interval != 0: - s_interval = str(ns_parser.n_interval) + "min" - - type_candles = lett_to_num(ns_parser.type) - - df_stock.sort_index(ascending=True, inplace=True) - - # Daily - if s_interval == "1440min": - # The default doesn't exist for intradaily data - ln_col_idx = [int(x) - 1 for x in list(type_candles)] - # Check that the types given are not bigger than 4, as there are only 5 types (0-4) - # pylint: disable=len-as-condition - if len([i for i in ln_col_idx if i > 4]) > 0: - print("An index bigger than 4 was given, which is wrong. Try again") - return - # Append last column of df to be filtered which corresponds to: Volume - ln_col_idx.append(5) - # Slice dataframe from the starting date YYYY-MM-DD selected - df_stock = df_stock[ns_parser.s_start_date :] - # Intraday - else: - # The default doesn't exist for intradaily data - # JM edit 6-7-21 -- It seems it does - if ns_parser.type == "a": - ln_col_idx = [4] - else: - ln_col_idx = [int(x) - 1 for x in list(type_candles)] - - # Append last column of df to be filtered which corresponds to: 5. Volume - ln_col_idx.append(5) - # Slice dataframe from the starting date YYYY-MM-DD selected - df_stock = df_stock[ns_parser.s_start_date.strftime("%Y-%m-%d") :] + if not s_ticker: + print("No ticker loaded. First use `load {ticker}`") + print("") + return # Plot view of the stock - plot_view_stock(df_stock.iloc[:, ln_col_idx], ns_parser.s_ticker, s_interval) + plot_view_stock(df_stock, s_ticker, s_interval) + except Exception as e: + print("Error in plotting:") + print(e, "\n") + + except SystemExit: + print("") + return + + +def export(other_args: List[str], df_stock): + parser = argparse.ArgumentParser( + add_help=False, + prog="export", + description="Exports the historical data from this ticker to a file or stdout.", + ) + parser.add_argument( + "-f", + "--filename", + type=str, + dest="s_filename", + default=sys.stdout, + help="Name of file to save the historical data exported (stdout if unspecified)", + ) + parser.add_argument( + "-F", + "--format", + dest="s_format", + type=str, + default="csv", + help="Export historical data into following formats: csv, json, excel, clipboard", + ) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return if ns_parser.b_raw: print( @@ -835,6 +742,7 @@ def bootup(): def reset(): print("resetting...") + plt.close("all") completed_process = subprocess.run("python terminal.py", shell=True, check=False) if completed_process.returncode != 0: completed_process = subprocess.run( diff --git a/poetry.lock b/poetry.lock index 9737f2de3d1..afde6995bc3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2973,7 +2973,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "yfinance" -version = "0.1.59" +version = "0.1.63" description = "Yahoo! Finance market data downloader" category = "main" optional = false @@ -3017,7 +3017,7 @@ prediction = ["tensorflow", "pmdarima"] [metadata] lock-version = "1.1" python-versions = "^3.6.8" -content-hash = "d6c9d143d3bd881bb77cc1f46da2f70687505cc9c1a108deab377e56a249bfa5" +content-hash = "a0ecec577098898eb373d028394e82cde1c0dd91f4f2bd034559de7f6f9f2fef" [metadata.files] absl-py = [ @@ -4172,9 +4172,13 @@ protobuf = [ {file = "protobuf-3.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ae692bb6d1992afb6b74348e7bb648a75bb0d3565a3f5eea5bec8f62bd06d87"}, {file = "protobuf-3.17.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99938f2a2d7ca6563c0ade0c5ca8982264c484fdecf418bd68e880a7ab5730b1"}, {file = "protobuf-3.17.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6902a1e4b7a319ec611a7345ff81b6b004b36b0d2196ce7a748b3493da3d226d"}, + {file = "protobuf-3.17.3-cp38-cp38-win32.whl", hash = "sha256:59e5cf6b737c3a376932fbfb869043415f7c16a0cf176ab30a5bbc419cd709c1"}, + {file = "protobuf-3.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ebcb546f10069b56dc2e3da35e003a02076aaa377caf8530fe9789570984a8d2"}, {file = "protobuf-3.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ffbd23640bb7403574f7aff8368e2aeb2ec9a5c6306580be48ac59a6bac8bde"}, {file = "protobuf-3.17.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:26010f693b675ff5a1d0e1bdb17689b8b716a18709113288fead438703d45539"}, {file = "protobuf-3.17.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e76d9686e088fece2450dbc7ee905f9be904e427341d289acbe9ad00b78ebd47"}, + {file = "protobuf-3.17.3-cp39-cp39-win32.whl", hash = "sha256:a38bac25f51c93e4be4092c88b2568b9f407c27217d3dd23c7a57fa522a17554"}, + {file = "protobuf-3.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:85d6303e4adade2827e43c2b54114d9a6ea547b671cb63fafd5011dc47d0e13d"}, {file = "protobuf-3.17.3-py2.py3-none-any.whl", hash = "sha256:2bfb815216a9cd9faec52b16fd2bfa68437a44b67c56bee59bc3926522ecb04e"}, {file = "protobuf-3.17.3.tar.gz", hash = "sha256:72804ea5eaa9c22a090d2803813e280fb273b62d5ae497aaf3553d141c4fdd7b"}, ] @@ -5027,7 +5031,7 @@ yarl = [ {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, ] yfinance = [ - {file = "yfinance-0.1.59.tar.gz", hash = "sha256:fe4dc46807eceadc6604bf51ece7297b752dc5402a38a87385094fbfc7565fa0"}, + {file = "yfinance-0.1.63.tar.gz", hash = "sha256:11364fe94f1cf7811c45fc620acb61c8c45fcb88de317c7718bbdbc9c1573a4c"}, ] zipp = [ {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, diff --git a/pyproject.toml b/pyproject.toml index c1781262092..f61c75688a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ alpha-vantage = "^2.3.1" finviz = "^1.3.4" bs4 = "^0.0.1" rapidfuzz = "^1.1.1" -yfinance = "^0.1.55" +yfinance = "^0.1.63" pandas-ta = "^0.2.23b0" prettytable = "^2.0.0" psaw = "^0.0.12" diff --git a/requirements-full.txt b/requirements-full.txt index 63273fa9612..fcd41a36896 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -224,6 +224,6 @@ werkzeug==2.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or py widgetsnbextension==3.5.1 wrapt==1.12.1; python_version >= "3.6" yarl==1.6.3; python_version >= "3.6" -yfinance==0.1.59 +yfinance==0.1.63 zipp==3.4.1; python_version < "3.8" and python_version >= "3.6" zope.interface==5.4.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" diff --git a/requirements.txt b/requirements.txt index 8dbb06f129b..4c5103cb56b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -203,6 +203,6 @@ websockets==9.1; python_full_version >= "3.6.1" widgetsnbextension==3.5.1 wrapt==1.12.1; python_version >= "3.6" yarl==1.6.3; python_version >= "3.6" -yfinance==0.1.59 +yfinance==0.1.63 zipp==3.4.1; python_version < "3.8" and python_version >= "3.6" zope.interface==5.4.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" diff --git a/terminal.py b/terminal.py index 492c3524a13..5df633f5283 100644 --- a/terminal.py +++ b/terminal.py @@ -270,7 +270,7 @@ Contexts: def call_view(self, other_args: List[str]): """Process view command""" - view(other_args, self.ticker, self.start, self.interval, self.stock) + view(other_args, self.ticker, self.interval, self.stock) def call_disc(self, _): """Process disc command"""