mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-28 04:52:07 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
|
||||
4
openbb_terminal/miscellaneous/i18n/en.yml
vendored
4
openbb_terminal/miscellaneous/i18n/en.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
23
openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb
vendored
Normal file
23
openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb
vendored
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
):
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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"},
|
||||
),
|
||||
),
|
||||
(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user