mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-12 12:22:35 +00:00
Allocation Command (#1204)
* Adding the allocation (al) command to the portfolio menu * Fix linting * Fixing errors * More fixes * Requested changes * More fixes Co-authored-by: jmaslek <jmaslek11@gmail.com>
This commit is contained in:
@@ -16,6 +16,7 @@ from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
valid_date,
|
||||
check_positive_float,
|
||||
check_positive,
|
||||
)
|
||||
from gamestonk_terminal.menu import session
|
||||
|
||||
@@ -42,6 +43,7 @@ class PortfolioController(BaseController):
|
||||
"rmv",
|
||||
"ar",
|
||||
"rmr",
|
||||
"al",
|
||||
]
|
||||
CHOICES_MENUS = [
|
||||
"bro",
|
||||
@@ -90,6 +92,7 @@ class PortfolioController(BaseController):
|
||||
|
||||
[info]Graphs:[/info][cmds]
|
||||
rmr graph your returns versus the market's returns
|
||||
al displays the allocation of the portfolio
|
||||
"""
|
||||
console.print(text=help_text, menu="Portfolio")
|
||||
|
||||
@@ -350,3 +353,52 @@ class PortfolioController(BaseController):
|
||||
console.print("Cannot generate a graph from an empty dataframe\n")
|
||||
else:
|
||||
console.print("Please add items to the portfolio\n")
|
||||
|
||||
def call_al(self, other_args: List[str]):
|
||||
"""Process al command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="al",
|
||||
description="Gives allocation",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
help="Number to show",
|
||||
type=check_positive,
|
||||
default=20,
|
||||
dest="limit",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--noetf",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="no_etf_positions",
|
||||
help="If etf positions should not be added to the portfolio",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-g",
|
||||
"--graph",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="graph",
|
||||
help="Display table or pi chart of holdings",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not self.portfolio.empty:
|
||||
val, hist = portfolio_model.convert_df(self.portfolio)
|
||||
if not val.empty:
|
||||
df = portfolio_model.get_allocation(
|
||||
val,
|
||||
hist,
|
||||
self.portfolio,
|
||||
ns_parser.limit,
|
||||
ns_parser.no_etf_positions,
|
||||
)
|
||||
portfolio_view.display_allocation(df, ns_parser.graph)
|
||||
else:
|
||||
console.print("Cannot generate a graph from an empty dataframe\n")
|
||||
else:
|
||||
console.print("Please add items to the portfolio\n")
|
||||
|
||||
@@ -16,6 +16,7 @@ from gamestonk_terminal.portfolio import (
|
||||
yfinance_model,
|
||||
portfolio_helper,
|
||||
)
|
||||
from gamestonk_terminal.etf import stockanalysis_model
|
||||
from gamestonk_terminal.rich_config import console
|
||||
|
||||
# pylint: disable=E1136
|
||||
@@ -278,7 +279,7 @@ def convert_df(portfolio: pd.DataFrame) -> pd.DataFrame:
|
||||
cashes = changes[changes["Type"] == "cash"]
|
||||
if cashes.empty:
|
||||
raise ValueError("Brokers require cash, input cash deposits")
|
||||
changes = changes[changes["Type"] == ("stock" or "etf")]
|
||||
changes = changes[(changes["Type"] == "stock") | (changes["Type"] == "etf")]
|
||||
uniques = list(set(changes["Name"].tolist()))
|
||||
if uniques:
|
||||
hist = yfinance_model.get_stocks(uniques, min(changes["Date"]))
|
||||
@@ -387,6 +388,137 @@ def get_rolling_beta(
|
||||
return comb
|
||||
|
||||
|
||||
def fix_etf_allocation(
|
||||
data: pd.DataFrame,
|
||||
df_new: pd.DataFrame,
|
||||
etf: str,
|
||||
no_etf_positions: bool,
|
||||
number: int,
|
||||
):
|
||||
"""Adjusting the dataframe to etf positions, by adding etf holdings to identical portfolio positions or
|
||||
if 'add_etf_positions' is true add a new position
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : pd.DataFrame
|
||||
The dataframe of positions
|
||||
df_new: pd.DataFrame
|
||||
A dataframe of etf holdings
|
||||
etf: str
|
||||
String of the etf
|
||||
no_etf_positions: bool
|
||||
If new positions should not be added to the 'data' dataframe for etf holdings
|
||||
number: int
|
||||
Number of etf positions to be checked
|
||||
|
||||
Returns
|
||||
----------
|
||||
data: pd.DataFrame
|
||||
etf adjusted dataframe
|
||||
"""
|
||||
i = 0
|
||||
length = int(df_new.index.size) - 1
|
||||
while i <= (
|
||||
length ^ ((number ^ length) & -(number < length))
|
||||
): # gets minimum of number and length
|
||||
if df_new.iloc[[i]].index[0].lower() in data.index:
|
||||
change = (
|
||||
float(df_new.iloc[i]["% Of Etf"].replace("%", ""))
|
||||
* data["value"].loc[data.index == etf][0]
|
||||
/ 100
|
||||
)
|
||||
data.loc[data.index == df_new.iloc[[i]].index[0].lower(), "value"] += change
|
||||
elif not no_etf_positions:
|
||||
change = (
|
||||
float(df_new.iloc[i]["% Of Etf"].replace("%", ""))
|
||||
* data["value"].loc[data.index == etf][0]
|
||||
/ 100
|
||||
)
|
||||
data.loc[len(data.index)] = [f"{etf}_etf", change]
|
||||
data.rename(
|
||||
index={len(data.index) - 1: df_new.iloc[[i]].index[0].lower()},
|
||||
inplace=True,
|
||||
)
|
||||
data.loc[data.index == etf, "value"] -= change
|
||||
data.loc[data.index == etf, "value"] -= change
|
||||
i += 1
|
||||
data.rename(
|
||||
index={data.loc[data.index == etf].index[0]: f"{etf}_rest"}, inplace=True
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def get_allocation(
|
||||
data: pd.DataFrame,
|
||||
hist: pd.DataFrame,
|
||||
portfolio: pd.DataFrame,
|
||||
number: int,
|
||||
no_etf_positions: bool,
|
||||
) -> pd.DataFrame:
|
||||
"""Formatting the dataframe to an allocation dataframe of the positions
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : pd.DataFrame
|
||||
The dataframe of positions
|
||||
hist: pd.DataFrame
|
||||
A dataframe of the positions historical performances
|
||||
portfolio: pd.DataFrame
|
||||
Portfolio dataframe
|
||||
number: int
|
||||
Number of etf positions to be checked
|
||||
no_etf_positions: bool
|
||||
If new positions should not be added to the 'data' dataframe for etf holdings
|
||||
|
||||
Returns
|
||||
----------
|
||||
df: pd.DataFrame
|
||||
Dataframe of allocation
|
||||
"""
|
||||
# Fixing dataframe formatting
|
||||
df = data["Quantity"].tail(1).transpose()
|
||||
df.columns = ["amount"]
|
||||
df = df.sort_index(axis=0, ascending=True)
|
||||
hist = hist.tail(1).transpose()
|
||||
hist = hist.reset_index(["first"])
|
||||
hist.columns = ["position", "value"]
|
||||
hist = pd.DataFrame(hist["value"])
|
||||
|
||||
i = 0
|
||||
value = []
|
||||
while i < df.index.size:
|
||||
value.append(df["amount"].iloc[i] * hist["value"].iloc[i])
|
||||
i += 1
|
||||
df["value"] = value
|
||||
|
||||
i = 0
|
||||
length = df.index.size
|
||||
while i < length:
|
||||
row_index = df.iloc[[i]].index[0]
|
||||
if (
|
||||
portfolio["Type"].loc[portfolio["Name"] == row_index].to_list()[0] == "etf"
|
||||
): # check if position is an etf
|
||||
df = fix_etf_allocation(
|
||||
df,
|
||||
stockanalysis_model.get_etf_holdings(row_index),
|
||||
row_index,
|
||||
no_etf_positions,
|
||||
number,
|
||||
)
|
||||
if df.iloc[i].loc["value"] < 0:
|
||||
# Converting negative positions to "shorts" in order to correctly calculate allocation
|
||||
df.loc[row_index, "value"] = abs(df.loc[row_index, "value"])
|
||||
df.loc[row_index, "amount"] = abs(df.loc[row_index, "amount"])
|
||||
df.rename(index={row_index: f"short_{row_index}"}, inplace=True)
|
||||
i += 1
|
||||
|
||||
total_value = df["value"].sum()
|
||||
df["pct_allocation"] = df["value"] / total_value * 100 # calculate allocation
|
||||
df = df.sort_values(by="pct_allocation", ascending=False)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def get_main_text(df: pd.DataFrame) -> str:
|
||||
"""Get main performance summary from a dataframe with returns
|
||||
|
||||
|
||||
@@ -251,6 +251,39 @@ def plot_ef(
|
||||
return ImageReader(imgdata)
|
||||
|
||||
|
||||
def display_allocation(data: pd.DataFrame, graph: bool):
|
||||
"""Displays allocation
|
||||
Parameters
|
||||
----------
|
||||
data: pd.DataFrame
|
||||
The portfolio allocation dataframe
|
||||
graph: bool
|
||||
If pie chart shall be displayed instead of table"""
|
||||
if graph:
|
||||
labels = data.index.values
|
||||
sizes = data["value"].to_list()
|
||||
fig, ax = plt.subplots()
|
||||
ax.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=90)
|
||||
ax.axis("equal")
|
||||
ax.set_title("Portfolio Allocation")
|
||||
fig.set_tight_layout(True)
|
||||
|
||||
plt.show()
|
||||
else:
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
data,
|
||||
headers=data.columns,
|
||||
tablefmt="fancy_grid",
|
||||
floatfmt=".2f",
|
||||
),
|
||||
)
|
||||
else:
|
||||
console.print(data.to_string())
|
||||
console.print("")
|
||||
|
||||
|
||||
class Report:
|
||||
def __init__(self, df: pd.DataFrame, hist: pd.DataFrame, m_tick: str, n: int):
|
||||
"""Generate financial reports.
|
||||
|
||||
2
website/content/portfolio/_index.md
vendored
2
website/content/portfolio/_index.md
vendored
@@ -11,6 +11,7 @@ What do you want to do?
|
||||
|
||||
> bro brokers holdings, supports: robinhood, ally, degiro, coinbase
|
||||
> po portfolio optimization, optimal portfolio weights from pyportfolioopt
|
||||
> pa portfolio analysis, analyse portfolios
|
||||
|
||||
Portfolio:
|
||||
load load data into the portfolio
|
||||
@@ -24,4 +25,5 @@ Reports:
|
||||
|
||||
Graphs:
|
||||
rmr graph your returns versus the market's returns
|
||||
al displays the allocation of the portfolio
|
||||
```
|
||||
|
||||
13
website/content/portfolio/al/_index.md
vendored
Normal file
13
website/content/portfolio/al/_index.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
```
|
||||
usage: al [-l] [-n] [-g] [-h]
|
||||
```
|
||||
|
||||
Displays the allocation of the portfolio
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-l, --limit number of etf positions to go through (default: 20)
|
||||
-n, --noetf if etf positions should not be added to the portfolio (default: False)
|
||||
-g, --graph display pi chart of holdings (default: False)
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
Reference in New Issue
Block a user