mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-02 23:41:27 +00:00
439 lines
15 KiB
Python
439 lines
15 KiB
Python
"""Mutual Fund Controller"""
|
|
__docformat__ = "numpy"
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Optional
|
|
|
|
import pandas as pd
|
|
|
|
from openbb_terminal.core.session.current_user import get_current_user
|
|
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
|
|
from openbb_terminal.decorators import log_start_end
|
|
from openbb_terminal.helper_funcs import (
|
|
EXPORT_ONLY_FIGURES_ALLOWED,
|
|
check_positive,
|
|
lower_str,
|
|
valid_date,
|
|
)
|
|
from openbb_terminal.menu import session
|
|
from openbb_terminal.mutual_funds import avanza_view, mstarpy_view
|
|
from openbb_terminal.mutual_funds.mutual_funds_utils import mapping_country
|
|
from openbb_terminal.parent_classes import BaseController
|
|
from openbb_terminal.rich_config import MenuText, console
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class FundController(BaseController):
|
|
"""Fund Controller class"""
|
|
|
|
CHOICES_COMMANDS = [
|
|
"resources",
|
|
"country",
|
|
"search",
|
|
"load",
|
|
"plot",
|
|
"sector",
|
|
"alswe",
|
|
"infoswe",
|
|
"holdings",
|
|
"carbon",
|
|
"exclusion",
|
|
]
|
|
|
|
fund_countries = list(mapping_country.keys())
|
|
PATH = "/funds/"
|
|
FILE_PATH = os.path.join(os.path.dirname(__file__), "README.md")
|
|
CHOICES_GENERATION = True
|
|
|
|
def __init__(self, queue: Optional[List[str]] = None):
|
|
"""Constructor"""
|
|
super().__init__(queue)
|
|
|
|
self.country = "united_states"
|
|
self.funds_loaded = None
|
|
self.fund_name = ""
|
|
self.fund_symbol = ""
|
|
self.fund_isin = ""
|
|
self.TRY_RELOAD = True
|
|
self.end_date = ""
|
|
self.start_date = ""
|
|
|
|
if session and get_current_user().preferences.USE_PROMPT_TOOLKIT:
|
|
choices: dict = self.choices_default
|
|
choices["country"].update({c: {} for c in self.fund_countries})
|
|
self.completer = NestedCompleter.from_nested_dict(choices)
|
|
|
|
def print_help(self):
|
|
"""Print help"""
|
|
if self.fund_name:
|
|
fund_string = (
|
|
f"{self.fund_name} ({self.fund_symbol})"
|
|
if self.fund_symbol
|
|
else f"{self.fund_name}"
|
|
)
|
|
else:
|
|
fund_string = ""
|
|
mt = MenuText("funds/")
|
|
mt.add_cmd("country")
|
|
mt.add_raw("\n")
|
|
mt.add_param("_country", self.country)
|
|
mt.add_raw("\n")
|
|
mt.add_cmd("search")
|
|
mt.add_cmd("load")
|
|
mt.add_raw("\n")
|
|
mt.add_param("_fund", fund_string)
|
|
mt.add_raw("\n")
|
|
mt.add_cmd("plot", self.fund_symbol)
|
|
mt.add_cmd("sector", self.fund_symbol)
|
|
mt.add_cmd("holdings", self.fund_symbol)
|
|
mt.add_cmd("carbon", self.fund_symbol)
|
|
mt.add_cmd("exclusion", self.fund_symbol)
|
|
mt.add_cmd("alswe", self.fund_symbol and self.country == "sweden")
|
|
mt.add_cmd("infoswe", self.fund_symbol and self.country == "sweden")
|
|
|
|
console.print(text=mt.menu_text, menu="Mutual Funds")
|
|
|
|
def custom_reset(self):
|
|
"""Class specific component of reset command"""
|
|
if self.fund_symbol and self.country:
|
|
return ["funds", f"country {self.country}", f"load {self.fund_symbol}"]
|
|
return []
|
|
|
|
@log_start_end(log=logger)
|
|
def call_alswe(self, other_args: List[str]):
|
|
"""Process alswe command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="alswe",
|
|
description="Show allocation of a swedish fund. "
|
|
"To get a list of available funds, check the file `avanza_fund_ID.csv`.",
|
|
)
|
|
parser.add_argument(
|
|
"--focus",
|
|
dest="focus",
|
|
type=str,
|
|
choices=["all", "country", "sector", "holding"],
|
|
default="all",
|
|
help="The focus of the funds exposure/allocation",
|
|
)
|
|
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
ava_fund = pd.read_csv(
|
|
os.path.join("openbb_terminal", "mutual_funds", "avanza_fund_ID.csv"),
|
|
index_col=0,
|
|
)
|
|
if self.country != "sweden":
|
|
console.print(
|
|
"Avanza implementation currently only supports funds from sweden."
|
|
)
|
|
return self.queue
|
|
|
|
if self.fund_isin not in ava_fund["ISIN"].tolist():
|
|
console.print("No fund data. Please use another fund.")
|
|
return self.queue
|
|
|
|
avanza_view.display_allocation(
|
|
name=self.fund_name, isin=self.fund_isin, focus=ns_parser.focus
|
|
)
|
|
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_infoswe(self, other_args: List[str]):
|
|
"""Process infoswe command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="infoswe",
|
|
description="Show fund info of a swedish fund. "
|
|
"To get a list of available funds, check the file `avanza_fund_ID.csv`.",
|
|
)
|
|
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
ava_fund = pd.read_csv(
|
|
os.path.join("openbb_terminal", "mutual_funds", "avanza_fund_ID.csv"),
|
|
index_col=0,
|
|
)
|
|
if self.country != "sweden":
|
|
console.print(
|
|
"Avanza implementation currently only supports funds from sweden."
|
|
)
|
|
return self.queue
|
|
|
|
if self.fund_isin not in ava_fund["ISIN"].tolist():
|
|
console.print("No fund data. Please use another fund.")
|
|
return self.queue
|
|
|
|
avanza_view.display_info(self.fund_isin)
|
|
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_country(self, other_args: List[str]):
|
|
"""Process country command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="country",
|
|
description="Set a country for funds.",
|
|
)
|
|
parser.add_argument(
|
|
"-n",
|
|
"--name",
|
|
type=lower_str,
|
|
choices=self.fund_countries,
|
|
dest="name",
|
|
help="country to select",
|
|
default="united_states",
|
|
metavar="NAME",
|
|
)
|
|
if other_args and "-" not in other_args[0][0]:
|
|
other_args.insert(0, "-n")
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
country_candidate = ns_parser.name.lower()
|
|
if country_candidate in self.fund_countries:
|
|
self.country = country_candidate
|
|
console.print(f"'{country_candidate}' selected.")
|
|
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_search(self, other_args: List[str]):
|
|
"""Process search command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="search",
|
|
description="Search mutual funds in selected country",
|
|
)
|
|
parser.add_argument(
|
|
"--fund",
|
|
help="Fund string to search for",
|
|
dest="fund",
|
|
type=str,
|
|
required="-h" not in other_args,
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-l",
|
|
"--limit",
|
|
help="Number of search results to show",
|
|
type=check_positive,
|
|
dest="limit",
|
|
default=10,
|
|
)
|
|
if other_args and "-" not in other_args[0][0]:
|
|
other_args.insert(0, "--fund")
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
mstarpy_view.display_search(
|
|
term=ns_parser.fund,
|
|
country=self.country,
|
|
limit=ns_parser.limit,
|
|
)
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_load(self, other_args: List[str]):
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="load",
|
|
description="Load the fund to perform analysis on.",
|
|
)
|
|
parser.add_argument(
|
|
"--fund",
|
|
help="Fund string to search for",
|
|
dest="fund",
|
|
type=str,
|
|
required="-h" not in other_args,
|
|
)
|
|
parser.add_argument(
|
|
"-s",
|
|
"--start",
|
|
type=valid_date,
|
|
default=(datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d"),
|
|
dest="start",
|
|
help="The starting date (format YYYY-MM-DD) of the stock",
|
|
)
|
|
parser.add_argument(
|
|
"-e",
|
|
"--end",
|
|
type=valid_date,
|
|
default=datetime.now().strftime("%Y-%m-%d"),
|
|
dest="end",
|
|
help="The ending date (format YYYY-MM-DD) of the stock",
|
|
)
|
|
if other_args and "-" not in other_args[0][0]:
|
|
other_args.insert(0, "--fund")
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
if not self.country:
|
|
console.print(
|
|
"[yellow]Loading without a country selected "
|
|
" might result in unexpected results.[/yellow]"
|
|
)
|
|
|
|
funds_loaded = mstarpy_view.display_load(
|
|
term=ns_parser.fund, country=self.country
|
|
)
|
|
|
|
if funds_loaded:
|
|
self.funds_loaded = funds_loaded
|
|
self.fund_name = funds_loaded.name
|
|
self.fund_symbol = funds_loaded.code
|
|
self.fund_isin = funds_loaded.isin
|
|
self.end_date = ns_parser.end.strftime("%Y-%m-%d")
|
|
self.start_date = ns_parser.start.strftime("%Y-%m-%d")
|
|
|
|
console.print(
|
|
f"The fund {self.fund_name} - {self.fund_isin} ({self.fund_symbol})"
|
|
" was successfully loaded."
|
|
)
|
|
|
|
else:
|
|
console.print("No funds were loaded.")
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_plot(self, other_args: List[str]):
|
|
"""Process plot command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="plot",
|
|
description="Plot historical data.",
|
|
)
|
|
parser.add_argument(
|
|
"-c",
|
|
default="",
|
|
choices=["category", "index", "both"],
|
|
dest="compare",
|
|
type=str.lower,
|
|
help="Compare funds performance with its category or index",
|
|
)
|
|
ns_parser = self.parse_known_args_and_warn(
|
|
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
|
|
)
|
|
if ns_parser:
|
|
if not self.fund_symbol:
|
|
console.print("No fund loaded. Please use `load` first to plot.")
|
|
return self.queue
|
|
mstarpy_view.display_historical(
|
|
loaded_funds=self.funds_loaded,
|
|
start_date=datetime.strptime(self.start_date, "%Y-%m-%d"),
|
|
end_date=datetime.strptime(self.end_date, "%Y-%m-%d"),
|
|
comparison=ns_parser.compare,
|
|
)
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_sector(self, other_args: List[str]):
|
|
"""Process plot command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="sector",
|
|
description="Show funds, index, category sector breakdown.",
|
|
)
|
|
parser.add_argument(
|
|
"-t",
|
|
"--type",
|
|
type=str,
|
|
choices=["equity", "fixed_income"],
|
|
dest="type",
|
|
help="asset type to select",
|
|
default="equity",
|
|
)
|
|
if other_args and "-" not in other_args[0][0]:
|
|
other_args.insert(0, "-t")
|
|
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
|
|
if ns_parser:
|
|
if not self.fund_symbol:
|
|
console.print("No fund loaded. Please use `load` first.")
|
|
return self.queue
|
|
|
|
mstarpy_view.display_sector(self.funds_loaded, asset_type=ns_parser.type)
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_holdings(self, other_args: List[str]):
|
|
"""Process holdings command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="holdings",
|
|
description="Show funds holdings.",
|
|
)
|
|
parser.add_argument(
|
|
"-t",
|
|
"--type",
|
|
type=str,
|
|
choices=["all", "equity", "bond", "other"],
|
|
dest="type",
|
|
help="type of holdings",
|
|
default="all",
|
|
)
|
|
if other_args and "-" not in other_args[0][0]:
|
|
other_args.insert(0, "-t")
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
|
|
if ns_parser:
|
|
if not self.fund_symbol:
|
|
console.print("No fund loaded. Please use `load` first.")
|
|
return self.queue
|
|
|
|
mstarpy_view.display_holdings(self.funds_loaded, ns_parser.type)
|
|
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_carbon(self, other_args: List[str]):
|
|
"""Process carbon_metrics command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="carbon",
|
|
description="Show funds carbon metrcis.",
|
|
)
|
|
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
if not self.fund_symbol:
|
|
console.print("No fund loaded. Please use `load` first.")
|
|
return self.queue
|
|
|
|
mstarpy_view.display_carbon_metrics(self.funds_loaded)
|
|
|
|
return self.queue
|
|
|
|
@log_start_end(log=logger)
|
|
def call_exclusion(self, other_args: List[str]):
|
|
"""Process exclusion_policy command"""
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
prog="exclusion",
|
|
description="Show funds exclsuion policy.",
|
|
)
|
|
|
|
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
|
if ns_parser:
|
|
if not self.fund_symbol:
|
|
console.print("No fund loaded. Please use `load` first to plot.")
|
|
return self.queue
|
|
|
|
mstarpy_view.display_exclusion_policy(self.funds_loaded)
|
|
|
|
return self.queue
|