diff options
author | jmaslek <jmaslek11@gmail.com> | 2021-09-23 13:50:38 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-23 13:50:38 -0400 |
commit | eafd65199b18d80a57d8f2d0a2be64f7e067097d (patch) | |
tree | 2f42a114e0ee5f96f571cb9716b1009ff5322b49 | |
parent | 4a50ac101975402c46b1ead791257c04abc91806 (diff) |
Major brokers refactor into separate menus (#754)
* Major brokers refactor into separate menus
* Add portfolio export folder
* Added some extra commands from pyally
* Update coinbase account command
* poetry update + pin openpyxl to 3.0.7
* requirement files
* Update hugo server
* Clean hugo files
* Update _index.md
Add output to _index
* Update _index.md
* Update _index.md
add output
* Update _index.md
add output
* Update _index.md
* Update _index.md
* Update _index.md
* Update _index.md
* Update _index.md
* Update _index.md
* Fix alpaca bug
* Fix hugo server + add to main menu
40 files changed, 1984 insertions, 784 deletions
diff --git a/exports/portfolio/.gitkeep b/exports/portfolio/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/exports/portfolio/.gitkeep diff --git a/gamestonk_terminal/portfolio/brokers/ally/ally_controller.py b/gamestonk_terminal/portfolio/brokers/ally/ally_controller.py new file mode 100644 index 00000000000..b71d7b378a7 --- /dev/null +++ b/gamestonk_terminal/portfolio/brokers/ally/ally_controller.py @@ -0,0 +1,298 @@ +"""Ally Controller""" +__docformat__ = "numpy" + +import argparse +import os +from typing import List +from prompt_toolkit.completion import NestedCompleter +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.menu import session +from gamestonk_terminal.portfolio.brokers.ally import ally_view +from gamestonk_terminal.helper_funcs import ( + get_flair, + parse_known_args_and_warn, +) + + +class AllyController: + + CHOICES = [ + "?", + "cls", + "help", + "q", + "quit", + ] + + ALLY_CHOICES = ["holdings", "history", "balances"] + ALLY_STOCK_CHOICES = ["quote", "movers"] + + def __init__(self): + """CONSTRUCTOR""" + + self._ally_parser = argparse.ArgumentParser(add_help=False, prog="ally") + self.CHOICES.extend(self.ALLY_CHOICES) + self.CHOICES.extend(self.ALLY_STOCK_CHOICES) + self._ally_parser.add_argument("cmd", choices=self.CHOICES) + + def print_help(self): + """Print help""" + help_text = """ +Ally: + cls clear screen + ?/help show this menu again + q quit this menu, and shows back to main menu + quit quit to abandon the program + + holdings show account holdings + history show history of your account + balances show balance details of account + +Stock Information: + quote get stock quote + movers get ranked lists of movers +""" + + print(help_text) + + 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 + """ + + # Empty command + if not an_input: + print("") + return None + + (known_args, other_args) = self._ally_parser.parse_known_args(an_input.split()) + + # Help menu again + if known_args.cmd == "?": + self.print_help() + return None + + # Clear screen + if known_args.cmd == "cls": + os.system("cls||clear") + return None + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help() + + 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_holdings(self, other_args: List[str]): + """Process holdings command""" + parser = argparse.ArgumentParser( + prog="holdings", + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Display info about your trading accounts on Ally", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + ally_view.display_holdings(export=ns_parser.export) + except Exception as e: + print(e, "\n") + + def call_history(self, other_args: List[str]): + """Process history command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="history", + description="""Account transaction history""", + ) + parser.add_argument( + "-n", + "--num", + dest="num", + default=15, + type=int, + help="Number of recent transactions to show", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + ally_view.display_history(n_to_show=ns_parser.num, export=ns_parser.export) + except Exception as e: + print(e, "\n") + + def call_balances(self, other_args: List[str]): + """Process balances command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="balances", + description="""Account balance details""", + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + ally_view.display_balances(export=ns_parser.export) + + except Exception as e: + print(e, "\n") + + def call_quote(self, other_args: List[str]): + """Process balances command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="quote", + description="""Get stock quote""", + ) + if other_args and "-" not in other_args[0]: + other_args.insert(0, "-t") + parser.add_argument( + "-t", + "--ticker", + help="Ticker to get quote for. Can be in form of 'tick1,tick2...'", + type=str, + dest="ticker", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + ally_view.display_stock_quote(ns_parser.ticker) + + except Exception as e: + print(e, "\n") + + def call_movers(self, other_args: List[str]): + """Process movers command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="movers", + description="""Get stock movers""", + ) + parser.add_argument( + "-l", + "--list", + help="List to get movers of", + choices=[ + "toplosers", + "toppctlosers", + "topvolume", + "topactive", + "topgainers", + "toppctgainers", + ], + default="topactive", + dest="list_type", + ) + parser.add_argument( + "-e", + "--exchange", + help="""Exchange to look at. Can be + A:American Stock Exchange. + N:New York Stock Exchange. + Q:NASDAQ + U:NASDAQ Bulletin Board + V:NASDAQ OTC Other""", + choices=["A", "N", "Q", "U", "V"], + default="N", + dest="exchange", + ) + parser.add_argument( + "-n", "--num", help="Number to show", type=int, default=15, dest="num" + ) + parser.add_argument( + "--export", + choices=["csv", "json", "xlsx"], + default="", + type=str, + dest="export", + help="Export dataframe data to csv,json,xlsx file", + ) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + ally_view.display_top_lists( + list_type=ns_parser.list_type, + exchange=ns_parser.exchange, + num_to_show=ns_parser.num, + export=ns_parser.export, + ) + + except Exception as e: + print(e, "\n") + + +def menu(): + + ally_controller = AllyController() + ally_controller.print_help() + + while True: + # Get input command from user + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in ally_controller.CHOICES} + ) + an_input = session.prompt( + f"{get_flair()} (bro)>(ally)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (bro)>(ally)> ") + + try: + process_input = ally_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/portfolio/brokers/ally/ally_model.py b/gamestonk_terminal/portfolio/brokers/ally/ally_model.py new file mode 100644 index 00000000000..3872d8af8fc --- /dev/null +++ b/gamestonk_terminal/portfolio/brokers/ally/ally_model.py @@ -0,0 +1,110 @@ +"""Ally Model""" +__docformat__ = "numpy" + +import ally +import pandas as pd + + +def get_holdings() -> pd.DataFrame: + """Get holdings from Ally account in pandas df + + Returns + ------- + pd.DataFrame + Dataframe of positions + """ + a = ally.Ally() + return ally_positions_to_df(a.holdings(dataframe=True)) + + +def ally_positions_to_df(df: pd.DataFrame) -> pd.DataFrame: + """Clean up ally holdings dataframe + + Parameters + ---------- + df : pd.DataFrame + Input dataframe of holdings + + Returns + ------- + pd.DataFrame + Processed holdings + """ + names = { + "costbasis": "CostBasis", + "marketvalue": "MarketValue", + "sym": "Symbol", + "qty": "Quantity", + } + df = df.loc[:, ["qty", "costbasis", "marketvalue", "sym"]] + df[["qty", "costbasis", "marketvalue"]] = df[ + ["qty", "costbasis", "marketvalue"] + ].astype(float) + df = df.rename(columns=names) + df["PnL"] = df["MarketValue"] - df["CostBasis"] + return df + + +def get_history() -> pd.DataFrame: + """Gets transaction history for the account." + + Returns + ------- + pd.DataFrame + Dataframe of transaction history + """ + a = ally.Ally() + return a.history(dataframe=True) + + +def get_balances() -> pd.DataFrame: + """Gets balance details for the account." + + Returns + ------- + pd.DataFrame + Dataframe of transaction history + """ + a = ally.Ally() + return a.balances(dataframe=True) + + +def get_stock_quote(ticker: str) -> pd.DataFrame: + """Gets quote for stock ticker + + Parameters + ---------- + ticker : str + Ticker to get. Can be in form of 'tick1,tick2...' + Returns + ------- + pd.DataFrame + Dataframe of ticker quote + """ + a = ally.Ally() + return a.quote( + ticker, + fields=["last", "bid", "ask", "opn", "dollar_value", "chg", "vl"], + dataframe=True, + ) + + +def get_top_movers(list_type: str, exchange: str) -> pd.DataFrame: + """ + Gets top lists from ally Invest API. Documentation for parameters below: + https://www.ally.com/api/invest/documentation/market-toplists-get/ + + Parameters + ---------- + list_type : str + Which list to get data for + exchange : str + Which exchange to look at + + Returns + ------- + pd.DataFrame + DataFrame of top movers + """ + a = ally.Ally() + return a.toplists(list_type, exchange, dataframe=True) diff --git a/gamestonk_terminal/portfolio/brokers/ally/ally_view.py b/gamestonk_terminal/portfolio/brokers/ally/ally_view.py new file mode 100644 index 00000000000..1bcac72d52e --- /dev/null +++ b/gamestonk_terminal/portfolio/brokers/ally/ally_view.py @@ -0,0 +1,161 @@ +"""Ally View""" +__docformat__ = "numpy" + +import os +from tabulate import tabulate +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal.portfolio.brokers.ally import ally_model + + +def display_history(n_to_show: int = 15, export: str = ""): + history = ally_model.get_history() + show_history = history[["amount", "date", "symbol", "transactiontype", "quantity"]] + if gtff.USE_TABULATE_DF: + print( + tabulate( + show_history.tail(n_to_show), + headers=show_history.columns, + tablefmt="fancy_grid", + floatfmt=".2f", + showindex=False, + ) + ) + else: + print(show_history.tail(n_to_show).to_string()) + print("") + export_data( + export, os.path.dirname(os.path.abspath(__file__)), "ally_history", history + ) + + +def display_holdings(export: str = ""): + """Display holdings from ally account + + Parameters + ---------- + export : str, optional + Format to export data, by default "" + """ + holdings = ally_model.get_holdings() + holdings = holdings.set_index("Symbol") + if gtff.USE_TABULATE_DF: + print( + tabulate( + holdings, + headers=holdings.columns, + tablefmt="fancy_grid", + floatfmt=".2f", + showindex=True, + ) + ) + else: + print(holdings.to_string()) + print("") + export_data( + export, + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + "ally_holdings", + holdings, + ) + + +def display_balances(export: str = ""): + """Display balances from ally account + + Parameters + ---------- + export : str, optional + Format to export data, by default "" + """ + balances = ally_model.get_balances() + # Export data here before picking out certain columns + export_data( + export, + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + "ally_balances", + balances, + ) + # Pick which balances to show + balances = balances[ + [ + "accountvalue", + "buyingpower.stock", + "money.cash", + "securities.stocks", + "securities.total", + ] + ] + if gtff.USE_TABULATE_DF: + print( + tabulate( + balances, + headers=balances.columns, + tablefmt="fancy_grid", + floatfmt=".2f", + showindex=False, + ) + ) + else: + print(balances.to_string()) + print("") + + +def display_stock_quote(ticker: str): + """Displays stock quote for ticker/tickers + + Parameters + ---------- + ticker : str + Ticker to get. Can be in form of 'tick1,tick2...' + """ + quote = ally_model.get_stock_quote(ticker) + if gtff.USE_TABULATE_DF: + print( + tabulate( + quote, tablefmt="fancy_grid", headers=quote.columns, showindex=True + ) + ) + else: + print(quote.to_string()) + print("") + + +def display_top_lists( + list_type: str, exchange: str, num_to_show: int = 20, export: str = "" +): + """< |