From 2c8f7cbd71d75ef9eb1490dcc1818dc01cd16583 Mon Sep 17 00:00:00 2001 From: didier Date: Wed, 10 Mar 2021 00:30:06 +0000 Subject: [PATCH] Add comparison analysis menu with historical and correlation heatmap comparisons --- .../comparison_analysis/ca_menu.py | 161 ++++++++++++++++++ .../comparison_analysis/yahoo_finance_api.py | 157 +++++++++++++++++ gamestonk_terminal/config_terminal.py | 3 + gamestonk_terminal/main_helper.py | 3 + terminal.py | 9 +- 5 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 gamestonk_terminal/comparison_analysis/ca_menu.py create mode 100644 gamestonk_terminal/comparison_analysis/yahoo_finance_api.py diff --git a/gamestonk_terminal/comparison_analysis/ca_menu.py b/gamestonk_terminal/comparison_analysis/ca_menu.py new file mode 100644 index 00000000000..1e79bbcf290 --- /dev/null +++ b/gamestonk_terminal/comparison_analysis/ca_menu.py @@ -0,0 +1,161 @@ +import argparse +import requests + +from gamestonk_terminal import config_terminal as cfg +from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn + +from gamestonk_terminal.comparison_analysis import yahoo_finance_api as yf_api + + +def get_similar_companies(l_args, s_ticker): + """ Get similar companies. [Source: Polygon API] """ + parser = argparse.ArgumentParser( + prog="get", + description="""Get similar companies to compare with.""", + ) + + l_similar = [] + try: + parse_known_args_and_warn(parser, l_args) + + result = requests.get( + f"https://api.polygon.io/v1/meta/symbols/{s_ticker}/company?&apiKey={cfg.API_POLYGON_KEY}" + ) + + if result.status_code == 200: + l_similar = result.json()["similar"] + + except Exception as e: + print(e) + + print("") + return l_similar + + +def select_similar_companies(l_args): + """ Select similar companies, e.g. NIO,XPEV,LI""" + parser = argparse.ArgumentParser( + prog="select", + description="""Select similar companies to compare with.""", + ) + parser.add_argument( + "-s", + "--similar", + dest="l_similar", + type=lambda s: [str(item) for item in s.split(",")], + default=[], + help="similar companies to compare with.", + ) + + try: + # For the case where a user uses: 'select NIO,XPEV,LI' + if l_args: + if "-" not in l_args[0]: + l_args.insert(0, "-s") + + ns_parser = parse_known_args_and_warn(parser, l_args) + print("") + return ns_parser.l_similar + + except Exception as e: + print(e) + print("") + return [] + + +def print_comparison_analysis(s_ticker, s_start, s_interval, l_similar, b_user): + """ Print help """ + + s_intraday = (f"Intraday {s_interval}", "Daily")[s_interval == "1440min"] + + if s_start: + print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") + else: + print(f"\n{s_intraday} Stock: {s_ticker}") + + s_similar_source = ("Polygon API", "User")[b_user] + + if l_similar: + print(f"[{s_similar_source}] Similar Companies: {', '.join(l_similar)}") + else: + print(f"No similar companies [{s_similar_source}]") + + print("\nComparison Analysis Mode:") + print(" help show this comparison analysis menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" get get similar companies [Polygon API]") + print(" select select similar companies") + print("") + print(" historical historical data comparison") + print(" corr correlation between similar companies") + print("") + return + + +def ca_menu(df_stock, s_ticker, s_start, s_interval): + + # Add list of arguments that the comparison analysis parser accepts + ca_parser = argparse.ArgumentParser(prog="ca", add_help=False) + ca_parser.add_argument( + "cmd", + choices=[ + "help", + "q", + "quit", + "get", + "select", + "historical", + "corr", + ], + ) + + b_user = False + l_similar = get_similar_companies([], s_ticker) + print_comparison_analysis(s_ticker, s_start, s_interval, l_similar, b_user) + + # Loop forever and ever + while True: + # Get input command from user + as_input = input(f"{get_flair()} (ca)> ") + + # Parse fundamental analysis command of the list of possible commands + try: + (ns_known_args, l_args) = ca_parser.parse_known_args(as_input.split()) + + except SystemExit: + print("The command selected doesn't exist\n") + continue + + if ns_known_args.cmd == "help": + print_comparison_analysis(s_ticker, s_start, s_interval, l_similar, b_user) + + elif ns_known_args.cmd == "q": + # Just leave the CA menu + return False + + elif ns_known_args.cmd == "quit": + # Abandon the program + return True + + elif ns_known_args.cmd == "get": + l_similar = get_similar_companies(l_args, s_ticker) + b_user = False + + elif ns_known_args.cmd == "select": + l_similar = select_similar_companies(l_args) + b_user = True + + elif ns_known_args.cmd == "historical": + yf_api.historical( + l_args, df_stock, s_ticker, s_start, s_interval, l_similar + ) + + elif ns_known_args.cmd == "corr": + yf_api.correlation( + l_args, df_stock, s_ticker, s_start, s_interval, l_similar + ) + + else: + print("Command not recognized!") diff --git a/gamestonk_terminal/comparison_analysis/yahoo_finance_api.py b/gamestonk_terminal/comparison_analysis/yahoo_finance_api.py new file mode 100644 index 00000000000..71ff828e70e --- /dev/null +++ b/gamestonk_terminal/comparison_analysis/yahoo_finance_api.py @@ -0,0 +1,157 @@ +import argparse +import requests +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +import yfinance as yf +import seaborn as sns +from datetime import datetime +from pandas.plotting import register_matplotlib_converters +from gamestonk_terminal.helper_funcs import parse_known_args_and_warn + +register_matplotlib_converters() + + +def historical(l_args, df_stock, s_ticker, s_start, s_interval, similar): + parser = argparse.ArgumentParser( + prog="historical", + description="""Historical price comparison between similar companies [Source: Yahoo Finance] + """, + ) + parser.add_argument( + "-s", + "--similar", + dest="l_similar", + type=lambda s: [str(item) for item in s.split(",")], + default=[], + help="similar companies to compare with.", + ) + parser.add_argument( + "-a", + "--also", + dest="l_also", + type=lambda s: [str(item) for item in s.split(",")], + default=[], + help="apart from similar companies (from Polygon API) also compare with.", + ) + + try: + if s_interval != "1440min": + print("Intraday historical data analysis comparison is not yet available.") + # Alpha Vantage only supports 5 calls per minute, we need another API to get intraday data + else: + ns_parser = parse_known_args_and_warn(parser, l_args) + + if ns_parser.l_similar: + similar = ns_parser.l_similar + + similar += ns_parser.l_also + + plt.figure() + plt.title(f"Similar companies to {s_ticker}") + plt.plot(df_stock.index, df_stock["5. adjusted close"].values, lw=2) + l_min = [df_stock.index[0]] + l_leg = [s_ticker] + for symbol in similar: + df_similar_stock = yf.download(symbol, start=s_start, progress=False) + if not df_similar_stock.empty: + plt.plot( + df_similar_stock.index, df_similar_stock["Adj Close"].values + ) + l_min.append(df_similar_stock.index[0]) + l_leg.append(symbol) + 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_stock.index[-1]]) + plt.show() + print("") + + except Exception as e: + print(e) + print("") + + +def correlation(l_args, df_stock, s_ticker, s_start, s_interval, similar): + parser = argparse.ArgumentParser( + prog="corr", + description=""" Correlation heatmap based on historical price comparison between similar companies. [Source: Yahoo Finance] + """, + ) + parser.add_argument( + "-s", + "--similar", + dest="l_similar", + type=lambda s: [str(item) for item in s.split(",")], + default=[], + help="similar companies to compare with.", + ) + parser.add_argument( + "-a", + "--also", + dest="l_also", + type=lambda s: [str(item) for item in s.split(",")], + default=[], + help="apart from similar companies (from Polygon API) also compare with.", + ) + + try: + if s_interval != "1440min": + print("Intraday historical data analysis comparison is not yet available.") + # Alpha Vantage only supports 5 calls per minute, we need another API to get intraday data + else: + ns_parser = parse_known_args_and_warn(parser, l_args) + + if ns_parser.l_similar: + similar = ns_parser.l_similar + + similar += ns_parser.l_also + + if not similar: + print("Provide at least a similar company for correlation") + else: + d_stock = {} + d_stock[s_ticker] = yf.download(s_ticker, start=s_start, progress=False) + l_min = [d_stock[s_ticker].index[0]] + + for symbol in similar: + d_stock[symbol] = yf.download(symbol, start=s_start, progress=False) + if not d_stock[symbol].empty: + l_min.append(d_stock[symbol].index[0]) + + min_start_date = max(l_min) + + df_stock = d_stock[s_ticker]["Adj Close"].rename(s_ticker) + for symbol in d_stock.keys(): + if symbol != s_ticker: + if not d_stock[symbol].empty: + df_stock = pd.concat( + [df_stock, d_stock[symbol]["Adj Close"].rename(symbol)], + axis=1, + ) + + mask = np.zeros((df_stock.shape[1], df_stock.shape[1]), dtype=bool) + mask[np.triu_indices(len(mask))] = True + + sns.heatmap( + df_stock[min_start_date:].corr(), + cbar_kws={"ticks": [-1.0, -0.5, 0.0, 0.5, 1.0]}, + cmap="RdYlGn", + linewidths=1, + annot=True, + vmin=-1, + vmax=1, + mask=mask, + ) + plt.title("Correlation Heatmap") + plt.show() + + print("") + + except Exception as e: + print(e) + print("") diff --git a/gamestonk_terminal/config_terminal.py b/gamestonk_terminal/config_terminal.py index c328fca062e..e02768d8794 100644 --- a/gamestonk_terminal/config_terminal.py +++ b/gamestonk_terminal/config_terminal.py @@ -18,6 +18,9 @@ API_REDDIT_USERNAME = os.getenv("GT_API_REDDIT_USERNAME") or "REPLACE_ME" API_REDDIT_USER_AGENT = os.getenv("GT_API_REDDIT_USER_AGENT") or "REPLACE_ME" API_REDDIT_PASSWORD = os.getenv("GT_API_REDDIT_PASSWORD") or "REPLACE_ME" +# https://polygon.io +API_POLYGON_KEY = os.getenv("GT_API_POLYGON_KEY") or "REPLACE_ME" + # https://developer.twitter.com API_TWITTER_KEY = os.getenv("GT_API_TWITTER_KEY") or "REPLACE_ME" API_TWITTER_SECRET_KEY = os.getenv("GT_API_TWITTER_SECRET_KEY") or "REPLACE_ME" diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 4416e5f1bcc..10cc2e8a17e 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -51,6 +51,9 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): print( " res research web page, \t e.g.: macroaxis, yahoo finance, fool" ) + print( + " ca comparison analysis, \t e.g.: historical, correlation, financials" + ) print( " fa fundamental analysis, \t e.g.: income, balance, cash, earnings" ) diff --git a/terminal.py b/terminal.py index 4c982d6106f..8ac585ebe99 100644 --- a/terminal.py +++ b/terminal.py @@ -13,6 +13,7 @@ from gamestonk_terminal.due_diligence import dd_menu as ddm from gamestonk_terminal.discovery import disc_menu as dm from gamestonk_terminal.sentiment import sen_menu as sm from gamestonk_terminal.papermill import papermill_menu as mill +from gamestonk_terminal.comparison_analysis import ca_menu as cam from gamestonk_terminal import res_menu as rm from gamestonk_terminal import config_terminal as cfg @@ -56,6 +57,7 @@ def main(): "mill", "sen", "res", + "ca", "fa", "ta", "dd", @@ -64,9 +66,7 @@ def main(): ) # Print first welcome message and help - print("") - print("🚀🚀 Welcome to Didier's Gamestonk Terminal!") - print("") + print("\nWelcome to Gamestonk Terminal 🚀\n") should_print_help = True # Loop forever and ever @@ -131,6 +131,9 @@ def main(): elif ns_known_args.opt == "res": b_quit = rm.res_menu(s_ticker, s_start, s_interval) + elif ns_known_args.opt == "ca": + b_quit = cam.ca_menu(df_stock, s_ticker, s_start, s_interval) + elif ns_known_args.opt == "fa": b_quit = fam.fa_menu(s_ticker, s_start, s_interval)