From a0b533d91c43e189b6e3779ef52606307c046271 Mon Sep 17 00:00:00 2001 From: didier Date: Thu, 6 May 2021 00:10:09 +0100 Subject: [PATCH] add government menu, in particular congress data --- .../government/gov_controller.py | 136 ++++++ .../government/quiverquant_model.py | 36 ++ .../government/quiverquant_view.py | 392 ++++++++++++++++++ gamestonk_terminal/main_helper.py | 1 + terminal.py | 5 + 5 files changed, 570 insertions(+) create mode 100644 gamestonk_terminal/government/gov_controller.py create mode 100644 gamestonk_terminal/government/quiverquant_model.py create mode 100644 gamestonk_terminal/government/quiverquant_view.py diff --git a/gamestonk_terminal/government/gov_controller.py b/gamestonk_terminal/government/gov_controller.py new file mode 100644 index 00000000000..2fe42da2a57 --- /dev/null +++ b/gamestonk_terminal/government/gov_controller.py @@ -0,0 +1,136 @@ +"""Government Controller Module""" +__docformat__ = "numpy" + +import argparse +from typing import List +from datetime import datetime +import pandas as pd +from matplotlib import 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 +from prompt_toolkit.completion import NestedCompleter +from gamestonk_terminal.government import quiverquant_view + + +class GovController: + """Gov Controller class""" + + # Command choices + CHOICES = [ + "help", + "q", + "quit", + "last_congress", + "buy_congress", + "sell_congress", + "congress", + ] + + def __init__( + self, + ticker: str, + ): + """Constructor""" + self.ticker = ticker + self.gov_parser = argparse.ArgumentParser(add_help=False, prog="gov") + self.gov_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + + @staticmethod + def print_help(self): + """Print help""" + + print("\nExploratory Data Analysis:") + 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("Explore:") + print(" last_congress last congress trading") + print(" buy_congress top buy congress tickers") + print(" sell_congress top sell congress tickers") + print("") + if self.ticker: + print(f"Ticker: {self.ticker}") + print(" congress congress trades on the ticker") + print("") + return + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + True, False or None + False - quit the menu + True - quit the program + None - continue in the menu + """ + (known_args, other_args) = self.gov_parser.parse_known_args(an_input.split()) + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help(self) + + def call_q(self, _): + """Process Q command - quit the menu""" + return False + + def call_quit(self, _): + """Process Quit command - quit the program""" + return True + + def call_last_congress(self, other_args: List[str]): + """Process last_congress command""" + quiverquant_view.last_congress(other_args) + + def call_buy_congress(self, other_args: List[str]): + """Process buy_congress command""" + quiverquant_view.buy_congress(other_args) + + def call_sell_congress(self, other_args: List[str]): + """Process sell_congress command""" + quiverquant_view.sell_congress(other_args) + + def call_congress(self, other_args: List[str]): + """Process congress command""" + quiverquant_view.congress(other_args, self.ticker) + + +def menu(ticker: str): + """Government Menu""" + + gov_controller = GovController(ticker) + gov_controller.call_help(None) + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in gov_controller.CHOICES} + ) + an_input = session.prompt( + f"{get_flair()} (gov)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (gov)> ") + + try: + plt.close("all") + + process_input = gov_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue diff --git a/gamestonk_terminal/government/quiverquant_model.py b/gamestonk_terminal/government/quiverquant_model.py new file mode 100644 index 00000000000..3bf5527108a --- /dev/null +++ b/gamestonk_terminal/government/quiverquant_model.py @@ -0,0 +1,36 @@ +import requests +import pandas as pd + +# Provided by Quiverquant guys to GST users +API_QUIVERQUANT_KEY = "5cd2a65e96d0486efbe926a7cdbc1e8d8ab6c7b3" + + +def get_congress_trading(ticker: str = "") -> pd.DataFrame: + """Returns the most recent transactions by members of U.S. Congress + + Parameters + ---------- + ticker : str + Ticker to get congress trading data from + + Returns + ------- + pd.DataFrame + Most recent transactions by members of U.S. Congress + """ + + if ticker: + url = f"https://api.quiverquant.com/beta/historical/congresstrading/{ticker}" + else: + url = "https://api.quiverquant.com/beta/live/congresstrading" + + headers = { + "accept": "application/json", + "X-CSRFToken": "TyTJwjuEC7VV7mOqZ622haRaaUr0x0Ng4nrwSRFKQs7vdoBcJlK9qjAS69ghzhFu", + "Authorization": f"Token {API_QUIVERQUANT_KEY}", + } + response = requests.get(url, headers=headers) + if response.status_code == 200: + return pd.DataFrame(response.json()) + + return pd.DataFrame() diff --git a/gamestonk_terminal/government/quiverquant_view.py b/gamestonk_terminal/government/quiverquant_view.py new file mode 100644 index 00000000000..f61dc32a3f8 --- /dev/null +++ b/gamestonk_terminal/government/quiverquant_view.py @@ -0,0 +1,392 @@ +import argparse +from typing import List +import pandas as pd +from datetime import datetime, timedelta +from gamestonk_terminal.government import quiverquant_model +from matplotlib import pyplot as plt +from gamestonk_terminal.helper_funcs import ( + parse_known_args_and_warn, + check_positive, + plot_autoscale, +) +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal import feature_flags as gtff + + +def last_congress(other_args: List[str]): + """Last congress trading + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="last_congress", + description=""" + Last congress trading. [Source: www.quiverquant.com] + """, + ) + parser.add_argument( + "-p", + "--past_transactions_days", + action="store", + dest="past_transactions_days", + type=check_positive, + default=3, + help="Past transaction days", + ) + + try: + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-p") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + df_congress = quiverquant_model.get_congress_trading() + + if df_congress.empty: + print("No congress trading data found\n") + return + + df_congress = df_congress.sort_values("TransactionDate", ascending=False) + + df_congress = df_congress[ + df_congress["TransactionDate"].isin( + df_congress["TransactionDate"].unique()[ + : ns_parser.past_transactions_days + ] + ) + ] + + df_congress = df_congress[ + [ + "TransactionDate", + "Ticker", + "Representative", + "Transaction", + "House", + "Range", + "ReportDate", + ] + ].rename( + columns={"TransactionDate": "Transaction Date", "ReportDate": "Report Date"} + ) + + print(df_congress.to_string(index=False)) + print("") + + except Exception as e: + print(e, "\n") + + +def buy_congress(other_args: List[str]): + """Top buy congress trading + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="buy_congress", + description=""" + Top buy congress trading. [Source: www.quiverquant.com] + """, + ) + parser.add_argument( + "-p", + "--past_transactions_months", + action="store", + dest="past_transactions_months", + type=check_positive, + default=6, + help="Past transaction months", + ) + parser.add_argument( + "-t", + "--top", + action="store", + dest="top_num", + type=check_positive, + default=10, + help="Number of top tickers", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + df_congress = quiverquant_model.get_congress_trading() + + if df_congress.empty: + print("No congress trading data found\n") + return + + df_congress = df_congress.sort_values("TransactionDate", ascending=False) + + start_date = datetime.now() - timedelta( + days=ns_parser.past_transactions_months * 30 + ) + + df_congress["TransactionDate"] = pd.to_datetime(df_congress["TransactionDate"]) + + df_congress = df_congress[df_congress["TransactionDate"] > start_date].dropna() + + df_congress["min"] = df_congress["Range"].apply( + lambda x: x.split("-")[0].strip("$").replace(",", "").strip() + ) + df_congress["max"] = df_congress["Range"].apply( + lambda x: x.split("-")[1].replace(",", "").strip().strip("$") + if "-" in x + else x.strip("$").replace(",", "") + ) + + df_congress["lower"] = df_congress[["min", "max", "Transaction"]].apply( + lambda x: float(x["min"]) + if x["Transaction"] == "Purchase" + else -float(x["max"]), + axis=1, + ) + df_congress["upper"] = df_congress[["min", "max", "Transaction"]].apply( + lambda x: float(x["max"]) + if x["Transaction"] == "Purchase" + else -float(x["min"]), + axis=1, + ) + + df_congress = df_congress.sort_values("TransactionDate", ascending=True) + + print( + f"Top {ns_parser.top_num} most sold stocks since last {ns_parser.past_transactions_months} months in the worst case scenario" + ) + print( + df_congress.groupby("Ticker")["lower"] + .sum() + .sort_values() + .abs() + .head(n=ns_parser.top_num) + .to_string(header=False) + ) + print("") + + except Exception as e: + print(e, "\n") + + +def sell_congress(other_args: List[str]): + """Top sell congress trading + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="sell_congress", + description=""" + Top sell congress trading. [Source: www.quiverquant.com] + """, + ) + parser.add_argument( + "-p", + "--past_transactions_months", + action="store", + dest="past_transactions_months", + type=check_positive, + default=6, + help="Past transaction months", + ) + parser.add_argument( + "-t", + "--top", + action="store", + dest="top_num", + type=check_positive, + default=10, + help="Number of top tickers", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + df_congress = quiverquant_model.get_congress_trading() + + if df_congress.empty: + print("No congress trading data found\n") + return + + df_congress = df_congress.sort_values("TransactionDate", ascending=False) + + start_date = datetime.now() - timedelta( + days=ns_parser.past_transactions_months * 30 + ) + + df_congress["TransactionDate"] = pd.to_datetime(df_congress["TransactionDate"]) + + df_congress = df_congress[df_congress["TransactionDate"] > start_date].dropna() + + df_congress["min"] = df_congress["Range"].apply( + lambda x: x.split("-")[0].strip("$").replace(",", "").strip() + ) + df_congress["max"] = df_congress["Range"].apply( + lambda x: x.split("-")[1].replace(",", "").strip().strip("$") + if "-" in x + else x.strip("$").replace(",", "") + ) + + df_congress["lower"] = df_congress[["min", "max", "Transaction"]].apply( + lambda x: float(x["min"]) + if x["Transaction"] == "Purchase" + else -float(x["max"]), + axis=1, + ) + df_congress["upper"] = df_congress[["min", "max", "Transaction"]].apply( + lambda x: float(x["max"]) + if x["Transaction"] == "Purchase" + else -float(x["min"]), + axis=1, + ) + + df_congress = df_congress.sort_values("TransactionDate", ascending=True) + + print( + f"Top {ns_parser.top_num} most sold stocks since last {ns_parser.past_transactions_months} months in the worst case scenario" + ) + print( + df_congress.groupby("Ticker")["upper"] + .sum() + .sort_values(ascending=False) + .head(n=ns_parser.top_num) + .to_string(header=False) + ) + print("") + + except Exception as e: + print(e, "\n") + + +def plot_congress(congress: pd.DataFrame, ticker: str): + """Plot congress trading + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + ticker: str + Ticker to plot congress trading + """ + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + + plt.gca().fill_between( + congress["TransactionDate"].unique(), + congress.groupby("TransactionDate")["lower"].sum().values / 1000, + congress.groupby("TransactionDate")["upper"].sum().values / 1000, + ) + + plt.xlim( + [congress["TransactionDate"].values[0], congress["TransactionDate"].values[-1]] + ) + plt.grid() + plt.title(f"Congress trading on {ticker}") + plt.xlabel("Date") + plt.ylabel("Amount [1k $]") + + if gtff.USE_ION: + plt.ion() + + plt.show() + + +def congress(other_args: List[str], ticker: str): + """Congress trading + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + ticker: str + Ticker to get congress trading data from + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="congress", + description=""" + Congress trading. [Source: www.quiverquant.com] + """, + ) + parser.add_argument( + "-p", + "--past_transactions_months", + action="store", + dest="past_transactions_months", + type=check_positive, + default=6, + help="Past transaction months", + ) + + try: + + if other_args: + if "-" not in other_args[0]: + other_args.insert(0, "-p") + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + df_congress = quiverquant_model.get_congress_trading(ticker) + + if df_congress.empty: + print("No congress trading data found\n") + return + + df_congress = df_congress.sort_values("TransactionDate", ascending=False) + + start_date = datetime.now() - timedelta( + days=ns_parser.past_transactions_months * 30 + ) + + df_congress["TransactionDate"] = pd.to_datetime(df_congress["TransactionDate"]) + + df_congress = df_congress[df_congress["TransactionDate"] > start_date] + + df_congress["min"] = df_congress["Range"].apply( + lambda x: x.split("-")[0].strip("$").replace(",", "").strip() + ) + df_congress["max"] = df_congress["Range"].apply( + lambda x: x.split("-")[1].replace(",", "").strip().strip("$") + if "-" in x + else x.strip("$").replace(",", "") + ) + + df_congress["lower"] = df_congress[["min", "max", "Transaction"]].apply( + lambda x: int(x["min"]) + if x["Transaction"] == "Purchase" + else -int(x["max"]), + axis=1, + ) + df_congress["upper"] = df_congress[["min", "max", "Transaction"]].apply( + lambda x: int(x["max"]) + if x["Transaction"] == "Purchase" + else -int(x["min"]), + axis=1, + ) + + df_congress = df_congress.sort_values("TransactionDate", ascending=True) + + plot_congress(df_congress, ticker) + + print("") + + except Exception as e: + print(e, "\n") diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 30ef54f0af0..e1d14aae445 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -65,6 +65,7 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): print( " > po portfolio optimization, \t optimal portfolio weights from pyportfolioopt" ) + print(" > gov government menu, \t\t congress, senate, house trading") print(" > fx forex menu, \t\t\t forex support through Oanda") print(" > rc resource collection, \t\t e.g. hf letters") diff --git a/terminal.py b/terminal.py index 69e6e6f5919..03189c3b1fc 100644 --- a/terminal.py +++ b/terminal.py @@ -43,6 +43,7 @@ from gamestonk_terminal.forex import fx_controller from gamestonk_terminal.backtesting import bt_controller from gamestonk_terminal.resource_collection import rc_controller from gamestonk_terminal.research import res_controller +from gamestonk_terminal.government import gov_controller # pylint: disable=too-many-statements,too-many-branches @@ -99,6 +100,7 @@ def main(): "po", "fx", "rc", + "gov", ] menu_parser.add_argument("opt", choices=choices) @@ -378,6 +380,9 @@ def main(): elif ns_known_args.opt == "rc": b_quit = rc_controller.menu() + elif ns_known_args.opt == "gov": + b_quit = gov_controller.menu(s_ticker) + else: print("Shouldn't see this command!") continue