Merge pull request #4079 from OpenBB-finance/hotfix/fix4054

Fix load a benchmark that isn't in the list of limited choices + yf.Ticker().info bugs
This commit is contained in:
James Maslek
2023-02-06 14:41:51 -05:00
committed by GitHub
7 changed files with 343 additions and 104 deletions

View File

@@ -2,6 +2,13 @@ portfolio
load --example
show
bench QQQ
bench FAKE_BENCH
bench TSLA
alloc assets -t -l 4
alloc sectors -t -l 4
alloc countries -t -l 4
alloc regions -t -l 4
bench SPY
alloc assets -t -l 4
alloc sectors -t -l 4
alloc countries -t -l 4

View File

@@ -72,11 +72,16 @@ def get_assets_allocation(
pd.DataFrame
DataFrame with the portfolio's asset allocations
"""
benchmark_assets_allocation = pd.DataFrame(benchmark_info["holdings"])
benchmark_assets_allocation.rename(
columns={"symbol": "Symbol", "holdingPercent": "Benchmark"}, inplace=True
)
benchmark_assets_allocation.drop(columns=["holdingName"], inplace=True)
if "holdings" in benchmark_info:
benchmark_assets_allocation = pd.DataFrame(benchmark_info["holdings"])
benchmark_assets_allocation.rename(
columns={"symbol": "Symbol", "holdingPercent": "Benchmark"}, inplace=True
)
benchmark_assets_allocation.drop(columns=["holdingName"], inplace=True)
else:
benchmark_assets_allocation = pd.DataFrame(
columns=["Symbol", "Benchmark", "Sector", "Country"]
)
portfolio_assets_allocation = (
portfolio_trades[portfolio_trades["Type"] != "CASH"]
@@ -109,34 +114,37 @@ def get_sectors_allocation(
Returns
-------
pd.DataFrame
DataFrame with regional allocations.
DataFrame with benchmark allocations.
pd.DataFrame
DataFrame with country allocations
DataFrame with portfolio allocations
"""
benchmark_sectors_allocation = (
pd.DataFrame.from_dict(
data={
sector_name: allocation
for sector in benchmark_info["sectorWeightings"]
for sector_name, allocation in sector.items()
},
orient="index",
if "sectorWeightings" in benchmark_info:
benchmark_sectors_allocation = (
pd.DataFrame.from_dict(
data={
sector_name: allocation
for sector in benchmark_info["sectorWeightings"]
for sector_name, allocation in sector.items()
},
orient="index",
)
.squeeze()
.sort_values(ascending=False)
)
.squeeze()
.sort_values(ascending=False)
)
# Prettify sector allocations of benchmark to align with Portfolio Excel
prettified = [
sector.replace("_", " ").title()
for sector in benchmark_sectors_allocation.index
]
# Prettify sector allocations of benchmark to align with Portfolio Excel
prettified = [
sector.replace("_", " ").title()
for sector in benchmark_sectors_allocation.index
]
benchmark_sectors_allocation.index = prettified
benchmark_sectors_allocation = pd.DataFrame(benchmark_sectors_allocation)
benchmark_sectors_allocation.reset_index(inplace=True)
benchmark_sectors_allocation.columns = ["Sector", "Benchmark"]
benchmark_sectors_allocation.index = prettified
benchmark_sectors_allocation = pd.DataFrame(benchmark_sectors_allocation)
benchmark_sectors_allocation.reset_index(inplace=True)
benchmark_sectors_allocation.columns = ["Sector", "Benchmark"]
else:
benchmark_sectors_allocation = pd.DataFrame(columns=["Sector", "Benchmark"])
# Define portfolio sector allocation
# Aggregate sector value for stocks and crypto
@@ -243,9 +251,9 @@ def get_countries_allocation(
Returns
-------
pd.DataFrame
DataFrame with regional allocations.
DataFrame with benchmark allocations.
pd.DataFrame
DataFrame with country allocations
DataFrame with portfolio allocations
"""
benchmark_allocation = get_symbol_allocation(
@@ -305,7 +313,7 @@ def get_symbol_allocation(
Returns
-------
pd.DataFrame
Dictionary with country allocations
Dictionary with category allocations
"""
if category == "Region":
@@ -332,7 +340,10 @@ def get_symbol_allocation(
# Collect data from Fidelity about the portfolio composition of the benchmark
URL = f"https://screener.fidelity.com/ftgw/etf/goto/snapshot/portfolioComposition.jhtml?symbols={symbol}"
html = request(URL).content
df_list = pd.read_html(html)
try:
df_list = pd.read_html(html)
except ValueError:
return pd.DataFrame(columns=[category, col_name])
# Find the ones that contain regions and countries
for index, item in enumerate(df_list):

View File

@@ -9,6 +9,7 @@ import pandas as pd
import yfinance as yf
from openbb_terminal.decorators import log_start_end
from openbb_terminal.rich_config import console
logger = logging.getLogger(__name__)
@@ -68,7 +69,11 @@ def get_spy_sector_contributions(
# Load in info
sp500_tickers_data = get_daily_sector_prices(start_date, end_date)
weight_data = yf.Ticker(sectors_ticker).info["sectorWeightings"]
try:
weight_data = yf.Ticker(sectors_ticker).info["sectorWeightings"]
except Exception as _: # noqa
console.print("[red]Could not get info for S&P 500.")
return pd.DataFrame()
# reformat Data
weights: Dict[str, dict] = {"SPY": {}}

View File

@@ -141,6 +141,7 @@ class PortfolioController(BaseController):
self.portfolio_name: str = ""
self.benchmark_name: str = ""
self.original_benchmark_ticker = ""
self.recalculate_alloc = False
self.risk_free_rate = 0
self.portlist: List[str] = os.listdir(self.DEFAULT_HOLDINGS_PATH)
self.portfolio = None
@@ -149,6 +150,10 @@ class PortfolioController(BaseController):
self.update_choices()
choices: dict = self.choices_default
self.choices = choices
self.choices["bench"] = {
"--benchmark": {c: None for c in statics.BENCHMARK_CHOICES},
"-b": "--benchmark",
}
self.completer = NestedCompleter.from_nested_dict(choices)
def update_choices(self):
@@ -416,7 +421,6 @@ class PortfolioController(BaseController):
dest="benchmark",
required="-h" not in other_args,
help="Set the benchmark for the portfolio. By default, this is SPDR S&P 500 ETF Trust (SPY).",
choices={c: {} for c in statics.BENCHMARK_CHOICES},
metavar="BENCHMARK",
)
parser.add_argument(
@@ -437,9 +441,14 @@ class PortfolioController(BaseController):
"[red]Please first load transactions file using 'load'[/red]"
)
else:
self.benchmark_name = statics.BENCHMARK_CHOICES.get(ns_parser.benchmark)
self.original_benchmark_ticker = ns_parser.benchmark
self.portfolio.set_benchmark(ns_parser.benchmark, ns_parser.full_shares)
if self.portfolio.set_benchmark(
ns_parser.benchmark, ns_parser.full_shares
):
self.benchmark_name = statics.BENCHMARK_CHOICES.get(
ns_parser.benchmark, ns_parser.benchmark
)
self.original_benchmark_ticker = ns_parser.benchmark
self.recalculate_alloc = True
@log_start_end(log=logger)
def call_alloc(self, other_args: List[str]):
@@ -481,27 +490,31 @@ class PortfolioController(BaseController):
):
if ns_parser.agg == "assets":
portfolio_view.display_assets_allocation(
self.portfolio,
ns_parser.limit,
ns_parser.tables,
portfolio_engine=self.portfolio,
limit=ns_parser.limit,
tables=ns_parser.tables,
recalculate=self.recalculate_alloc,
)
elif ns_parser.agg == "sectors":
portfolio_view.display_sectors_allocation(
self.portfolio,
ns_parser.limit,
ns_parser.tables,
portfolio_engine=self.portfolio,
limit=ns_parser.limit,
tables=ns_parser.tables,
recalculate=self.recalculate_alloc,
)
elif ns_parser.agg == "countries":
portfolio_view.display_countries_allocation(
self.portfolio,
ns_parser.limit,
ns_parser.tables,
portfolio_engine=self.portfolio,
limit=ns_parser.limit,
tables=ns_parser.tables,
recalculate=self.recalculate_alloc,
)
elif ns_parser.agg == "regions":
portfolio_view.display_regions_allocation(
self.portfolio,
ns_parser.limit,
ns_parser.tables,
portfolio_engine=self.portfolio,
limit=ns_parser.limit,
tables=ns_parser.tables,
recalculate=self.recalculate_alloc,
)
else:
console.print(
@@ -576,9 +589,13 @@ class PortfolioController(BaseController):
bench_result = attribution_model.get_spy_sector_contributions(
start_date, end_date
)
if bench_result.empty:
return
portfolio_result = attribution_model.get_portfolio_sector_contributions(
start_date, self.portfolio.portfolio_trades
)
if portfolio_result.empty:
return
# relative results - the proportions of return attribution
if ns_parser.type == "relative":
@@ -951,6 +968,23 @@ class PortfolioController(BaseController):
help="Period to select start end of the year returns",
metavar="PERIOD",
)
parser.add_argument(
"-i",
"--instrument",
type=str,
dest="instrument",
default="both",
choices=["both", "portfolio", "benchmark"],
help="Whether to show portfolio or benchmark monthly returns. By default both are shown in one table.",
)
parser.add_argument(
"-g",
"--graph",
action="store_true",
default=False,
dest="graph",
help="Plot the monthly returns on a heatmap",
)
parser.add_argument(
"-s",
"--show",
@@ -965,7 +999,7 @@ class PortfolioController(BaseController):
parser,
other_args,
raw=True,
export_allowed=EXPORT_ONLY_FIGURES_ALLOWED,
export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED,
)
if ns_parser and self.portfolio is not None:
@@ -975,10 +1009,13 @@ class PortfolioController(BaseController):
portfolio_view.display_monthly_returns(
self.portfolio,
ns_parser.period,
ns_parser.raw,
ns_parser.instrument,
ns_parser.graph,
ns_parser.show_vals,
ns_parser.export,
ns_parser.sheet_name,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
@log_start_end(log=logger)
@@ -1020,7 +1057,9 @@ class PortfolioController(BaseController):
ns_parser.raw,
ns_parser.limit,
ns_parser.export,
ns_parser.sheet_name,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
@log_start_end(log=logger)
@@ -1042,9 +1081,6 @@ class PortfolioController(BaseController):
portfolio_view.display_maximum_drawdown(
self.portfolio,
export=ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
@log_start_end(log=logger)
@@ -1278,18 +1314,26 @@ class PortfolioController(BaseController):
self.portfolio,
ns_parser.risk_free_rate / 100,
ns_parser.export,
ns_parser.sheet_name,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "sortino":
portfolio_view.display_sortino_ratio(
self.portfolio,
ns_parser.risk_free_rate / 100,
ns_parser.export,
ns_parser.sheet_name,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "maxdrawdown":
portfolio_view.display_maximum_drawdown_ratio(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "rsquare":
portfolio_view.display_rsquare(
@@ -1301,15 +1345,27 @@ class PortfolioController(BaseController):
)
elif ns_parser.metric == "gaintopain":
portfolio_view.display_gaintopain_ratio(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "trackerr":
portfolio_view.display_tracking_error(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "information":
portfolio_view.display_information_ratio(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "tail":
portfolio_view.display_tail_ratio(
@@ -1321,30 +1377,52 @@ class PortfolioController(BaseController):
)
elif ns_parser.metric == "commonsense":
portfolio_view.display_common_sense_ratio(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "jensens":
portfolio_view.display_jensens_alpha(
self.portfolio,
ns_parser.risk_free_rate / 100,
ns_parser.export,
ns_parser.sheet_name,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "calmar":
portfolio_view.display_calmar_ratio(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "kelly":
portfolio_view.display_kelly_criterion(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "payoff" and self.portfolio is not None:
portfolio_view.display_payoff_ratio(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
elif ns_parser.metric == "profitfactor" and self.portfolio is not None:
portfolio_view.display_profit_factor(
self.portfolio, ns_parser.export, ns_parser.sheet_name
self.portfolio,
ns_parser.export,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
@log_start_end(log=logger)
@@ -1384,7 +1462,9 @@ class PortfolioController(BaseController):
ns_parser.period,
ns_parser.raw,
ns_parser.export,
ns_parser.sheet_name,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)
@log_start_end(log=logger)
@@ -1431,7 +1511,9 @@ class PortfolioController(BaseController):
ns_parser.period,
ns_parser.risk_free_rate / 100,
ns_parser.export,
ns_parser.sheet_name,
sheet_name=" ".join(ns_parser.sheet_name)
if ns_parser.sheet_name
else None,
)

View File

@@ -22,7 +22,8 @@ from openbb_terminal.rich_config import console
from openbb_terminal.terminal_helper import suppress_stdout
# pylint: disable=E1136,W0201,R0902,C0302
# pylint: disable=unsupported-assignment-operation,redefined-outer-name,too-many-public-methods, consider-using-f-string
# pylint: disable=unsupported-assignment-operation,redefined-outer-name
# pylint: too-many-public-methods, consider-using-f-string,disable=raise-missing-from
logger = logging.getLogger(__name__)
@@ -117,7 +118,7 @@ class PortfolioEngine:
self.tickers_list = None
self.tickers: Dict[Any, Any] = {}
self.benchmark_ticker: str = ""
self.benchmark_info = None
self.benchmark_info: Dict[Any, Any] = {}
self.historical_trade_data = pd.DataFrame()
# Portfolio
@@ -226,7 +227,7 @@ class PortfolioEngine:
13. Populate fields Sector, Industry and Country
"""
p_bar = tqdm(range(14), desc="Preprocessing transactions")
p_bar = tqdm(range(14), desc="Preprocessing transactions", leave=False)
try:
# 0. If optional fields not in the transactions add missing
@@ -495,7 +496,7 @@ class PortfolioEngine:
] = info_list
@log_start_end(log=logger)
def set_benchmark(self, symbol: str = "SPY", full_shares: bool = False):
def set_benchmark(self, symbol: str = "SPY", full_shares: bool = False) -> bool:
"""Load benchmark into portfolio.
Parameters
@@ -505,11 +506,14 @@ class PortfolioEngine:
full_shares: bool
Whether to mimic the portfolio trades exactly (partial shares) or round down the
quantity to the nearest number
Returns
-------
bool
True if successful, False otherwise
"""
p_bar = tqdm(range(4), desc=" Loading benchmark")
self.benchmark_ticker = symbol
p_bar = tqdm(range(4), desc=" Loading benchmark", leave=False)
self.benchmark_historical_prices = yf.download(
symbol,
@@ -519,6 +523,15 @@ class PortfolioEngine:
ignore_tz=True,
)["Adj Close"]
if self.benchmark_historical_prices.empty:
console.print(
f"\n[red]Could not download benchmark data for {symbol}."
" Choose another symbol.\n[/red]"
)
return False
self.benchmark_ticker = symbol
p_bar.n += 1
p_bar.refresh()
@@ -538,11 +551,6 @@ class PortfolioEngine:
p_bar.refresh()
self.benchmark_returns = self.benchmark_historical_prices.pct_change().dropna()
self.benchmark_info = yf.Ticker(symbol).info
p_bar.n += 1
p_bar.refresh()
(
self.portfolio_returns,
self.benchmark_returns,
@@ -551,6 +559,21 @@ class PortfolioEngine:
p_bar.n += 1
p_bar.refresh()
try:
self.benchmark_info = dict(yf.Ticker(symbol).info)
self.benchmark_info["symbol"] = symbol
except Exception as _: # noqa
console.print(
f"[red]\n\nCould not get info for {symbol}."
" This affects 'alloc' command.[/red]\n"
)
return False
p_bar.n += 1
p_bar.refresh()
return True
@log_start_end(log=logger)
def __mimic_trades_for_benchmark(self, full_shares: bool = False):
"""Mimic trades from the transactions based on chosen benchmark assuming partial shares
@@ -650,7 +673,9 @@ class PortfolioEngine:
whether to use close or adjusted close prices
"""
p_bar = tqdm(range(len(self.tickers)), desc=" Loading price data")
p_bar = tqdm(
range(len(self.tickers)), desc=" Loading price data", leave=False
)
for ticker_type, data in self.tickers.items():
price_data = yf.download(
@@ -781,7 +806,7 @@ class PortfolioEngine:
from any sales during the period [Cash Inflow(t)].
"""
p_bar = tqdm(range(1), desc=" Calculating returns")
p_bar = tqdm(range(1), desc=" Calculating returns", leave=False)
trade_data = self.historical_trade_data
@@ -906,6 +931,12 @@ class PortfolioEngine:
Flag to force recalculate allocation if already exists
"""
if not self.benchmark_info:
return
if self.portfolio_trades.empty:
return
if category == "Asset":
if (
self.benchmark_assets_allocation.empty

View File

@@ -58,8 +58,8 @@ def generate_portfolio(
transactions = PortfolioEngine.read_transactions(transactions_file_path)
portfolio_engine = PortfolioEngine(transactions)
portfolio_engine.generate_portfolio_data()
portfolio_engine.set_benchmark(symbol=benchmark_symbol, full_shares=full_shares)
portfolio_engine.set_risk_free_rate(risk_free_rate)
portfolio_engine.set_benchmark(symbol=benchmark_symbol, full_shares=full_shares)
return portfolio_engine
@@ -92,7 +92,7 @@ def get_transactions(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
@log_start_end(log=logger)
def set_benchmark(
portfolio_engine: PortfolioEngine, symbol: str, full_shares: bool = False
):
) -> bool:
"""Load benchmark into portfolio
Parameters
@@ -106,6 +106,11 @@ def set_benchmark(
Whether to mimic the portfolio trades exactly (partial shares) or round down the
quantity to the nearest number
Returns
-------
bool
True if successful, False otherwise
Examples
--------
>>> from openbb_terminal.sdk import openbb
@@ -113,7 +118,7 @@ def set_benchmark(
>>> output = openbb.portfolio.bench(p, symbol="SPY")
"""
portfolio_engine.set_benchmark(symbol=symbol, full_shares=full_shares)
return portfolio_engine.set_benchmark(symbol=symbol, full_shares=full_shares)
@log_start_end(log=logger)
@@ -366,7 +371,36 @@ def get_monthly_returns(
],
)
return monthly_returns, bench_monthly_returns
years = [
(year, instrument)
for year in monthly_returns.index
for instrument in ["Portfolio", "Benchmark", "Alpha"]
]
total_monthly_returns = pd.DataFrame(
np.nan,
columns=monthly_returns.columns,
index=pd.MultiIndex.from_tuples(years, names=["Year", "Instrument"]),
)
for year in monthly_returns.index:
for instrument in ["Portfolio", "Benchmark", "Alpha"]:
if instrument == "Portfolio":
total_monthly_returns.loc[
(year, instrument), monthly_returns.columns
] = monthly_returns.loc[year].values
elif instrument == "Benchmark":
total_monthly_returns.loc[
(year, instrument), monthly_returns.columns
] = bench_monthly_returns.loc[year].values
elif instrument == "Alpha":
total_monthly_returns.loc[
(year, instrument), monthly_returns.columns
] = (
monthly_returns.loc[year].values
- bench_monthly_returns.loc[year].values
)
return monthly_returns, bench_monthly_returns, total_monthly_returns
@log_start_end(log=logger)
@@ -430,6 +464,13 @@ def join_allocation(
pd.DataFrame
DataFrame with portfolio and benchmark allocations
"""
if portfolio.empty:
portfolio = pd.DataFrame(columns=[column, "Portfolio"])
if benchmark.empty:
benchmark = pd.DataFrame(columns=[column, "Benchmark"])
combined = pd.merge(portfolio, benchmark, on=column, how="left")
combined["Difference"] = combined["Portfolio"] - combined["Benchmark"]
combined = combined.replace(np.nan, "-")

View File

@@ -145,6 +145,7 @@ def display_assets_allocation(
portfolio_engine: PortfolioEngine,
limit: int = 10,
tables: bool = False,
recalculate: bool = False,
):
"""Display portfolio asset allocation compared to the benchmark
@@ -152,16 +153,19 @@ def display_assets_allocation(
----------
portfolio_engine: PortfolioEngine
Instance of PortfolioEngine class
tables: bool
Whether to include separate asset allocation tables
limit: int
The amount of assets you wish to show, by default this is set to 10
tables: bool
Whether to include separate asset allocation tables
recalculate: bool
Whether to recalculate the allocation
"""
combined, portfolio_allocation, benchmark_allocation = get_assets_allocation(
portfolio_engine=portfolio_engine,
limit=limit,
tables=True,
recalculate=recalculate,
)
display_category(
@@ -179,6 +183,7 @@ def display_sectors_allocation(
portfolio_engine: PortfolioEngine,
limit: int = 10,
tables: bool = False,
recalculate: bool = False,
):
"""Display portfolio sector allocation compared to the benchmark
@@ -190,12 +195,15 @@ def display_sectors_allocation(
The amount of assets you wish to show, by default this is set to 10
tables: bool
Whether to include separate asset allocation tables
recalculate: bool
Whether to recalculate the allocation
"""
combined, portfolio_allocation, benchmark_allocation = get_sectors_allocation(
portfolio_engine=portfolio_engine,
limit=limit,
tables=True,
recalculate=recalculate,
)
display_category(
@@ -213,6 +221,7 @@ def display_countries_allocation(
portfolio_engine: PortfolioEngine,
limit: int = 10,
tables: bool = False,
recalculate: bool = False,
):
"""Display portfolio country allocation compared to the benchmark
@@ -224,12 +233,15 @@ def display_countries_allocation(
The amount of assets you wish to show, by default this is set to 10
tables: bool
Whether to include separate asset allocation tables
recalculate: bool
Whether to recalculate the allocation
"""
combined, portfolio_allocation, benchmark_allocation = get_countries_allocation(
portfolio_engine=portfolio_engine,
limit=limit,
tables=True,
recalculate=recalculate,
)
display_category(
@@ -247,6 +259,7 @@ def display_regions_allocation(
portfolio_engine: PortfolioEngine,
limit: int = 10,
tables: bool = False,
recalculate: bool = False,
):
"""Display portfolio region allocation compared to the benchmark
@@ -258,12 +271,15 @@ def display_regions_allocation(
The amount of assets you wish to show, by default this is set to 10
tables: bool
Whether to include separate asset allocation tables
recalculate: bool
Whether to recalculate the allocation
"""
combined, portfolio_allocation, benchmark_allocation = get_regions_allocation(
portfolio_engine=portfolio_engine,
limit=limit,
tables=True,
recalculate=recalculate,
)
display_category(
@@ -459,7 +475,8 @@ def display_yearly_returns(
def display_monthly_returns(
portfolio_engine: PortfolioEngine,
window: str = "all",
raw: bool = False,
instrument: str = "both",
graph: bool = False,
show_vals: bool = False,
export: str = "",
sheet_name: str = None,
@@ -474,27 +491,38 @@ def display_monthly_returns(
Use `portfolio.load` to create a PortfolioEngine.
window : str
interval to compare cumulative returns and benchmark
raw : False
Display raw data from cumulative return
instrument : str
Display raw data from cumulative return, default is showing both the portfolio and benchmark in one table
show_vals : False
Show values on heatmap
export : str
Export certain type of data
sheet_name : str
Whether to export to a specific sheet name in an Excel file
external_axes: plt.Axes
Optional axes to display plot on
"""
portfolio_returns, benchmark_returns = get_monthly_returns(portfolio_engine, window)
portfolio_returns, benchmark_returns, total_monthly_returns = get_monthly_returns(
portfolio_engine, window
)
if raw:
if instrument == "portfolio":
print_rich_table(
portfolio_returns,
title="Monthly returns - portfolio [%]",
headers=portfolio_returns.columns,
show_index=True,
)
console.print("\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"mret_portfolio",
portfolio_returns,
sheet_name,
)
elif instrument == "benchmark":
print_rich_table(
benchmark_returns,
title="Monthly returns - benchmark [%]",
@@ -502,7 +530,49 @@ def display_monthly_returns(
show_index=True,
)
else:
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"mret_benchmark",
benchmark_returns,
sheet_name,
)
elif instrument == "both":
# Converts multi-index into readable rich table while keeping the multi-index intact for export
total_monthly_returns_rich = total_monthly_returns.copy()
total_monthly_returns_rich.insert(
0, "Instruments", total_monthly_returns_rich.index.get_level_values(1)
)
total_monthly_returns_rich.insert(
0,
"Year",
sum(
[
[year, "", ""]
for year in total_monthly_returns_rich.index.get_level_values(
0
).unique()
],
[],
),
)
print_rich_table(
total_monthly_returns_rich,
title="Total Monthly returns [%]",
headers=total_monthly_returns_rich.columns,
show_index=False,
)
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"mret_total",
total_monthly_returns,
sheet_name,
)
if graph or show_vals:
if external_axes is None:
_, ax = plt.subplots(
2,
@@ -548,14 +618,6 @@ def display_monthly_returns(
if external_axes is None:
theme.visualize_output()
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"mret",
portfolio_returns,
sheet_name,
)
@log_start_end(log=logger)
def display_daily_returns(