diff --git a/openbb_terminal/common/quantitative_analysis/qa_model.py b/openbb_terminal/common/quantitative_analysis/qa_model.py index 24aa11eee12..a50e7cc77c0 100644 --- a/openbb_terminal/common/quantitative_analysis/qa_model.py +++ b/openbb_terminal/common/quantitative_analysis/qa_model.py @@ -231,7 +231,7 @@ def get_var( percentile: Union[int, float] = 99.9, portfolio: bool = False, ) -> pd.DataFrame: - """Gets value at risk for specified stock dataframe + """Gets value at risk for specified stock dataframe. Parameters ---------- @@ -250,10 +250,8 @@ def get_var( Returns ------- - list - list of VaR - list - list of historical VaR + pd.DataFrame + DataFrame with Value at Risk per percentile """ if not portfolio: data = data[["adjclose"]].copy() @@ -310,10 +308,10 @@ def get_var( else: # Regular Var - var_90 = np.exp(mean + percentile_90 * std) - 1 - var_95 = np.exp(mean + percentile_95 * std) - 1 - var_99 = np.exp(mean + percentile_99 * std) - 1 - var_custom = np.exp(mean + percentile_custom * std) - 1 + var_90 = mean + percentile_90 * std + var_95 = mean + percentile_95 * std + var_99 = mean + percentile_99 * std + var_custom = mean + percentile_custom * std if not portfolio: data.sort_values("return", inplace=True, ascending=True) @@ -328,17 +326,22 @@ def get_var( hist_var_99 = data_return.quantile(0.01) hist_var_custom = data_return.quantile(1 - percentile) - var_list = [var_90, var_95, var_99, var_custom] - hist_var_list = [hist_var_90, hist_var_95, hist_var_99, hist_var_custom] + var_list = [var_90 * 100, var_95 * 100, var_99 * 100, var_custom * 100] + hist_var_list = [ + hist_var_90 * 100, + hist_var_95 * 100, + hist_var_99 * 100, + hist_var_custom * 100, + ] - str_hist_label = "Historical VaR" + str_hist_label = "Historical VaR [%]" if adjusted_var: - str_var_label = "Adjusted VaR" + str_var_label = "Adjusted VaR [%]" elif student_t: - str_var_label = "Student-t VaR" + str_var_label = "Student-t VaR [%]" else: - str_var_label = "VaR" + str_var_label = "Gaussian VaR [%]" data_dictionary = {str_var_label: var_list, str_hist_label: hist_var_list} df = pd.DataFrame( @@ -346,6 +349,7 @@ def get_var( ) df.sort_index(inplace=True) + df = df.replace(np.nan, "-") return df @@ -357,7 +361,7 @@ def get_es( percentile: Union[float, int] = 99.9, portfolio: bool = False, ) -> pd.DataFrame: - """Gets Expected Shortfall for specified stock dataframe + """Gets Expected Shortfall for specified stock dataframe. Parameters ---------- @@ -374,10 +378,8 @@ def get_es( Returns ------- - list - list of ES - list - list of historical ES + pd.DataFrame + DataFrame with Expected Shortfall per percentile """ if not portfolio: data = data[["adjclose"]].copy() @@ -497,26 +499,31 @@ def get_es( # Historical Expected Shortfall df = get_var(data, use_mean, False, False, percentile, portfolio) - hist_var_list = list(df["Historical VaR"].values) + hist_var_list = list(df["Historical VaR [%]"].values) hist_es_90 = data_return[data_return <= hist_var_list[0]].mean() hist_es_95 = data_return[data_return <= hist_var_list[1]].mean() hist_es_99 = data_return[data_return <= hist_var_list[2]].mean() hist_es_custom = data_return[data_return <= hist_var_list[3]].mean() - es_list = [es_90, es_95, es_99, es_custom] - hist_es_list = [hist_es_90, hist_es_95, hist_es_99, hist_es_custom] + es_list = [es_90 * 100, es_95 * 100, es_99 * 100, es_custom * 100] + hist_es_list = [ + hist_es_90 * 100, + hist_es_95 * 100, + hist_es_99 * 100, + hist_es_custom * 100, + ] - str_hist_label = "Historical ES" + str_hist_label = "Historical ES [%]" if distribution == "laplace": - str_es_label = "Laplace ES" + str_es_label = "Laplace ES [%]" elif distribution == "student_t": - str_es_label = "Student-t ES" + str_es_label = "Student-t ES [%]" elif distribution == "logistic": - str_es_label = "Logistic ES" + str_es_label = "Logistic ES [%]" else: - str_es_label = "ES" + str_es_label = "ES [%]" data_dictionary = {str_es_label: es_list, str_hist_label: hist_es_list} df = pd.DataFrame( @@ -524,6 +531,7 @@ def get_es( ) df.sort_index(inplace=True) + df = df.replace(np.nan, "-") return df diff --git a/openbb_terminal/common/quantitative_analysis/qa_view.py b/openbb_terminal/common/quantitative_analysis/qa_view.py index e83a24d0afe..f5dd188fec4 100644 --- a/openbb_terminal/common/quantitative_analysis/qa_view.py +++ b/openbb_terminal/common/quantitative_analysis/qa_view.py @@ -1013,7 +1013,7 @@ def display_var( data_range: int = 0, portfolio: bool = False, ) -> None: - """Displays VaR of dataframe + """Displays VaR of dataframe. Parameters ---------- @@ -1059,7 +1059,7 @@ def display_var( show_index=True, headers=list(df.columns), title=f"[bold]{symbol}{str_title}Value at Risk[/bold]", - floatfmt=".4f", + floatfmt=".2f", ) @@ -1071,7 +1071,7 @@ def display_es( percentile: float = 99.9, portfolio: bool = False, ) -> None: - """Displays expected shortfall + """Displays expected shortfall. Parameters ---------- @@ -1107,7 +1107,7 @@ def display_es( show_index=True, headers=list(df.columns), title=f"[bold]{symbol}{str_title}Expected Shortfall[/bold]", - floatfmt=".4f", + floatfmt=".2f", ) diff --git a/openbb_terminal/miscellaneous/i18n/en.yml b/openbb_terminal/miscellaneous/i18n/en.yml index c02cbda3c2a..fa0432e3275 100644 --- a/openbb_terminal/miscellaneous/i18n/en.yml +++ b/openbb_terminal/miscellaneous/i18n/en.yml @@ -905,8 +905,8 @@ en: econometrics/coint: co-integration test on a multitude of columns portfolio/bro: brokers holdings, supports robinhood, ally, degiro, coinbase portfolio/po: portfolio optimization, optimize your portfolio weights efficiently - portfolio/load: load data into the portfolio - portfolio/_loaded: Loaded orderbook + portfolio/load: load transactions into the portfolio + portfolio/_loaded: Loaded transactions portfolio/_riskfreerate: Risk Free Rate portfolio/show: show existing transactions portfolio/bench: define the benchmark diff --git a/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio.openbb b/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio.openbb index 2f0c23d17bd..3ca3c51cdcb 100644 --- a/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio.openbb +++ b/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio.openbb @@ -1,30 +1,110 @@ portfolio load -f Public_Equity_Orderbook.xlsx show -bench SPDR S&P 500 ETF Trust (SPY) -alloc assets -alloc sectors -alloc countries -alloc regions +bench Vanguard Total Stock Market ETF (VTI) +alloc assets -t -l 4 +alloc sectors -t -l 4 +alloc countries -t -l 4 +alloc regions -t -l 4 +summary -p mtd -r 0.5 +summary -p qtd -r 0.5 +summary -p ytd -r 0.5 +summary -p 3m -r 0.5 +summary -p 6m -r 0.5 +summary -p 1y -r 0.5 +summary -p 5y -r 0.5 +summary -p 10y -r 0.5 +summary -p all -r 0.5 +metric volatility +metric sharpe -r 0.5 +metric sortino -r 0.5 +metric maxdrawdown +metric rsquare +metric skew +metric kurtosis +metric gaintopain +metric trackerr +metric information +metric tail +metric commonsense +metric jensens +metric calmar +metric kelly +metric payoff +metric profitfactor perf perf -t -rolling -rmr -dd -rvol -rsharpe -rsort -rbeta +holdv +holdv --unstack --raw -l 4 +holdp +holdp --unstack --raw -l 4 +yret +yret -p 3y +yret -p 5y +yret -p 10y +yret -p all --raw +mret +mret -p 3y -s +mret -p 5y -s +mret -p 10y -s +mret -p all -s +mret -p all -s --raw +dret +dret -p 3y +dret -p 5y +dret -p 10y +dret -p all --raw -l 5 +distr -p mtd +distr -p qtd +distr -p ytd +distr -p 3m +distr -p 6m +distr -p 1y +distr -p 5y +distr -p 10y +distr -p all +maxdd rvol -p mtd -rsharpe -p qtd -rsort -p ytd +rvol -p qtd +rvol -p ytd +rvol -p 3m +rvol -p 6m +rvol -p 1y +rvol -p 5y +rvol -p 10y +rvol -p all +rsharpe -p mtd -r 0.5 +rsharpe -p qtd -r 0.5 +rsharpe -p ytd -r 0.5 +rsharpe -p 3m -r 0.5 +rsharpe -p 6m -r 0.5 +rsharpe -p 1y -r 0.5 +rsharpe -p 5y -r 0.5 +rsharpe -p 10y -r 0.5 +rsharpe -p all -r 0.5 +rsort -p mtd -r 0.5 +rsort -p qtd -r 0.5 +rsort -p ytd -r 0.5 +rsort -p 3m -r 0.5 +rsort -p 6m -r 0.5 +rsort -p 1y -r 0.5 +rsort -p 5y -r 0.5 +rsort -p 10y -r 0.5 +rsort -p all -r 0.5 +rbeta -p mtd +rbeta -p qtd +rbeta -p ytd +rbeta -p 3m +rbeta -p 6m +rbeta -p 1y +rbeta -p 5y +rbeta -p 10y rbeta -p all var var -p 80 es es -p 50 -sh -so om +om --start 0.05 --end 0.2 q -exit \ No newline at end of file +exit diff --git a/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb b/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb new file mode 100644 index 00000000000..8029c4461b5 --- /dev/null +++ b/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb @@ -0,0 +1,23 @@ +portfolio +po +load OpenBB_Portfolio_Template_v1.0.0.xlsx +file --file OpenBB_Parameters_Template_v1.0.0.xlsx +file -f defaults.ini +params +set maxsharpe +arg log_returns True +.. +maxsharpe +minrisk +maxutil +maxret +maxdiv +maxdecorr +ef +riskparity +relriskparity +hrp +herc +nco +equal +exit diff --git a/openbb_terminal/portfolio/allocation_model.py b/openbb_terminal/portfolio/allocation_model.py index 7f87fe9357e..c4f2616e7c9 100644 --- a/openbb_terminal/portfolio/allocation_model.py +++ b/openbb_terminal/portfolio/allocation_model.py @@ -3,6 +3,7 @@ from typing import Dict import pandas as pd import requests +from tqdm import tqdm import yfinance as yf from openbb_terminal.decorators import log_start_end from openbb_terminal.rich_config import console @@ -22,7 +23,7 @@ def get_assets_allocation(benchmark_info: Dict, portfolio_trades: pd.DataFrame): Object containing trades made within the portfolio. Returns - ------ + ------- benchmark_assets_allocation: dict Dictionary with the top 10 of the benchmark's asset allocations. portfolio_assets_allocation: dict @@ -61,14 +62,14 @@ def get_sector_allocation(benchmark_info: Dict, portfolio_trades: pd.DataFrame): Object containing trades made within the portfolio. Returns - ------ + ------- regional_allocation: dict Dictionary with regional allocations. country_allocation: dict Dictionary with country allocations """ - console.print(" Loading sector data: ", end="") + p_bar = tqdm(range(3), desc="Loading sector data") benchmark_sectors_allocation = ( pd.DataFrame.from_dict( @@ -98,6 +99,8 @@ def get_sector_allocation(benchmark_info: Dict, portfolio_trades: pd.DataFrame): .groupby(by="Sector") .agg({"Portfolio Value": "sum"}) ) + p_bar.n += 1 + p_bar.refresh() # Aggregate sector value for ETFs # Start by getting value by isin/ticker @@ -144,8 +147,8 @@ def get_sector_allocation(benchmark_info: Dict, portfolio_trades: pd.DataFrame): etf_global_sector_alloc = etf_global_sector_alloc.sum(axis=1) - console.print(".", end="") - console.print("\n") + p_bar.n += 1 + p_bar.refresh() etf_global_sector_alloc = pd.DataFrame( etf_global_sector_alloc, columns=["Portfolio Value"] @@ -180,6 +183,11 @@ def get_sector_allocation(benchmark_info: Dict, portfolio_trades: pd.DataFrame): portfolio_sectors_allocation.fillna(0, inplace=True) + p_bar.n += 1 + p_bar.refresh() + p_bar.disable = True + console.print("\n") + return benchmark_sectors_allocation, portfolio_sectors_allocation @@ -202,7 +210,7 @@ def get_region_country_allocation( which list on the Fidelity page is the correct one Returns - ------ + ------- region_allocation: dict Dictionary with regional allocations. country_allocation: dict @@ -289,7 +297,7 @@ def get_portfolio_region_country_allocation(portfolio_trades: pd.DataFrame): Dictionary with country allocations """ - console.print(" Loading country/region data: ", end="") + p_bar = tqdm(range(3), desc="Loading country/region data") # Define portfolio regional allocation if not portfolio_trades["Region"].isnull().any(): @@ -311,6 +319,9 @@ def get_portfolio_region_country_allocation(portfolio_trades: pd.DataFrame): else: portfolio_country_allocation = pd.DataFrame() + p_bar.n += 1 + p_bar.refresh() + # Aggregate sector value for ETFs # Start by getting value by ticker etf_ticker_value = ( @@ -355,8 +366,8 @@ def get_portfolio_region_country_allocation(portfolio_trades: pd.DataFrame): etf_global_country_alloc.fillna(0, inplace=True) etf_global_country_alloc = etf_global_country_alloc.sum(axis=1) - console.print(".", end="") - console.print("\n") + p_bar.n += 1 + p_bar.refresh() etf_global_region_alloc = pd.DataFrame( etf_global_region_alloc, columns=["Portfolio Value"] @@ -405,4 +416,9 @@ def get_portfolio_region_country_allocation(portfolio_trades: pd.DataFrame): portfolio_region_allocation.fillna(0, inplace=True) portfolio_country_allocation.fillna(0, inplace=True) + p_bar.n += 1 + p_bar.refresh() + p_bar.disable = True + console.print("\n") + return portfolio_region_allocation, portfolio_country_allocation diff --git a/openbb_terminal/portfolio/portfolio_controller.py b/openbb_terminal/portfolio/portfolio_controller.py index 2645109f3a3..156b8362343 100644 --- a/openbb_terminal/portfolio/portfolio_controller.py +++ b/openbb_terminal/portfolio/portfolio_controller.py @@ -6,7 +6,6 @@ import logging import os from typing import List -import numpy as np import pandas as pd from openbb_terminal.custom_prompt_toolkit import NestedCompleter @@ -17,7 +16,6 @@ from openbb_terminal.helper_funcs import ( EXPORT_BOTH_RAW_DATA_AND_FIGURES, EXPORT_ONLY_FIGURES_ALLOWED, EXPORT_ONLY_RAW_DATA_ALLOWED, - check_positive_float, ) from openbb_terminal.menu import session @@ -160,13 +158,12 @@ class PortfolioController(BaseController): choices: dict = {c: {} for c in self.controller_choices} one_to_hundred: dict = {str(c): {} for c in range(1, 100)} - zero_to_hundred_detailed = {str(c): {} for c in np.arange(0.0, 100.0, 0.1)} choices["load"] = { "--file": {c: {} for c in self.DATA_HOLDINGS_FILES}, "-f": "--file", "--name": None, "-n": "--name", - "--rfr": zero_to_hundred_detailed, + "--rfr": None, "-r": "--rfr", } choices["show"] = { @@ -174,11 +171,16 @@ class PortfolioController(BaseController): "-l": "--limit", } choices["bench"] = {c: {} for c in statics.BENCHMARK_LIST} + choices["bench"] = { + "--benchmark": {c: {} for c in statics.BENCHMARK_LIST}, + "-b": "--benchmark", + } + choices["bench"]["--full_shares"] = {} choices["bench"]["-s"] = "--full_shares" hold = { - "--sum": {}, - "-s": "--sum", + "--unstack": {}, + "-u": "--unstack", "--raw": {}, "--limit": one_to_hundred, "-l": "--limit", @@ -216,7 +218,7 @@ class PortfolioController(BaseController): r_auto_complete = { "--period": {c: {} for c in portfolio_helper.PERIODS}, "-p": "--period", - "--rfr": zero_to_hundred_detailed, + "--rfr": None, "-r": "--rfr", } choices["rsharpe"] = r_auto_complete @@ -236,7 +238,7 @@ class PortfolioController(BaseController): choices["metric"] = {c: {} for c in self.VALID_METRICS} choices["metric"]["--metric"] = {c: {} for c in self.VALID_METRICS} choices["metric"]["-m"] = "--metric" - choices["metric"]["--rfr"] = zero_to_hundred_detailed + choices["metric"]["--rfr"] = None choices["metric"]["-r"] = "--rfr" choices["perf"] = { "--period": {c: {} for c in portfolio_helper.PERIODS}, @@ -251,7 +253,7 @@ class PortfolioController(BaseController): "-a": "--adjusted", "--student": {}, "-s": "--student", - "--percentile": zero_to_hundred_detailed, + "--percentile": None, "-p": "--percentile", } choices["es"] = { @@ -259,7 +261,7 @@ class PortfolioController(BaseController): "-m": "--mean", "--dist": {c: {} for c in self.VALID_DISTRIBUTIONS}, "-d": "--dist", - "--percentile": zero_to_hundred_detailed, + "--percentile": None, "-p": "--percentile", } choices["om"] = { @@ -284,14 +286,11 @@ class PortfolioController(BaseController): mt.add_raw("\n") mt.add_cmd("load") + mt.add_cmd("show") + mt.add_cmd("bench") mt.add_raw("\n") mt.add_param("_loaded", self.portfolio_name) mt.add_param("_riskfreerate", self.portfolio_name) - mt.add_raw("\n") - mt.add_cmd("show") - mt.add_raw("\n") - mt.add_cmd("bench") - mt.add_raw("\n") mt.add_param("_benchmark", self.benchmark_name) mt.add_raw("\n") @@ -309,8 +308,8 @@ class PortfolioController(BaseController): mt.add_cmd("rbeta", self.portfolio_name and self.benchmark_name) mt.add_info("_metrics_") - mt.add_cmd("alloc", self.portfolio_name and self.benchmark_name) mt.add_cmd("summary", self.portfolio_name and self.benchmark_name) + mt.add_cmd("alloc", self.portfolio_name and self.benchmark_name) mt.add_cmd("metric", self.portfolio_name and self.benchmark_name) mt.add_cmd("perf", self.portfolio_name and self.benchmark_name) @@ -326,16 +325,13 @@ class PortfolioController(BaseController): > bro brokers holdings, \t\t supports: robinhood, ally, degiro, coinbase > po portfolio optimization, \t optimize your portfolio weights efficiently[/menu] [cmds] - load load data into the portfolio[/cmds] - -[param]Loaded orderbook:[/param] {self.portfolio_name or ""} + load load data into the portfolio + show show existing transactions + bench define the benchmark +[/cmds] +[param]Loaded transactions file:[/param] {self.portfolio_name} [param]Risk Free Rate: [/param] {self.risk_free_rate:.2%} -{("[unvl]", "[cmds]")[port]} - show show existing transactions{("[/unvl]", "[/cmds]")[port]} -{("[unvl]", "[cmds]")[port]} - bench define the benchmark{("[/unvl]", "[/cmds]")[port]} - -[param]Benchmark:[/param] {self.benchmark_name or ""} +[param]Benchmark:[/param] {self.benchmark_name} [info]Graphs:[/info]{("[unvl]", "[cmds]")[port_bench]} holdv holdings of assets (absolute value) @@ -351,8 +347,8 @@ class PortfolioController(BaseController): rbeta rolling beta {("[/unvl]", "[/cmds]")[port_bench]} [info]Metrics:[/info]{("[unvl]", "[cmds]")[port_bench]} - alloc allocation on an asset, sector, countries or regions basis summary all portfolio vs benchmark metrics for a certain period of choice + alloc allocation on an asset, sector, countries or regions basis metric portfolio vs benchmark metric for all different periods perf performance of the portfolio versus benchmark{("[/unvl]", "[/cmds]")[port_bench]} @@ -408,7 +404,7 @@ class PortfolioController(BaseController): add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, prog="load", - description="Load your portfolio", + description="Load your portfolio transactions.", ) parser.add_argument( "-f", @@ -442,10 +438,10 @@ class PortfolioController(BaseController): else: file_location = ns_parser.file # type: ignore - orderbook = portfolio_model.PortfolioModel.read_orderbook( + transactions = portfolio_model.PortfolioModel.read_transactions( str(file_location) ) - self.portfolio = portfolio_model.PortfolioModel(orderbook) + self.portfolio = portfolio_model.PortfolioModel(transactions) self.benchmark_name = "" if ns_parser.name: @@ -489,7 +485,7 @@ class PortfolioController(BaseController): limit=10, ) if ns_parser and self.portfolio is not None: - portfolio_view.display_orderbook( + portfolio_view.display_transactions( self.portfolio, show_index=False, limit=ns_parser.limit, @@ -552,7 +548,9 @@ class PortfolioController(BaseController): ) else: - console.print("[red]Please first load orderbook using 'load'[/red]\n") + console.print( + "[red]Please first load transactions file using 'load'[/red]\n" + ) console.print() @log_start_end(log=logger) @@ -589,7 +587,7 @@ class PortfolioController(BaseController): ns_parser = self.parse_known_args_and_warn(parser, other_args, limit=10) if ns_parser and self.portfolio is not None: - console.print() + if check_portfolio_benchmark_defined( self.portfolio_name, self.benchmark_name ): @@ -692,11 +690,11 @@ class PortfolioController(BaseController): description="Display holdings of assets (absolute value)", ) parser.add_argument( - "-s", - "--sum", + "-u", + "--unstack", action="store_true", default=False, - dest="sum_assets", + dest="unstack", help="Sum all assets value over time", ) ns_parser = self.parse_known_args_and_warn( @@ -712,7 +710,7 @@ class PortfolioController(BaseController): ): portfolio_view.display_holdings_value( self.portfolio, - ns_parser.sum_assets, + ns_parser.unstack, ns_parser.raw, ns_parser.limit, ns_parser.export, @@ -728,11 +726,11 @@ class PortfolioController(BaseController): description="Display holdings of assets (in percentage)", ) parser.add_argument( - "-s", - "--sum", + "-u", + "--unstack", action="store_true", default=False, - dest="sum_assets", + dest="unstack", help="Sum all assets percentage over time", ) ns_parser = self.parse_known_args_and_warn( @@ -748,7 +746,7 @@ class PortfolioController(BaseController): ): portfolio_view.display_holdings_percentage( self.portfolio, - ns_parser.sum_assets, + ns_parser.unstack, ns_parser.raw, ns_parser.limit, ns_parser.export, @@ -769,7 +767,7 @@ class PortfolioController(BaseController): "-m", "--mean", action="store_true", - default=False, + default=True, dest="use_mean", help="If one should use the mean of the portfolio return", ) @@ -841,7 +839,7 @@ class PortfolioController(BaseController): "-m", "--mean", action="store_true", - default=False, + default=True, dest="use_mean", help="If one should use the mean of the portfolios return", ) @@ -1126,7 +1124,7 @@ class PortfolioController(BaseController): parser.add_argument( "-r", "--rfr", - type=check_positive_float, + type=float, dest="risk_free_rate", default=self.risk_free_rate, help="Set risk free rate for calculations.", @@ -1168,7 +1166,7 @@ class PortfolioController(BaseController): parser.add_argument( "-r", "--rfr", - type=check_positive_float, + type=float, dest="risk_free_rate", default=self.risk_free_rate, help="Set risk free rate for calculations.", @@ -1243,7 +1241,7 @@ class PortfolioController(BaseController): parser.add_argument( "-r", "--rfr", - type=check_positive_float, + type=float, dest="risk_free_rate", default=self.risk_free_rate, help="Set risk free rate for calculations.", @@ -1375,7 +1373,7 @@ class PortfolioController(BaseController): parser.add_argument( "-r", "--rfr", - type=check_positive_float, + type=float, dest="risk_free_rate", default=self.risk_free_rate, help="Set risk free rate for calculations.", @@ -1415,16 +1413,13 @@ def check_portfolio_benchmark_defined(portfolio_name: str, benchmark_name: str) bool If both portfolio and benchmark have been defined """ - if portfolio_name and benchmark_name: - return True + if not portfolio_name: - if not benchmark_name: - console.print( - "[red]Please first define the portfolio (via 'load') " - "and the benchmark (via 'bench').[/red]\n" - ) - else: - console.print("[red]Please first define the portfolio (via 'load')[/red]\n") - else: + console.print("[red]Please first define the portfolio (via 'load')[/red]\n") + return False + + if not benchmark_name: console.print("[red]Please first define the benchmark (via 'bench')[/red]\n") - return False + return False + + return True diff --git a/openbb_terminal/portfolio/portfolio_model.py b/openbb_terminal/portfolio/portfolio_model.py index 0f3689c6f2e..d0807652c3e 100644 --- a/openbb_terminal/portfolio/portfolio_model.py +++ b/openbb_terminal/portfolio/portfolio_model.py @@ -10,6 +10,7 @@ import numpy as np import scipy import pandas as pd import yfinance as yf +from tqdm import tqdm from sklearn.metrics import r2_score from pycoingecko import CoinGeckoAPI from openbb_terminal.common.quantitative_analysis import qa_model @@ -18,7 +19,7 @@ from openbb_terminal.portfolio import portfolio_helper, allocation_model from openbb_terminal.rich_config import console # pylint: disable=E1136,W0201,R0902,C0302 -# pylint: disable=unsupported-assignment-operation,redefined-outer-name,too-many-public-methods +# pylint: disable=unsupported-assignment-operation,redefined-outer-name,too-many-public-methods, consider-using-f-string logger = logging.getLogger(__name__) cg = CoinGeckoAPI() @@ -33,17 +34,17 @@ class PortfolioModel: Methods ------- - read_orderbook: Class method to read orderbook from file + read_transactions: Class method to read transactions from file - __set_orderbook: - preprocess_orderbook: Method to preprocess, format and compute auxiliary fields + __set_transactions: + preprocess_transactions: Method to preprocess, format and compute auxiliary fields - get_orderbook: Outputs the formatted transactions DataFrame + get_transactions: Outputs the formatted transactions DataFrame load_benchmark: Adds benchmark ticker, info, prices and returns - mimic_trades_for_benchmark: Mimic trades from the orderbook based on chosen benchmark assuming partial shares + mimic_trades_for_benchmark: Mimic trades from the transactions based on chosen benchmark assuming partial shares - generate_portfolio_data: Generates portfolio data from orderbook + generate_portfolio_data: Generates portfolio data from transactions load_portfolio_historical_prices: Loads historical adj close prices for tickers in list of trades populate_historical_trade_data: Create a new dataframe to store historical prices by ticker calculate_value: Calculate value end of day from historical data @@ -56,7 +57,7 @@ class PortfolioModel: """ - def __init__(self, orderbook: pd.DataFrame = pd.DataFrame()): + def __init__(self, transactions: pd.DataFrame = pd.DataFrame()): """Initialize PortfolioModel class""" # Portfolio @@ -90,22 +91,22 @@ class PortfolioModel: self.benchmark_region_allocation = pd.DataFrame() self.benchmark_country_allocation = pd.DataFrame() - # Set and preprocess orderbook - if not orderbook.empty: - self.__set_orderbook(orderbook) + # Set and preprocess transactions + if not transactions.empty: + self.__set_transactions(transactions) - def __set_orderbook(self, orderbook): - self.__orderbook = orderbook - self.preprocess_orderbook() + def __set_transactions(self, transactions): + self.__transactions = transactions + self.preprocess_transactions() self.empty = False - def get_orderbook(self): + def get_transactions(self): """Get formatted transactions Returns: pd.DataFrame: formatted transactions """ - df = self.__orderbook[ + df = self.__transactions[ [ "Date", "Type", @@ -128,30 +129,30 @@ class PortfolioModel: return df @staticmethod - def read_orderbook(path: str) -> pd.DataFrame: - """Static method to read orderbook from file + def read_transactions(path: str) -> pd.DataFrame: + """Static method to read transactions from file Parameters ---------- path: str - path to orderbook file + path to transactions file """ - # Load orderbook from file + # Load transactions from file if path.endswith(".xlsx"): - orderbook = pd.read_excel(path) + transactions = pd.read_excel(path) elif path.endswith(".csv"): - orderbook = pd.read_csv(path) + transactions = pd.read_csv(path) - return orderbook + return transactions @log_start_end(log=logger) - def preprocess_orderbook(self): - """Method to preprocess, format and compute auxiliary fields + def preprocess_transactions(self): + """Method to preprocess, format and compute auxiliary fields. Preprocessing steps: - 0. If optional fields not in the orderbook add missing + 0. If optional fields not in the transactions add missing 1. Convert Date to datetime - 2. Sort orderbook by date + 2. Sort transactions by date 3. Capitalize Ticker and Type [of instrument...] 4. Translate side: ["deposit", "buy"] -> 1 and ["withdrawal", "sell"] -> -1 5. Convert quantity to signed integer @@ -161,14 +162,15 @@ class PortfolioModel: 9. Remove unsupported ISINs that came out empty 10. Create tickers dictionary with structure {'Type': [Ticker]} 11. Create list with tickers except cash - 12. Save orderbook inception date + 12. Save transactions inception date 13. Populate fields Sector, Industry and Country """ - try: - console.print(" Preprocessing orderbook: ", end="") + p_bar = tqdm(range(14), desc="Preprocessing transactions") - # 0. If optional fields not in the orderbook add missing + try: + + # 0. If optional fields not in the transactions add missing optional_fields = [ "Sector", "Industry", @@ -178,68 +180,89 @@ class PortfolioModel: "Premium", "ISIN", ] - if not set(optional_fields).issubset(set(self.__orderbook.columns)): + if not set(optional_fields).issubset(set(self.__transactions.columns)): for field in optional_fields: - if field not in self.__orderbook.columns: - self.__orderbook[field] = np.nan + if field not in self.__transactions.columns: + self.__transactions[field] = np.nan + + p_bar.n += 1 + p_bar.refresh() # 1. Convert Date to datetime - self.__orderbook["Date"] = pd.to_datetime(self.__orderbook["Date"]) - console.print(".", end="") + self.__transactions["Date"] = pd.to_datetime(self.__transactions["Date"]) - # 2. Sort orderbook by date - self.__orderbook = self.__orderbook.sort_values(by="Date") - console.print(".", end="") + p_bar.n += 1 + p_bar.refresh() + + # 2. Sort transactions by date + self.__transactions = self.__transactions.sort_values(by="Date") + + p_bar.n += 1 + p_bar.refresh() # 3. Capitalize Ticker and Type [of instrument...] - self.__orderbook["Ticker"] = self.__orderbook["Ticker"].map( + self.__transactions["Ticker"] = self.__transactions["Ticker"].map( lambda x: x.upper() ) - self.__orderbook["Type"] = self.__orderbook["Type"].map(lambda x: x.upper()) - console.print(".", end="") + self.__transactions["Type"] = self.__transactions["Type"].map( + lambda x: x.upper() + ) + + p_bar.n += 1 + p_bar.refresh() # 4. Translate side: ["deposit", "buy"] -> 1 and ["withdrawal", "sell"] -> -1 - self.__orderbook["Signal"] = self.__orderbook["Side"].map( + self.__transactions["Signal"] = self.__transactions["Side"].map( lambda x: 1 if x.lower() in ["deposit", "buy"] else (-1 if x.lower() in ["withdrawal", "sell"] else 0) ) - console.print(".", end="") + + p_bar.n += 1 + p_bar.refresh() # 5. Convert quantity to signed integer - self.__orderbook["Quantity"] = ( - abs(self.__orderbook["Quantity"]) * self.__orderbook["Signal"] + self.__transactions["Quantity"] = ( + abs(self.__transactions["Quantity"]) * self.__transactions["Signal"] ) - console.print(".", end="") + + p_bar.n += 1 + p_bar.refresh() # 6. Determining the investment/divestment value - self.__orderbook["Investment"] = ( - self.__orderbook["Quantity"] * self.__orderbook["Price"] - + self.__orderbook["Fees"] + self.__transactions["Investment"] = ( + self.__transactions["Quantity"] * self.__transactions["Price"] + + self.__transactions["Fees"] ) - console.print(".", end="") + + p_bar.n += 1 + p_bar.refresh() # 7. Reformat crypto tickers to yfinance format (e.g. BTC -> BTC-USD) - crypto_trades = self.__orderbook[self.__orderbook.Type == "CRYPTO"] - self.__orderbook.loc[(self.__orderbook.Type == "CRYPTO"), "Ticker"] = [ + crypto_trades = self.__transactions[self.__transactions.Type == "CRYPTO"] + self.__transactions.loc[ + (self.__transactions.Type == "CRYPTO"), "Ticker" + ] = [ f"{crypto}-{currency}" for crypto, currency in zip( crypto_trades.Ticker, crypto_trades.Currency ) ] - console.print(".", end="") + + p_bar.n += 1 + p_bar.refresh() # 8. Reformat STOCK/ETF tickers to yfinance format if ISIN provided. # If isin not valid ticker is empty - self.__orderbook["yf_Ticker"] = self.__orderbook["ISIN"].apply( + self.__transactions["yf_Ticker"] = self.__transactions["ISIN"].apply( lambda x: yf.utils.get_ticker_by_isin(x) if not pd.isna(x) else np.nan ) empty_tickers = list( - self.__orderbook[ - (self.__orderbook["yf_Ticker"] == "") - | (self.__orderbook["yf_Ticker"].isna()) + self.__transactions[ + (self.__transactions["yf_Ticker"] == "") + | (self.__transactions["yf_Ticker"].isna()) ]["Ticker"].unique() ) @@ -257,51 +280,61 @@ class PortfolioModel: ) if valid_ticker: # Invalid ISIN but valid ticker - self.__orderbook.loc[ - self.__orderbook["Ticker"] == item, "yf_Ticker" + self.__transactions.loc[ + self.__transactions["Ticker"] == item, "yf_Ticker" ] = np.nan else: - self.__orderbook.loc[ - self.__orderbook["Ticker"] == item, "yf_Ticker" + self.__transactions.loc[ + self.__transactions["Ticker"] == item, "yf_Ticker" ] = "" removed_tickers.append(item) # Merge reformated tickers into Ticker - self.__orderbook["Ticker"] = self.__orderbook["yf_Ticker"].fillna( - self.__orderbook["Ticker"] + self.__transactions["Ticker"] = self.__transactions["yf_Ticker"].fillna( + self.__transactions["Ticker"] ) - console.print(".", end="") + p_bar.n += 1 + p_bar.refresh() # 9. Remove unsupported ISINs that came out empty - self.__orderbook.drop( - self.__orderbook[self.__orderbook["Ticker"] == ""].index, inplace=True + self.__transactions.drop( + self.__transactions[self.__transactions["Ticker"] == ""].index, + inplace=True, ) - console.print(".", end="") + + p_bar.n += 1 + p_bar.refresh() # 10. Create tickers dictionary with structure {'Type': [Ticker]} - for ticker_type in set(self.__orderbook["Type"]): + for ticker_type in set(self.__transactions["Type"]): self.tickers[ticker_type] = list( set( - self.__orderbook[self.__orderbook["Type"].isin([ticker_type])][ - "Ticker" - ] + self.__transactions[ + self.__transactions["Type"].isin([ticker_type]) + ]["Ticker"] ) ) - console.print(".", end="") + + p_bar.n += 1 + p_bar.refresh() # 11. Create list with tickers except cash - self.tickers_list = list(set(self.__orderbook["Ticker"])) - console.print(".", end="") + self.tickers_list = list(set(self.__transactions["Ticker"])) - # 12. Save orderbook inception date - self.inception_date = self.__orderbook["Date"][0] - console.print(".", end="") + p_bar.n += 1 + p_bar.refresh() + + # 12. Save transactions inception date + self.inception_date = self.__transactions["Date"][0] + + p_bar.n += 1 + p_bar.refresh() # 13. Populate fields Sector, Industry and Country if ( - self.__orderbook.loc[ - self.__orderbook["Type"] == "STOCK", + self.__transactions.loc[ + self.__transactions["Type"] == "STOCK", optional_fields, ] .isnull() @@ -309,7 +342,9 @@ class PortfolioModel: ): # If any fields is empty for stocks (overwrites any info there) self.load_company_data() - console.print(".", end="") + + p_bar.n += 1 + p_bar.refresh() # Warn user of removed ISINs if removed_tickers: @@ -321,14 +356,12 @@ class PortfolioModel: f"\nE.g. IWDA -> IWDA.AS[/red]" ) except Exception: - console.print("\nCould not preprocess orderbook.") + console.print("\nCould not preprocess transactions.") @log_start_end(log=logger) def load_company_data(self): """Method to populate company data for stocks such as sector, industry and country""" - console.print("\n Loading company data: ", end="") - for ticker_type, ticker_list in self.tickers.items(): # yfinance only has sector, industry and country for stocks if ticker_type == "STOCK": @@ -337,8 +370,8 @@ class PortfolioModel: # TODO: Should only get field missing for tickers with missing data # now it's taking the 4 of them if ( - self.__orderbook.loc[ - self.__orderbook["Ticker"] == ticker, + self.__transactions.loc[ + self.__transactions["Ticker"] == ticker, ["Sector", "Industry", "Country", "Region"], ] .isnull() @@ -347,20 +380,17 @@ class PortfolioModel: # Get ticker info in list ["Sector", "Industry", "Country", "Region"] from isin/ticker info_list = portfolio_helper.get_info_from_ticker(ticker) - # Replace fields in orderbook - self.__orderbook.loc[ - self.__orderbook.Ticker == ticker, + # Replace fields in transactions + self.__transactions.loc[ + self.__transactions.Ticker == ticker, ["Sector", "Industry", "Country", "Region"], ] = info_list - # Display progress - console.print(".", end="") - elif ticker_type == "CRYPTO": for ticker in ticker_list: if ( - self.__orderbook.loc[ - self.__orderbook["Ticker"] == ticker, + self.__transactions.loc[ + self.__transactions["Ticker"] == ticker, ["Sector", "Industry", "Country", "Region"], ] .isnull() @@ -369,20 +399,17 @@ class PortfolioModel: # Get ticker info in list ["Sector", "Industry", "Country", "Region"] info_list = ["Crypto", "Crypto", "Crypto", "Crypto"] - # Replace fields in orderbook - self.__orderbook.loc[ - self.__orderbook.Ticker == ticker, + # Replace fields in transactions + self.__transactions.loc[ + self.__transactions.Ticker == ticker, ["Sector", "Industry", "Country", "Region"], ] = info_list - # Display progress - console.print(".", end="") - else: for ticker in ticker_list: if ( - self.__orderbook.loc[ - self.__orderbook["Ticker"] == ticker, + self.__transactions.loc[ + self.__transactions["Ticker"] == ticker, ["Sector", "Industry", "Country", "Region"], ] .isnull() @@ -391,15 +418,12 @@ class PortfolioModel: # Get ticker info in list ["Sector", "Industry", "Country", "Region"] info_list = ["-", "-", "-", "-"] - # Replace fields in orderbook - self.__orderbook.loc[ - self.__orderbook.Ticker == ticker, + # Replace fields in transactions + self.__transactions.loc[ + self.__transactions.Ticker == ticker, ["Sector", "Industry", "Country", "Region"], ] = info_list - # Display progress - console.print(".", end="") - @log_start_end(log=logger) def load_benchmark(self, ticker: str = "SPY", full_shares: bool = False): """Adds benchmark dataframe @@ -413,7 +437,7 @@ class PortfolioModel: quantity to the nearest number. """ - console.print("\n Loading benchmark: ", end="") + p_bar = tqdm(range(3), desc=" Loading benchmark") self.benchmark_ticker = ticker @@ -424,6 +448,9 @@ class PortfolioModel: progress=False, )["Adj Close"] + p_bar.n += 1 + p_bar.refresh() + self.mimic_trades_for_benchmark(full_shares) # Merge benchmark and portfolio dates to ensure same length @@ -436,15 +463,18 @@ class PortfolioModel: )["Adj Close"] self.benchmark_historical_prices.fillna(method="ffill", inplace=True) + p_bar.n += 1 + p_bar.refresh() + self.benchmark_returns = self.benchmark_historical_prices.pct_change().dropna() self.benchmark_info = yf.Ticker(ticker).info - # Display progress - console.print(".") + p_bar.n += 1 + p_bar.refresh() @log_start_end(log=logger) def mimic_trades_for_benchmark(self, full_shares: bool = False): - """Mimic trades from the orderbook based on chosen benchmark assuming partial shares + """Mimic trades from the transactions based on chosen benchmark assuming partial shares Parameters ---------- full_shares: bool @@ -453,7 +483,9 @@ class PortfolioModel: """ # Create dataframe to store benchmark trades - self.benchmark_trades = self.__orderbook[["Date", "Type", "Investment"]].copy() + self.benchmark_trades = self.__transactions[ + ["Date", "Type", "Investment"] + ].copy() # Set current price of benchmark self.benchmark_trades["Last price"] = self.benchmark_historical_prices[-1] @@ -514,7 +546,7 @@ class PortfolioModel: @log_start_end(log=logger) def generate_portfolio_data(self): - """Generates portfolio data from orderbook""" + """Generates portfolio data from transactions""" self.load_portfolio_historical_prices() self.populate_historical_trade_data() @@ -544,7 +576,7 @@ class PortfolioModel: last_price = self.historical_trade_data["Close"].iloc[-1] # Save portfolio trades to compute allocations later - self.portfolio_trades = self.__orderbook.copy() + self.portfolio_trades = self.__transactions.copy() self.portfolio_trades[ [ "Portfolio Investment", @@ -555,7 +587,7 @@ class PortfolioModel: ] ] = float(0) - for index, trade in self.__orderbook.iterrows(): + for index, trade in self.__transactions.iterrows(): self.portfolio_trades["Close"][index] = last_price[trade["Ticker"]] self.portfolio_trades["Portfolio Investment"][index] = trade["Investment"] self.portfolio_trades["Portfolio Value"][index] = ( @@ -580,7 +612,7 @@ class PortfolioModel: whether to use close or adjusted close prices """ - console.print("\n Loading price data: ", end="") + p_bar = tqdm(range(len(self.tickers)), desc=" Loading price data") for ticker_type, data in self.tickers.items(): if ticker_type in ["STOCK", "ETF", "CRYPTO"]: @@ -601,7 +633,8 @@ class PortfolioModel: else: console.print(f"Type {ticker_type} not supported.") - console.print(".", end="") + p_bar.n += 1 + p_bar.refresh() # Fill missing values with last known price self.portfolio_historical_prices.fillna(method="ffill", inplace=True) @@ -610,7 +643,7 @@ class PortfolioModel: def populate_historical_trade_data(self): """Create a new dataframe to store historical prices by ticker""" - trade_data = self.__orderbook.pivot_table( + trade_data = self.__transactions.pivot_table( index="Date", columns=["Ticker"], values=[ @@ -652,7 +685,7 @@ class PortfolioModel: def calculate_value(self): """Calculate end of day value from historical data""" - console.print("\n Calculating returns: ", end="") + p_bar = tqdm(range(1), desc=" Calculating returns") trade_data = self.historical_trade_data @@ -691,7 +724,8 @@ class PortfolioModel: 0 ]["Investment"][t] - console.print(".", end="") + p_bar.n += 1 + p_bar.refresh() self.historical_trade_data = trade_data @@ -1786,7 +1820,7 @@ def get_summary( scipy.stats.kurtosis(portfolio_returns), scipy.stats.kurtosis(benchmark_returns), ], - "Maximum Drawdowwn": [ + "Maximum Drawdown": [ portfolio_helper.maximum_drawdown(portfolio_returns), portfolio_helper.maximum_drawdown(benchmark_returns), ], @@ -1808,6 +1842,11 @@ def get_summary( metrics.values(), index=metrics.keys(), columns=["Portfolio", "Benchmark"] ) summary["Difference"] = summary["Portfolio"] - summary["Benchmark"] + summary.loc["Volatility"] = summary.loc["Volatility"].apply("{:.2%}".format) + summary.loc["Maximum Drawdown"] = summary.loc["Maximum Drawdown"].apply( + "{:.2%}".format + ) + summary.loc["R2 Score"] = summary.loc["R2 Score"].apply("{:.2%}".format) return summary diff --git a/openbb_terminal/portfolio/portfolio_view.py b/openbb_terminal/portfolio/portfolio_view.py index e793eb22331..79bc370c45a 100644 --- a/openbb_terminal/portfolio/portfolio_view.py +++ b/openbb_terminal/portfolio/portfolio_view.py @@ -61,13 +61,13 @@ In order to load a CSV do the following: @log_start_end(log=logger) -def display_orderbook( +def display_transactions( portfolio=None, show_index=False, limit: int = 10, export: str = "", ): - """Display portfolio orderbook + """Display portfolio transactions Parameters ---------- @@ -82,10 +82,10 @@ def display_orderbook( """ if portfolio.empty: - logger.warning("No orderbook loaded") - console.print("[red]No orderbook loaded.[/red]\n") + logger.warning("No transactions file loaded.") + console.print("[red]No transactions file loaded.[/red]\n") else: - df = portfolio.get_orderbook() + df = portfolio.get_transactions() print_rich_table( df=df[:limit], show_index=show_index, @@ -120,6 +120,7 @@ def display_assets_allocation( include_separate_tables: bool Whether to include separate asset allocation tables """ + benchmark_allocation = benchmark_allocation.iloc[:limit] portfolio_allocation = portfolio_allocation.iloc[:limit] @@ -139,6 +140,7 @@ def display_assets_allocation( ) if include_separate_tables: + console.print("") print_rich_table( portfolio_allocation, headers=list(portfolio_allocation.columns), @@ -147,6 +149,7 @@ def display_assets_allocation( floatfmt=[".2%", ".2%"], show_index=False, ) + console.print("") print_rich_table( benchmark_allocation, headers=list(benchmark_allocation.columns), @@ -225,6 +228,7 @@ def display_category_allocation( ) if include_separate_tables: + print_rich_table( pd.DataFrame(portfolio_allocation), headers=list(["Allocation"]), @@ -233,6 +237,7 @@ def display_category_allocation( floatfmt=[".2%"], show_index=True, ) + print_rich_table( pd.DataFrame(benchmark_allocation), headers=list(["Allocation"]), @@ -241,6 +246,7 @@ def display_category_allocation( floatfmt=[".2%"], show_index=True, ) + console.print("\n") @log_start_end(log=logger) @@ -398,16 +404,19 @@ def display_monthly_returns( if raw: print_rich_table( portfolio_returns, - title="Monthly returns", + title="Monthly returns - portfolio [%]", headers=portfolio_returns.columns, show_index=True, ) + console.print("\n") + print_rich_table( benchmark_returns, - title="Monthly returns", + title="Monthly returns - benchmark [%]", headers=benchmark_returns.columns, show_index=True, ) + console.print("\n") else: if external_axes is None: @@ -621,7 +630,7 @@ def display_distribution_returns( @log_start_end(log=logger) def display_holdings_value( portfolio: portfolio_model.PortfolioModel, - sum_assets: bool = False, + unstack: bool = False, raw: bool = False, limit: int = 10, export: str = "", @@ -633,8 +642,8 @@ def display_holdings_value( ---------- portfolio: Portfolio Portfolio object with trades loaded - sum_assets: bool - Sum assets over time + unstack: bool + Individual assets over time raw : bool To display raw data limit : int @@ -664,7 +673,8 @@ def display_holdings_value( return ax = external_axes[0] - if sum_assets: + if not unstack: + all_holdings.drop(columns=["Total Value"], inplace=True) ax.stackplot( all_holdings.index, [all_holdings[col] for col in all_holdings.columns], @@ -698,7 +708,7 @@ def display_holdings_value( @log_start_end(log=logger) def display_holdings_percentage( portfolio: portfolio_model.PortfolioModel, - sum_assets: bool = False, + unstack: bool = False, raw: bool = False, limit: int = 10, export: str = "", @@ -710,8 +720,8 @@ def display_holdings_percentage( ---------- portfolio: Portfolio Portfolio object with trades loaded - sum_assets: bool - Sum assets over time + unstack: bool + Individual assets over time raw : bool To display raw data limit : int @@ -747,7 +757,7 @@ def display_holdings_percentage( return ax = external_axes[0] - if sum_assets: + if not unstack: ax.stackplot( all_holdings.index, all_holdings.values.T, @@ -1614,7 +1624,7 @@ def display_summary( @log_start_end(log=logger) def display_var( portfolio: portfolio_model.PortfolioModel, - use_mean: bool = False, + use_mean: bool = True, adjusted_var: bool = False, student_t: bool = False, percentile: float = 99.9, @@ -1634,6 +1644,7 @@ def display_var( percentile: float var percentile (%) """ + qa_view.display_var( data=portfolio.returns, symbol="Portfolio", @@ -1648,7 +1659,7 @@ def display_var( @log_start_end(log=logger) def display_es( portfolio: portfolio_model.PortfolioModel, - use_mean: bool = False, + use_mean: bool = True, distribution: str = "normal", percentile: float = 99.9, ): diff --git a/openbb_terminal/reports/reports_model.py b/openbb_terminal/reports/reports_model.py index a19f2385e05..f72ce6e39f9 100644 --- a/openbb_terminal/reports/reports_model.py +++ b/openbb_terminal/reports/reports_model.py @@ -72,7 +72,7 @@ REPORT_CHOICES = { "--symbol": {c: None for c in FX_TICKERS}, }, "portfolio": { - "--orderbook": {c: None for c in PORTFOLIO_HOLDINGS_FILES}, + "--transactions": {c: None for c in PORTFOLIO_HOLDINGS_FILES}, }, "economy": None, "equity": { diff --git a/openbb_terminal/reports/templates/portfolio.ipynb b/openbb_terminal/reports/templates/portfolio.ipynb index 9f1be4d2ca6..072a0295872 100644 --- a/openbb_terminal/reports/templates/portfolio.ipynb +++ b/openbb_terminal/reports/templates/portfolio.ipynb @@ -90,7 +90,7 @@ "source": [ "# Parameters that will be replaced when calling this notebook\n", "# Do not leave parameters blank as notebook will not run otherwise\n", - "orderbook = \"Public_Equity_Orderbook.xlsx\"\n", + "transactions = \"Public_Equity_Orderbook.xlsx\"\n", "report_name = \"Portfolio Report\"" ] }, @@ -105,9 +105,9 @@ " str(REPOSITORY_DIRECTORY)\n", " + \"/openbb_terminal/miscellaneous/portfolio_examples/holdings/\"\n", ")\n", - "orderbook_path = holdings + orderbook\n", - "_, orderbook_file = os.path.split(orderbook_path)\n", - "report_title = f\"Portfolio report for {orderbook_file}\"\n", + "transactions_path = holdings + transactions\n", + "_, transactions_file = os.path.split(transactions_path)\n", + "report_title = f\"Portfolio report for {transactions_file}\"\n", "report_date = pd.Timestamp.now(tz=user_time_zone).strftime(\"%d %B, %Y\")\n", "report_time = pd.Timestamp.now(tz=user_time_zone).strftime(\"%H:%M\")\n", "report_timezone = pd.Timestamp.now(tz=user_time_zone).tzinfo\n", @@ -138,14 +138,14 @@ "outputs": [], "source": [ "try:\n", - " transactions = Portfolio.read_orderbook(orderbook_path)\n", + " transactions = Portfolio.read_transactions(transactions_path)\n", " P = Portfolio(transactions)\n", " P.generate_portfolio_data()\n", " P.load_benchmark()\n", - " P.get_orderbook()\n", + " P.get_transactions()\n", "except ValueError:\n", " raise ValueError(\n", - " \"Failed to load the orderbook. Is this file inside the 'holdings' folder?\"\n", + " \"Failed to load the transactions. Is this file inside the 'holdings' folder?\"\n", " )" ] }, diff --git a/tests/openbb_terminal/portfolio/txt/test_portfolio_controller/test_print_help.txt b/tests/openbb_terminal/portfolio/txt/test_portfolio_controller/test_print_help.txt index e21ca161e68..a907828c409 100644 --- a/tests/openbb_terminal/portfolio/txt/test_portfolio_controller/test_print_help.txt +++ b/tests/openbb_terminal/portfolio/txt/test_portfolio_controller/test_print_help.txt @@ -3,14 +3,11 @@ > po portfolio optimization, optimize your portfolio weights efficiently load load data into the portfolio + show show existing transactions + bench define the benchmark -Loaded orderbook: +Loaded transactions file: Risk Free Rate: 0.00% -[unvl] - show show existing transactions[/unvl] -[unvl] - bench define the benchmark[/unvl] - Benchmark: Graphs:[unvl] @@ -27,8 +24,8 @@ Graphs:[unvl] rbeta rolling beta [/unvl] Metrics:[unvl] - alloc allocation on an asset, sector, countries or regions basis summary all portfolio vs benchmark metrics for a certain period of choice + alloc allocation on an asset, sector, countries or regions basis metric portfolio vs benchmark metric for all different periods perf performance of the portfolio versus benchmark[/unvl] diff --git a/tests/openbb_terminal/reports/test_reports_controller.py b/tests/openbb_terminal/reports/test_reports_controller.py index a45942fee56..5cffe7bf90b 100644 --- a/tests/openbb_terminal/reports/test_reports_controller.py +++ b/tests/openbb_terminal/reports/test_reports_controller.py @@ -232,13 +232,13 @@ def test_call_func_expect_queue(expected_queue, func, queue): ( "call_portfolio", [ - "--orderbook=market.csv", + "--transactions=market.csv", ], "reports_model.render_report", [], dict( input_path=str(reports_model.REPORTS_FOLDER / "portfolio"), - args_dict={"orderbook": "market.csv"}, + args_dict={"transactions": "market.csv"}, ), ), ( diff --git a/tests/openbb_terminal/reports/txt/test_reports_controller/test_print_help.txt b/tests/openbb_terminal/reports/txt/test_reports_controller/test_print_help.txt index 3b82a2c2452..164b52b4424 100644 --- a/tests/openbb_terminal/reports/txt/test_reports_controller/test_print_help.txt +++ b/tests/openbb_terminal/reports/txt/test_reports_controller/test_print_help.txt @@ -7,7 +7,7 @@ OpenBB reports: etf forecast forex - portfolio + portfolio Custom reports: run Run a Jupyter notebook from OpenBBUserData/reports/custom reports diff --git a/website/content/terminal/portfolio/holdp/_index.md b/website/content/terminal/portfolio/holdp/_index.md index 3f1986cfed6..001841aaca5 100644 --- a/website/content/terminal/portfolio/holdp/_index.md +++ b/website/content/terminal/portfolio/holdp/_index.md @@ -1,14 +1,15 @@ ``` -usage: holdp [-s] [-h] [--export EXPORT] [--raw] [-l LIMIT] +usage: holdp [-u] [-h] [--export EXPORT] [--raw] [-l LIMIT] ``` Display holdings of assets (in percentage) ``` optional arguments: - -s, --sum Sum all assets percentage over time (default: False) + -u, --unstack Sum all assets percentage over time (default: False) -h, --help show this help message (default: False) - --export EXPORT Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg (default: ) + --export EXPORT Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg + (default: ) --raw Flag to display raw data (default: False) -l LIMIT, --limit LIMIT Number of entries to show in data. (default: 10) diff --git a/website/content/terminal/portfolio/holdv/_index.md b/website/content/terminal/portfolio/holdv/_index.md index 61d62c49913..6ea33b6bc29 100644 --- a/website/content/terminal/portfolio/holdv/_index.md +++ b/website/content/terminal/portfolio/holdv/_index.md @@ -1,14 +1,15 @@ ``` -usage: holdv [-s] [-h] [--export EXPORT] [--raw] [-l LIMIT] +usage: holdv [-u] [-h] [--export EXPORT] [--raw] [-l LIMIT] ``` Display holdings of assets (absolute value) ``` optional arguments: - -s, --sum Sum all assets value over time (default: False) + -u, --unstack Sum all assets value over time (default: False) -h, --help show this help message (default: False) - --export EXPORT Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg (default: ) + --export EXPORT Export raw data into csv, json, xlsx and figure into png, jpg, pdf, svg + (default: ) --raw Flag to display raw data (default: False) -l LIMIT, --limit LIMIT Number of entries to show in data. (default: 10)