From c637b540097d3b5d9752c894b135ebb1126469b8 Mon Sep 17 00:00:00 2001 From: didier Date: Tue, 6 Apr 2021 22:46:11 +0100 Subject: [PATCH 1/7] Add view to screener data --- .../screener/screener_controller.py | 7 + .../screener/yahoo_finance_view.py | 178 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 gamestonk_terminal/screener/yahoo_finance_view.py diff --git a/gamestonk_terminal/screener/screener_controller.py b/gamestonk_terminal/screener/screener_controller.py index 6d71576f95b..597ed2544ce 100644 --- a/gamestonk_terminal/screener/screener_controller.py +++ b/gamestonk_terminal/screener/screener_controller.py @@ -12,6 +12,7 @@ from gamestonk_terminal.helper_funcs import ( parse_known_args_and_warn, ) from gamestonk_terminal.screener import finviz_view +from gamestonk_terminal.screener import yahoo_finance_view class ScreenerController: @@ -24,6 +25,7 @@ class ScreenerController: "quit", "view", "set", + "historical", "overview", "valuation", "financial", @@ -56,6 +58,7 @@ class ScreenerController: print("") print(f"PRESET: {self.preset}") print("") + print(" historical view historical price of stocks that meet preset") print(" overview overview information") print(" valuation valuation information") print(" financial financial information") @@ -181,6 +184,10 @@ class ScreenerController: """Process overview command""" self.set_preset(self, other_args) + def call_historical(self, other_args: List[str]): + """Process historical command""" + yahoo_finance_view.historical(other_args, self.preset) + def call_overview(self, other_args: List[str]): """Process overview command""" finviz_view.screener(other_args, self.preset, "overview") diff --git a/gamestonk_terminal/screener/yahoo_finance_view.py b/gamestonk_terminal/screener/yahoo_finance_view.py new file mode 100644 index 00000000000..71295686854 --- /dev/null +++ b/gamestonk_terminal/screener/yahoo_finance_view.py @@ -0,0 +1,178 @@ +import argparse +from typing import List +import numpy as np +import pandas as pd +from pandas.plotting import register_matplotlib_converters +import matplotlib.pyplot as plt +import datetime +import configparser +import yfinance as yf +import seaborn as sns +from finvizfinance.screener import ticker +from gamestonk_terminal.screener import finviz_view + +from gamestonk_terminal.helper_funcs import ( + parse_known_args_and_warn, + plot_autoscale, + valid_date, +) +from gamestonk_terminal.config_plot import PLOT_DPI + +register_matplotlib_converters() + +d_candle_types = { + "o": "Open", + "h": "High", + "l": "Low", + "c": "Close", + "a": "Adj Close", +} + + +def check_one_of_ohlca(type_candles: str) -> str: + if ( + type_candles == "o" + or type_candles == "h" + or type_candles == "l" + or type_candles == "c" + or type_candles == "a" + ): + return type_candles + raise argparse.ArgumentTypeError("The type of candles specified is not recognized") + + +def historical(other_args: List[str], preset_loaded: str): + """View historical price of stocks that meet preset + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + ticker : str + Loaded preset filter + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="historical", + description="""Historical price comparison between similar companies [Source: Yahoo Finance] + """, + ) + parser.add_argument( + "--start", + type=valid_date, + default=datetime.datetime.strftime( + datetime.datetime.now() - datetime.timedelta(days=6 * 30), "%Y-%m-%d" + ), + dest="start", + help="The starting date (format YYYY-MM-DD) of the historical price to plot", + ) + parser.add_argument( + "-t", + "--type", + action="store", + dest="type_candle", + type=check_one_of_ohlca, + default="a", # in case it's adjusted close + help=("type of candles: o-open, h-high, l-low, c-close, a-adjusted close."), + ) + parser.add_argument( + "-s", + "--signal", + action="store", + dest="signal", + type=str, + default=None, + help="Signal", + choices=list(finviz_view.d_signals.keys()), + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + preset_filter = configparser.RawConfigParser() + preset_filter.optionxform = str # type: ignore + preset_filter.read( + "gamestonk_terminal/screener/presets/" + preset_loaded + ".ini" + ) + + d_general = preset_filter["General"] + d_filters = { + **preset_filter["Descriptive"], + **preset_filter["Fundamental"], + **preset_filter["Technical"], + } + + d_filters = {k: v for k, v in d_filters.items() if v} + + screen = ticker.Ticker() + + if ns_parser.signal: + screen.set_filter(signal=d_signals[ns_parser.signal]) + else: + if d_general["Signal"]: + screen.set_filter(filters_dict=d_filters, signal=d_general["Signal"]) + else: + screen.set_filter(filters_dict=d_filters) + + l_min = list() + l_leg = list() + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + + l_stocks = screen.ScreenerView(verbose=0) + + if len(l_stocks) > 10: + print( + "We limit stocks shown to 10, as after this the plot becomes too noisy. " + "Also, we ran out of different colors." + ) + l_stocks = l_stocks[:10] + + while l_stocks: + l_parsed_stocks = list() + for symbol in l_stocks: + try: + df_similar_stock = yf.download( + symbol, start=ns_parser.start, progress=False, threads=False + ) + if not df_similar_stock.empty: + plt.plot( + df_similar_stock.index, + df_similar_stock[ + d_candle_types[ns_parser.type_candle] + ].values, + ) + l_min.append(df_similar_stock.index[0]) + l_leg.append(symbol) + + l_parsed_stocks.append(symbol) + except Exception as e: + print("") + print(e) + print( + "Disregard previous error, which is due to API Rate limits from Yahoo Finance." + ) + print( + f"Because we like '{symbol}', and we won't leave without getting data from it." + ) + + for parsed_stock in l_parsed_stocks: + l_stocks.remove(parsed_stock) + + plt.title(f"Screener Historical Price using {preset_loaded} preset") + plt.xlabel("Time") + plt.ylabel("Share Price ($)") + plt.legend(l_leg) + plt.grid(b=True, which="major", color="#666666", linestyle="-") + plt.minorticks_on() + plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + # ensures that the historical data starts from same datapoint + plt.xlim([max(l_min), df_similar_stock.index[-1]]) + plt.show() + print("") + + except SystemExit: + print("Similar companies need to be provided", "\n") + except Exception as e: + print(e, "\n") From 44afeca0132a7329ad590ad21e7e7cd40b2dab0b Mon Sep 17 00:00:00 2001 From: didier Date: Tue, 6 Apr 2021 22:58:39 +0100 Subject: [PATCH 2/7] Fix start time --- gamestonk_terminal/screener/yahoo_finance_view.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/gamestonk_terminal/screener/yahoo_finance_view.py b/gamestonk_terminal/screener/yahoo_finance_view.py index 71295686854..3dd9f94ad38 100644 --- a/gamestonk_terminal/screener/yahoo_finance_view.py +++ b/gamestonk_terminal/screener/yahoo_finance_view.py @@ -1,13 +1,10 @@ import argparse from typing import List -import numpy as np -import pandas as pd from pandas.plotting import register_matplotlib_converters import matplotlib.pyplot as plt import datetime import configparser import yfinance as yf -import seaborn as sns from finvizfinance.screener import ticker from gamestonk_terminal.screener import finviz_view @@ -60,9 +57,7 @@ def historical(other_args: List[str], preset_loaded: str): parser.add_argument( "--start", type=valid_date, - default=datetime.datetime.strftime( - datetime.datetime.now() - datetime.timedelta(days=6 * 30), "%Y-%m-%d" - ), + default=datetime.datetime.now() - datetime.timedelta(days=6 * 30), dest="start", help="The starting date (format YYYY-MM-DD) of the historical price to plot", ) @@ -109,7 +104,7 @@ def historical(other_args: List[str], preset_loaded: str): screen = ticker.Ticker() if ns_parser.signal: - screen.set_filter(signal=d_signals[ns_parser.signal]) + screen.set_filter(signal=finviz_view.d_signals[ns_parser.signal]) else: if d_general["Signal"]: screen.set_filter(filters_dict=d_filters, signal=d_general["Signal"]) @@ -134,7 +129,10 @@ def historical(other_args: List[str], preset_loaded: str): for symbol in l_stocks: try: df_similar_stock = yf.download( - symbol, start=ns_parser.start, progress=False, threads=False + symbol, + start=datetime.datetime.strftime(ns_parser.start, "%Y-%m-%d"), + progress=False, + threads=False, ) if not df_similar_stock.empty: plt.plot( From b3d5ebc15561211d221d5418b0c86daa0ad87f5c Mon Sep 17 00:00:00 2001 From: DidierRLopes Date: Tue, 6 Apr 2021 23:15:55 +0100 Subject: [PATCH 3/7] Update README.md --- gamestonk_terminal/screener/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gamestonk_terminal/screener/README.md b/gamestonk_terminal/screener/README.md index 5011b355582..d805ade7fba 100644 --- a/gamestonk_terminal/screener/README.md +++ b/gamestonk_terminal/screener/README.md @@ -6,6 +6,8 @@ This menu aims to filter stocks based on pre-specified preset filters, and the u * view available presets * [set](#set) * set one of the available presets +* [historical](#historical) + * view historical price [Yahoo Finance] * [overview](#overview) * overview information [Finviz] * [valuation](#valuation) @@ -45,6 +47,23 @@ Set preset from under presets folder. * -p : Filter presets +## historical + +```text +usage: historical [-t {o,h,l,c,a}] [-s {top_gainers,top_losers,...}] [--start START] +``` + +View historical price of stocks. [Source: Yahoo Finance] + +* -t : Type of candle: Default 'a' for adjusted close. +* -s : Signal type. Default: None. When specified (see list available in [signals](#signals)), the preset is disregarded. +* --start : Start time with format "%Y-%m-%d". Default 6 months earlier. + +![screener_view](https://user-images.githubusercontent.com/25267873/113784557-8bd13c00-972d-11eb-9776-0e192bb83515.png) + +![top_gainers](https://user-images.githubusercontent.com/25267873/113784834-f97d6800-972d-11eb-8112-6c80f4e2cf5e.png) + + ## overview ```text From 3397f32048e85743dcfddbbe803210c75019431c Mon Sep 17 00:00:00 2001 From: didier Date: Tue, 6 Apr 2021 23:16:54 +0100 Subject: [PATCH 4/7] Improve historical price command from screener menu --- gamestonk_terminal/screener/screener_controller.py | 2 +- gamestonk_terminal/screener/yahoo_finance_view.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gamestonk_terminal/screener/screener_controller.py b/gamestonk_terminal/screener/screener_controller.py index 597ed2544ce..4c1e8467969 100644 --- a/gamestonk_terminal/screener/screener_controller.py +++ b/gamestonk_terminal/screener/screener_controller.py @@ -58,7 +58,7 @@ class ScreenerController: print("") print(f"PRESET: {self.preset}") print("") - print(" historical view historical price of stocks that meet preset") + print(" historical view historical price") print(" overview overview information") print(" valuation valuation information") print(" financial financial information") diff --git a/gamestonk_terminal/screener/yahoo_finance_view.py b/gamestonk_terminal/screener/yahoo_finance_view.py index 3dd9f94ad38..c2139de6482 100644 --- a/gamestonk_terminal/screener/yahoo_finance_view.py +++ b/gamestonk_terminal/screener/yahoo_finance_view.py @@ -158,7 +158,11 @@ def historical(other_args: List[str], preset_loaded: str): for parsed_stock in l_parsed_stocks: l_stocks.remove(parsed_stock) - plt.title(f"Screener Historical Price using {preset_loaded} preset") + if ns_parser.signal: + plt.title(f"Screener Historical Price using {finviz_view.d_signals[ns_parser.signal]} signal") + else: + plt.title(f"Screener Historical Price using {preset_loaded} preset") + plt.xlabel("Time") plt.ylabel("Share Price ($)") plt.legend(l_leg) From d6f135aba2b5a3a36cfc5a8f6bef86a71f12b985 Mon Sep 17 00:00:00 2001 From: didier Date: Tue, 6 Apr 2021 23:18:40 +0100 Subject: [PATCH 5/7] add historical cmd to screener menu to the roadmap --- ROADMAP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ROADMAP.md b/ROADMAP.md index 1e76964f59f..b1f6accd521 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -180,6 +180,7 @@ * [x] Add Screener menu with overview, valuation, financial, ownership, performance, technical commands based on filter presets from Finviz (@didier) - [PR #314](https://github.com/DidierRLopes/GamestonkTerminal/pull/314) * [x] Add README with explanation of how presets are stored and can be added by experienced users. (@didier) - [PR #314](https://github.com/DidierRLopes/GamestonkTerminal/pull/314) * [x] Add screener signals (e.g. top gainers, new highs, most volatile, oversold, major news, ...) from Finviz (@didier) - [PR #314](https://github.com/DidierRLopes/GamestonkTerminal/pull/314) +* [x] Plot screener historical using Yahoo Finance data (@didier) - [PR #319](https://github.com/DidierRLopes/GamestonkTerminal/pull/319) **NEXT** From 48328208f236040363639292b004c4859ee13790 Mon Sep 17 00:00:00 2001 From: didier Date: Tue, 6 Apr 2021 23:20:06 +0100 Subject: [PATCH 6/7] black fix --- gamestonk_terminal/screener/yahoo_finance_view.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gamestonk_terminal/screener/yahoo_finance_view.py b/gamestonk_terminal/screener/yahoo_finance_view.py index c2139de6482..13f5f684eb2 100644 --- a/gamestonk_terminal/screener/yahoo_finance_view.py +++ b/gamestonk_terminal/screener/yahoo_finance_view.py @@ -159,7 +159,9 @@ def historical(other_args: List[str], preset_loaded: str): l_stocks.remove(parsed_stock) if ns_parser.signal: - plt.title(f"Screener Historical Price using {finviz_view.d_signals[ns_parser.signal]} signal") + plt.title( + f"Screener Historical Price using {finviz_view.d_signals[ns_parser.signal]} signal" + ) else: plt.title(f"Screener Historical Price using {preset_loaded} preset") From 11bec1928bc75e778d0cf01539eeeca9cc6cc2db Mon Sep 17 00:00:00 2001 From: didier Date: Wed, 7 Apr 2021 00:12:13 +0100 Subject: [PATCH 7/7] Add ION feature flag --- gamestonk_terminal/screener/screener_controller.py | 3 +++ gamestonk_terminal/screener/yahoo_finance_view.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gamestonk_terminal/screener/screener_controller.py b/gamestonk_terminal/screener/screener_controller.py index 4c1e8467969..40640cc6d8b 100644 --- a/gamestonk_terminal/screener/screener_controller.py +++ b/gamestonk_terminal/screener/screener_controller.py @@ -4,6 +4,7 @@ __docformat__ = "numpy" import os import argparse from typing import List +import matplotlib.pyplot as plt from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.helper_funcs import get_flair from gamestonk_terminal.menu import session @@ -237,6 +238,8 @@ def menu(): an_input = input(f"{get_flair()} (scr)> ") try: + plt.close("all") + process_input = scr_controller.switch(an_input) if process_input is not None: diff --git a/gamestonk_terminal/screener/yahoo_finance_view.py b/gamestonk_terminal/screener/yahoo_finance_view.py index 13f5f684eb2..6ad1c6e93bb 100644 --- a/gamestonk_terminal/screener/yahoo_finance_view.py +++ b/gamestonk_terminal/screener/yahoo_finance_view.py @@ -7,7 +7,7 @@ import configparser import yfinance as yf from finvizfinance.screener import ticker from gamestonk_terminal.screener import finviz_view - +from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.helper_funcs import ( parse_known_args_and_warn, plot_autoscale, @@ -173,6 +173,10 @@ def historical(other_args: List[str], preset_loaded: str): plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) # ensures that the historical data starts from same datapoint plt.xlim([max(l_min), df_similar_stock.index[-1]]) + + if gtff.USE_ION: + plt.ion() + plt.show() print("")