mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
342 lines
11 KiB
Python
342 lines
11 KiB
Python
"""Portfolio View"""
|
|
__docformat__ = "numpy"
|
|
|
|
from typing import List
|
|
from datetime import datetime
|
|
from io import BytesIO
|
|
from os import path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
from tabulate import tabulate
|
|
from reportlab.lib.pagesizes import letter
|
|
from reportlab.pdfgen import canvas
|
|
from reportlab.lib.utils import ImageReader
|
|
from matplotlib.lines import Line2D
|
|
from matplotlib import pyplot as plt
|
|
import matplotlib.ticker as mtick
|
|
from pypfopt import plotting
|
|
|
|
from gamestonk_terminal.config_plot import PLOT_DPI
|
|
from gamestonk_terminal import feature_flags as gtff
|
|
from gamestonk_terminal.portfolio import (
|
|
portfolio_model,
|
|
yfinance_model,
|
|
)
|
|
from gamestonk_terminal.portfolio import reportlab_helpers
|
|
from gamestonk_terminal.helper_funcs import get_rf
|
|
from gamestonk_terminal.portfolio.portfolio_optimization import optimizer_model
|
|
|
|
|
|
def load_info():
|
|
"""Prints instructions to load a CSV
|
|
|
|
Returns
|
|
----------
|
|
text : str
|
|
Information on how to load a csv
|
|
"""
|
|
text = """
|
|
In order to load a CSV do the following:
|
|
|
|
1. Add headers to the first row, below is data for each column:\n
|
|
\t1. Identifier for the asset (such as a stock ticker)
|
|
\t2. Type of asset (stock, bond, option, crypto)
|
|
\t3. The volume of the asset transacted
|
|
\t4. The buy date in yyyy/mm/dd
|
|
\t5. The Price paid for the asset
|
|
\t6. Any fees paid during the transaction
|
|
\t7. A premium paid or received if this was an option
|
|
\t8. Whether the asset was bought (covered) or sold (shorted)\n
|
|
2. Place this file in gamestonk_terminal/portfolio/portfolios\n
|
|
"""
|
|
print(text)
|
|
|
|
|
|
def show_df(df: pd.DataFrame, show: bool) -> None:
|
|
"""Shows the given dataframe
|
|
|
|
Parameters
|
|
----------
|
|
df : pd.DataFrame
|
|
The dataframe to be shown
|
|
show : bool
|
|
Whether to show the dataframe index
|
|
"""
|
|
|
|
df = df.dropna(how="all", axis=1).fillna("")
|
|
|
|
if gtff.USE_TABULATE_DF:
|
|
print(
|
|
tabulate(
|
|
df,
|
|
headers=df.columns,
|
|
floatfmt=".2f",
|
|
showindex=show,
|
|
tablefmt="fancy_grid",
|
|
),
|
|
"\n",
|
|
)
|
|
else:
|
|
print(df.to_string, "\n")
|
|
|
|
|
|
def plot_overall_return(
|
|
comb: pd.DataFrame, m_tick: str, plot: bool = False
|
|
) -> ImageReader:
|
|
"""Generates overall return graph
|
|
|
|
Parameters
|
|
----------
|
|
comb : pd.DataFrame
|
|
Dataframe with returns
|
|
m_tick : str
|
|
The ticker for the market asset
|
|
plot : bool
|
|
Whether to plot the graph or return it for PDF
|
|
|
|
Returns
|
|
----------
|
|
img : ImageReader
|
|
Overal return graph
|
|
"""
|
|
fig, ax = plt.subplots(figsize=(10, 5))
|
|
ax.plot(comb.index, comb["return"], color="tab:blue", label="Portfolio")
|
|
ax.plot(comb.index, comb[("Market", "Return")], color="orange", label=m_tick)
|
|
|
|
ax.set_ylabel("", fontweight="bold", fontsize=12, color="black")
|
|
ax.set_xlabel("")
|
|
ax.yaxis.set_label_coords(-0.1, 0.5)
|
|
ax.grid(True)
|
|
ax.spines["top"].set_visible(False)
|
|
ax.spines["right"].set_visible(False)
|
|
ax.spines["bottom"].set_visible(False)
|
|
ax.spines["left"].set_visible(False)
|
|
fig.suptitle(
|
|
"Cumulative Performance", y=0.99, fontweight="bold", fontsize=14, color="black"
|
|
)
|
|
ax.axhline(0, ls="-", lw=1, color="gray", zorder=1)
|
|
ax.axhline(0, ls="--", lw=1, color="black", zorder=2)
|
|
fig.set_facecolor("white")
|
|
ax.set_title(
|
|
f'{comb.index[:1][0].strftime("%Y/%m/%d")} - {comb.index[-1:][0].strftime("%Y/%m/%d")}',
|
|
fontsize=12,
|
|
color="gray",
|
|
)
|
|
ax.yaxis.set_major_formatter(mtick.PercentFormatter(1.0))
|
|
ax.set_facecolor("white")
|
|
ax.legend()
|
|
fig.autofmt_xdate()
|
|
if plot:
|
|
plt.show()
|
|
print("")
|
|
return None
|
|
imgdata = BytesIO()
|
|
fig.savefig(imgdata, format="png")
|
|
plt.close("all")
|
|
imgdata.seek(0)
|
|
return ImageReader(imgdata)
|
|
|
|
|
|
def plot_rolling_beta(df: pd.DataFrame) -> ImageReader:
|
|
"""Returns a chart with the portfolio's rolling beta
|
|
|
|
Parameters
|
|
----------
|
|
df : pd.DataFrame
|
|
The dataframe to be analyzed
|
|
|
|
Returns
|
|
----------
|
|
img : ImageReader
|
|
Rolling beta graph
|
|
"""
|
|
|
|
fig, ax = plt.subplots(figsize=(10, 5))
|
|
ax.plot(
|
|
df.index,
|
|
df["total"],
|
|
color="tab:blue",
|
|
)
|
|
|
|
ax.set_ylabel("", fontweight="bold", fontsize=12, color="black")
|
|
ax.set_xlabel("")
|
|
ax.yaxis.set_label_coords(-0.1, 0.5)
|
|
ax.grid(True)
|
|
ax.spines["top"].set_visible(False)
|
|
ax.spines["right"].set_visible(False)
|
|
ax.spines["bottom"].set_visible(False)
|
|
ax.spines["left"].set_visible(False)
|
|
fig.suptitle(
|
|
"Rolling Beta of Stocks", y=0.99, fontweight="bold", fontsize=14, color="black"
|
|
)
|
|
ax.axhline(0, ls="-", lw=1, color="gray", zorder=1)
|
|
ax.axhline(0, ls="--", lw=1, color="black", zorder=2)
|
|
fig.set_facecolor("white")
|
|
ax.set_title(
|
|
f'{df.index[:1][0].strftime("%Y-%m-%d")} - {df.index[-1:][0].strftime("%Y-%m-%d")}',
|
|
color="gray",
|
|
)
|
|
ax.set_facecolor("white")
|
|
fig.autofmt_xdate()
|
|
imgdata = BytesIO()
|
|
fig.savefig(imgdata, format="png")
|
|
plt.close("all")
|
|
imgdata.seek(0)
|
|
return ImageReader(imgdata)
|
|
|
|
|
|
def plot_ef(
|
|
stocks: List[str],
|
|
variance: float,
|
|
per_ret: float,
|
|
rf_rate: float,
|
|
period: str = "3mo",
|
|
n_portfolios: int = 300,
|
|
risk_free: bool = False,
|
|
):
|
|
"""Display efficient frontier
|
|
|
|
Parameters
|
|
----------
|
|
stocks : List[str]
|
|
List of the stocks to be included in the weights
|
|
variance : float
|
|
The variance for the portfolio
|
|
per_ret : float
|
|
The portfolio's return for the portfolio
|
|
rf_rate : float
|
|
The risk free rate
|
|
period : str
|
|
The period to track
|
|
n_portfolios : int
|
|
The number of portfolios to generate
|
|
risk_free : bool
|
|
Include the risk-free asset
|
|
"""
|
|
fig, ax = plt.subplots(figsize=(10, 5), dpi=PLOT_DPI)
|
|
ef, rets, stds = optimizer_model.generate_random_portfolios(
|
|
[x.upper() for x in stocks], period, n_portfolios
|
|
)
|
|
sharpes = rets / stds
|
|
ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r")
|
|
plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True)
|
|
# Find the tangency portfolio
|
|
ret_sharpe, std_sharpe, _ = ef.portfolio_performance(risk_free_rate=rf_rate)
|
|
ax.scatter(std_sharpe, ret_sharpe, marker="*", s=100, c="r", label="Max Sharpe")
|
|
plt.plot(variance, per_ret, "ro", label="Portfolio")
|
|
# Add risk free line
|
|
if risk_free:
|
|
y = ret_sharpe * 1.2
|
|
m = (ret_sharpe - rf_rate) / std_sharpe
|
|
x2 = (y - rf_rate) / m
|
|
x = [0, x2]
|
|
y = [rf_rate, y]
|
|
line = Line2D(x, y, color="#FF0000", label="Capital Allocation Line")
|
|
ax.set_xlim(xmin=min(stds) * 0.8)
|
|
ax.add_line(line)
|
|
ax.set_title(f"Efficient Frontier simulating {n_portfolios} portfolios")
|
|
ax.legend()
|
|
fig.tight_layout()
|
|
ax.grid(b=True, which="major", color="#666666", linestyle="-")
|
|
|
|
if gtff.USE_ION:
|
|
plt.ion()
|
|
|
|
imgdata = BytesIO()
|
|
fig.savefig(imgdata, format="png")
|
|
plt.close("all")
|
|
imgdata.seek(0)
|
|
return ImageReader(imgdata)
|
|
|
|
|
|
class Report:
|
|
def __init__(self, df: pd.DataFrame, hist: pd.DataFrame, m_tick: str, n: int):
|
|
"""Generate financial reports.
|
|
Financial reports allow users to show the how they have been performing in
|
|
trades. This allows for a simple way to show progress and analyze metrics
|
|
that track portfolio performance
|
|
|
|
Parameters
|
|
----------
|
|
df : pd.DataFrame
|
|
The dataframe with previous holdings information
|
|
hist : pd.DataFrame
|
|
The dataframe with previous prices for stocks in the portfolio
|
|
m_tick : str
|
|
The market asset to be identified
|
|
n : int
|
|
The number of days to analyze
|
|
|
|
Attributes
|
|
----------
|
|
generate_report : None
|
|
Generates a report with the given parameters
|
|
generate_pg1 : None
|
|
Creates the first page of the PDF report
|
|
generate_pg2 : None
|
|
Creates the second page of the PDF report
|
|
|
|
"""
|
|
self.df = df
|
|
self.hist = hist
|
|
self.m_tick = m_tick
|
|
self.df_m = yfinance_model.get_market(self.df.index[0], self.m_tick)
|
|
self.returns, self.variance = portfolio_model.get_return(df, self.df_m, n)
|
|
self.rf = get_rf()
|
|
self.betas = portfolio_model.get_rolling_beta(
|
|
self.df, self.hist, self.df_m, 365
|
|
)
|
|
|
|
def generate_report(self) -> None:
|
|
d = path.dirname(path.abspath(__file__)).replace(
|
|
"gamestonk_terminal", "exports"
|
|
)
|
|
loc = path.abspath(
|
|
path.join(
|
|
d,
|
|
f"ar_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
|
|
)
|
|
)
|
|
report = canvas.Canvas(loc, pagesize=letter)
|
|
reportlab_helpers.base_format(report, "Overview")
|
|
self.generate_pg1(report)
|
|
self.generate_pg2(report)
|
|
report.save()
|
|
print("File save in:\n", loc, "\n")
|
|
|
|
def generate_pg1(self, report: canvas.Canvas) -> None:
|
|
report.drawImage(
|
|
plot_overall_return(self.returns, self.m_tick, False), 15, 400, 600, 300
|
|
)
|
|
main_text = portfolio_model.get_main_text(self.returns)
|
|
reportlab_helpers.draw_paragraph(report, main_text, 30, 410, 550, 200)
|
|
current_return = self.returns["return"][-1]
|
|
beta = self.betas["total"][-1]
|
|
market_return = self.returns[("Market", "Return")][-1]
|
|
sharpe = f"{(current_return - self.rf)/ np.std(self.returns['return']):.2f}"
|
|
treynor = f"{(current_return - self.rf)/ beta:.2f}" if beta > 0 else "N/A"
|
|
alpha = f"{current_return - (self.rf + beta * (market_return - self.rf)):.2f}"
|
|
information = (
|
|
f"{float(alpha)/ (np.std(self.returns['return'] - market_return)):.2f}"
|
|
)
|
|
perf = [
|
|
["Sharpe", sharpe],
|
|
["Treynor", treynor],
|
|
["Alpha", alpha],
|
|
["Information", information],
|
|
]
|
|
reportlab_helpers.draw_table(report, "Performance", 540, 300, 30, perf)
|
|
reportlab_helpers.draw_paragraph(
|
|
report, portfolio_model.performance_text, 140, 290, 460, 200
|
|
)
|
|
report.showPage()
|
|
|
|
def generate_pg2(self, report: canvas.Canvas) -> None:
|
|
reportlab_helpers.base_format(report, "Portfolio Analysis")
|
|
if "Holding" in self.df.columns:
|
|
report.drawImage(plot_rolling_beta(self.betas), 15, 400, 600, 300)
|
|
main_t = portfolio_model.get_beta_text(self.betas)
|
|
reportlab_helpers.draw_paragraph(report, main_t, 30, 410, 550, 200)
|
|
# report.drawImage(plot_ef(uniques, self.variance, self.returns["return"][-1], self.rf), 15, 65, 600, 300)
|