mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-13 04:41:24 +00:00
285 lines
8.3 KiB
Python
285 lines
8.3 KiB
Python
"""Portfolio Analysis Controller"""
|
|
__docformat__ = "numpy"
|
|
|
|
import argparse
|
|
import os
|
|
|
|
import pandas as pd
|
|
from prompt_toolkit.completion import NestedCompleter
|
|
|
|
from gamestonk_terminal import feature_flags as gtff
|
|
from gamestonk_terminal.helper_funcs import (
|
|
get_flair,
|
|
parse_known_args_and_warn,
|
|
try_except,
|
|
system_clear,
|
|
)
|
|
from gamestonk_terminal.menu import session
|
|
from gamestonk_terminal.portfolio.portfolio_analysis import (
|
|
portfolio_model,
|
|
portfolio_view,
|
|
)
|
|
|
|
portfolios_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "portfolios")
|
|
possible_paths = [
|
|
port
|
|
for port in os.listdir(portfolios_path)
|
|
if port.endswith(".csv") or port.endswith(".json") or port.endswith(".xlsx")
|
|
]
|
|
|
|
|
|
class PortfolioController:
|
|
"""Portfolio Controller"""
|
|
|
|
CHOICES = [
|
|
"cls",
|
|
"?",
|
|
"help",
|
|
"q",
|
|
"quit",
|
|
]
|
|
CHOICES_COMMANDS = [
|
|
"view",
|
|
"load",
|
|
"group",
|
|
]
|
|
|
|
CHOICES += CHOICES_COMMANDS
|
|
|
|
def __init__(self):
|
|
self.pa_parser = argparse.ArgumentParser(add_help=False, prog="pa")
|
|
self.pa_parser.add_argument("cmd", choices=self.CHOICES)
|
|
self.portfolio_name = ""
|
|
self.portfolio = pd.DataFrame()
|
|
|
|
def print_help(self):
|
|
"""Print help"""
|
|
help_string = f"""
|
|
>>PORTFOLIO ANALYSIS<<
|
|
|
|
What would you like to do?
|
|
cls clear screen
|
|
?/help show this menu again
|
|
q quit this menu, and shows back to main menu
|
|
quit quit to abandon program
|
|
|
|
view view available portfolios
|
|
load load portfolio from a file
|
|
|
|
Portfolio: {self.portfolio_name or None}
|
|
|
|
group view holdings grouped by parameter
|
|
"""
|
|
print(help_string)
|
|
|
|
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
|
|
|
|
(known_args, other_args) = self.pa_parser.parse_known_args(an_input.split())
|
|
|
|
# Help menu again
|
|
if known_args.cmd == "?":
|
|
self.print_help()
|
|
return
|
|
|
|
# Clear screen
|
|
if known_args.cmd == "cls":
|
|
system_clear()
|
|
return
|
|
|
|
getattr(
|
|
self,
|
|
"call_" + known_args.cmd,
|
|
lambda _: "Command not recognized!",
|
|
)(other_args)
|
|
|
|
return
|
|
|
|
def call_help(self, _):
|
|
"""Process Help command"""
|
|
self.print_help()
|
|
|
|
def call_q(self, _):
|
|
"""Process Q command - quit the menu"""
|
|
|
|
def call_quit(self, _):
|
|
"""Process Quit command - quit the program"""
|
|
|
|
@try_except
|
|
def call_load(self, other_args):
|
|
"""Process load command"""
|
|
parser = argparse.ArgumentParser(
|
|
prog="load",
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
description="Function to get portfolio from predefined csv/json/xlsx file inside portfolios folder",
|
|
epilog="usage: load file_name",
|
|
)
|
|
parser.add_argument(
|
|
"-s",
|
|
"--sector",
|
|
action="store_true",
|
|
default=False,
|
|
help="Add sector to dataframe",
|
|
dest="sector",
|
|
)
|
|
parser.add_argument(
|
|
"--no_last_price",
|
|
action="store_false",
|
|
default=True,
|
|
help="Don't add last price from yfinance",
|
|
dest="last_price",
|
|
)
|
|
parser.add_argument(
|
|
"--nan",
|
|
action="store_true",
|
|
default=False,
|
|
help="Show nan entries",
|
|
dest="show_nan",
|
|
)
|
|
parser.add_argument(
|
|
"-p",
|
|
"--path",
|
|
default="my_portfolio.csv",
|
|
choices=possible_paths,
|
|
help="Path to portfolio file",
|
|
dest="path",
|
|
)
|
|
|
|
if other_args and "-" not in other_args[0][0]:
|
|
other_args.insert(0, "-p")
|
|
|
|
ns_parser = parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
self.portfolio_name = ns_parser.path
|
|
self.portfolio = portfolio_model.load_portfolio(
|
|
full_path=os.path.join(portfolios_path, ns_parser.path),
|
|
sector=ns_parser.sector,
|
|
last_price=ns_parser.last_price,
|
|
show_nan=ns_parser.show_nan,
|
|
)
|
|
if not self.portfolio.empty:
|
|
print(f"Successfully loaded: {self.portfolio_name}\n")
|
|
|
|
@try_except
|
|
def call_group(self, other_args):
|
|
"""Process group command"""
|
|
parser = argparse.ArgumentParser(
|
|
prog="group",
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
description="Displays portfolio grouped by a given column",
|
|
)
|
|
if other_args and "-" not in other_args[0][0]:
|
|
other_args.insert(0, "-g")
|
|
parser.add_argument(
|
|
"-g",
|
|
"--group",
|
|
type=str,
|
|
dest="group",
|
|
default="Ticker",
|
|
choices=self.portfolio.columns,
|
|
help="Column to group by",
|
|
)
|
|
|
|
# The following arguments will be used in a later PR for customizable 'reports'
|
|
|
|
# The --func flag will need to be tested that it exists for pandas groupby
|
|
# parser.add_argument("-f",
|
|
# "--func",
|
|
# type=str,
|
|
# dest="function",
|
|
# help="Aggregate function to apply to groups"
|
|
# )
|
|
# parser.add_argument("-d",
|
|
# "--display",
|
|
# default = None,
|
|
# help = "Columns to display",
|
|
# dest="cols")
|
|
|
|
ns_parser = parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
if "value" in self.portfolio.columns:
|
|
portfolio_view.display_group_holdings(
|
|
portfolio=self.portfolio, group_column=ns_parser.group
|
|
)
|
|
else:
|
|
print(
|
|
"'value' column not in portfolio. Either add manually or load without --no_last_price flag\n"
|
|
)
|
|
|
|
@try_except
|
|
def call_view(self, other_args):
|
|
parser = argparse.ArgumentParser(
|
|
prog="view",
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
description="Show available portfolios to load.",
|
|
)
|
|
parser.add_argument(
|
|
"-f",
|
|
"-format",
|
|
choices=["csv", "json", "xlsx", "all"],
|
|
help="Format of portfolios to view. 'csv' will show all csv files available, etc.",
|
|
default="all",
|
|
dest="file_format",
|
|
)
|
|
|
|
ns_parser = parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
available_ports = os.listdir(portfolios_path)
|
|
if ns_parser.file_format != "all":
|
|
available_ports = [
|
|
port
|
|
for port in available_ports
|
|
if port.endswith(ns_parser.file_format)
|
|
]
|
|
|
|
print("\nAvailable Portfolios:\n")
|
|
for port in available_ports:
|
|
print(port)
|
|
print("")
|
|
|
|
|
|
def menu():
|
|
"""Portfolio Analysis Menu"""
|
|
pa_controller = PortfolioController()
|
|
pa_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 pa_controller.CHOICES}
|
|
)
|
|
try:
|
|
an_input = session.prompt(
|
|
f"{get_flair()} (portfolio)>(pa)> ",
|
|
completer=completer,
|
|
)
|
|
except KeyboardInterrupt:
|
|
# Exit in case of keyboard interrupt
|
|
an_input = "exit"
|
|
else:
|
|
an_input = input(f"{get_flair()} (portfolio)>(pa)> ")
|
|
|
|
try:
|
|
pa_controller.switch(an_input)
|
|
|
|
except SystemExit:
|
|
print("The command selected doesn't exist\n")
|
|
continue
|