Portfolio menu bug fixes (#3204)

* normalized way moving average is treated and prevent huge exception prompt

* changed descriptions on docs

* change to check_positive_float

* add integration tests

* fix linting

* add more integration tests

* add more integration tests

* fix linting

* add some po integration tests

* fix flag without prompt

* change orderbook to transactions

* limit warning to portfolio

* change print help

* format portfolio var

* reformat controller

* reformat es

* remove autocompletion

* change print help

* add percentage symbol to summary

* fix holp holv

* fix scripts

* update website doc

* add tqdm progress bars

* fix table spacing

* identify mret tables

* remove positive float from rfr

* flake8

* pylint

* fix reports

* revert to old menu help

* revert to old menu help

* Update test_portfolio.openbb

* quick change on empty lines

Co-authored-by: hjoaquim <h.joaquim@campus.fct.unl.pt>
Co-authored-by: James Maslek <jmaslek11@gmail.com>
This commit is contained in:
montezdesousa
2022-11-09 10:46:42 +00:00
committed by GitHub
parent 5b739b0b48
commit f9086d6f38
16 changed files with 451 additions and 280 deletions

View File

@@ -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

View File

@@ -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",
)

View File

@@ -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

View File

@@ -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
exit

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,
):

View File

@@ -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": {

View File

@@ -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",
" )"
]
},

View File

@@ -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]

View File

@@ -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"},
),
),
(

View File

@@ -7,7 +7,7 @@ OpenBB reports:
etf <symbol>
forecast <symbol>
forex <symbol>
portfolio <orderbook>
portfolio <transactions>
Custom reports:
run Run a Jupyter notebook from OpenBBUserData/reports/custom reports

View File

@@ -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)

View File

@@ -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)