more refactoring + edit pie chart function

This commit is contained in:
Jamie
2021-04-14 11:56:56 -04:00
parent aeaf6b4e16
commit f33dddebc4
4 changed files with 111 additions and 30 deletions

View File

@@ -117,7 +117,7 @@ The usage is:
eff_risk [-p PERIOD] [-r --risk RISK_LEVEL] [-v --value VALUE] [--pie]
````
* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo.
* -r/--risk Risk tolerance. 5% is 0.05.
* -r/--risk Risk tolerance. Default is 0.1 (10%)
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
@@ -128,15 +128,42 @@ The usage is:
eff_ret [-p PERIOD] [-r --return] [-v --value VALUE] [--pie]
````
* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo.
* -r/--return. Desired return. 5% is 0.05.
* -r/--return. Desired return. Default is 0.1 (10%)
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
### show_eff
This function plots random portfolios basd on their risk and returns and shows the efficient frontier.
This function plots random portfolios based on their risk and returns and shows the efficient frontier.
The usage is:
````
show_eff [-p PERIOD] [-n N_PORTFOLIOS]
````
* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo.
* -n Number of portfolios to simulate.
* -n Number of portfolios to simulate.
##Sample Usage
In this example, we generate weights for a list of 6 stocks using the eff_ret command. This optimization looks to maximize returns
at a given risk level. We start by adding the stocks we want to analyze:
````
select aapl,amzn,msft,f,gm,ge
````
Which shows:
````
Current Tickers: GE, GM, AMZN, AAPL, F, MSFT
````
To perform the optimization, we will set a target return of 25% (.25). Given how stocks performed during COVID,
there is a lot of volatility, so many optimizations may not get low volatility. The module also returns annualized volatility,
so the number is your portfolio volatility * `sqrt(252`. Ths optimization will be (including a pie chart!). Note we could also supply a different
time period, which changes the expected returns and historical volatility, which changes the optimization. We could also specify a dollar
amount that you wish to allocate using the `-v` flag.
````
eff_ret -r .25 --pie
````
The console will show (numbers will vary based on when this is done).
![console](https://user-images.githubusercontent.com/18151143/114740311-bd429c80-9d17-11eb-90e2-97430781431a.png)
And the pie chart:
![yummypie](https://user-images.githubusercontent.com/18151143/114740289-b9167f00-9d17-11eb-9c29-470785b21d09.png)
Note that since `AAPL` had zero allocation, it was ommitted from the chart.

View File

@@ -2,9 +2,10 @@
__docformat__ = "numpy"
import argparse
from typing import List
from typing import List, Optional
import math
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
from pypfopt.efficient_frontier import EfficientFrontier
@@ -14,7 +15,15 @@ from gamestonk_terminal.config_plot import PLOT_DPI
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import plot_autoscale, parse_known_args_and_warn
title_maps = {}
title_maps = {
"max_sharpe": "Maximum Sharpe Portfolio",
"min_volatility": "Minimum Volatility Portfolio",
"eff_risk": "Maximum Return Portfolio at Risk = {:1f} %",
"eff_ret": "Minimum Volatility Portfolio at Target Return = {:.1f} %",
"equal": "Equally Weighted Portfolio",
"marketCap": "MarketCap Weighted Portfolio",
"dividendYield": "Dividend Yield Weighted Portfolio",
}
def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame:
@@ -71,11 +80,24 @@ def display_weights(weights: dict):
if not weights:
return
weight_df = pd.DataFrame.from_dict(data=weights, orient="index", columns=["value"])
weight_df["weight"] = (weight_df["value"] * 100).astype(str) + " %"
print(weight_df["weight"])
if math.isclose(weight_df.sum()["value"], 1, rel_tol=0.1):
weight_df["weight"] = (weight_df["value"] * 100).astype(str).apply(
lambda s: s[:6]
) + " %"
print(pd.DataFrame(weight_df["weight"]))
else:
print(weight_df)
def pie_chart_weights(weights: dict, optimizer: str):
def my_autopct(x):
"""Function for autopct of plt.pie. This results in values not being printed in the pie if they are 'too small'"""
if x > 4:
return f"{x:.2f} %"
else:
return ""
def pie_chart_weights(weights: dict, optimizer: str, value: Optional[float]):
"""
Show a pie chart of holdings
Parameters
@@ -83,43 +105,71 @@ def pie_chart_weights(weights: dict, optimizer: str):
weights: dict
weights to display. Keys are stocks. Values are either weights or values if -v specified
optimzer: str
Optmization technique used
Optimization technique used for title
"""
plt.close("all")
if not weights:
return
stocks = list(weights.keys())
sizes = list(weights.values())
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
stocks = np.array(list(weights.keys()))
sizes = np.array(list(weights.values()))
to_not_include = sizes == 0
stocks, sizes = stocks[to_not_include == False], sizes[to_not_include == False]
total_size = np.sum(sizes)
leg_labels = [
f"{str(a)}: {str(round(100*b/total_size,3))[:4]}%"
for a, b in zip(stocks, sizes)
]
if math.isclose(sum(sizes), 1, rel_tol=0.1):
wedges, _, autotexts = ax.pie(
wedges, _, autotexts = plt.pie(
sizes,
labels=stocks,
autopct="%1.1f%%",
autopct=my_autopct,
textprops=dict(color="k"),
labeldistance=5,
explode=[s / (5 * total_size) for s in sizes],
normalize=True,
shadow=True,
)
else:
wedges, _, autotexts = ax.pie(
sizes, labels=stocks, autopct="", textprops=dict(color="k"), labeldistance=5
wedges, _, autotexts = plt.pie(
sizes,
labels=stocks,
autopct="",
textprops=dict(color="k"),
explode=[s / (5 * total_size) for s in sizes],
normalize=True,
shadow=True,
)
for i, a in enumerate(autotexts):
a.set_text(f"{sizes[i]}")
if sizes[i] / total_size > 0.05:
a.set_text(f"{sizes[i]:.2f}")
else:
a.set_text("")
ax.axis("equal")
ax.legend(
plt.axis("equal")
plt.legend(
wedges,
stocks,
leg_labels,
title="Stocks",
loc="center left",
bbox_to_anchor=(0.85, 0, 0.5, 1),
)
plt.setp(autotexts, size=8, weight="bold")
ax.set_title("Portfolio Holdings")
if optimizer in ["eff_ret", "eff_risk"]:
plt.title(title_maps[optimizer].format(100 * value))
else:
plt.title(title_maps[optimizer])
if gtff.USE_ION:
plt.ion()
plt.show()
print("")

View File

@@ -81,7 +81,7 @@ def equal_weight(stocks: List[str], other_args: List[str]):
for stock in stocks:
values[stock] = ns_parser.value * round(1 / n_stocks, 5)
if ns_parser.pie:
pie_chart_weights(values)
pie_chart_weights(values, "equal", None)
if n_stocks >= 1:
print("Equal Weight Portfolio: ")
display_weights(values)
@@ -147,7 +147,7 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s
weights[k] = round(v / prop_sum, 5) * ns_parser.value
if ns_parser.pie:
pie_chart_weights(weights)
pie_chart_weights(weights, property_type, None)
if property_type == "marketCap":
print("Market Cap Weighted Portfolio: ")
@@ -225,6 +225,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
key: ns_parser.value * round(value, 5)
for key, value in ef_sharpe.items()
}
val = None
print("Weights that maximize Sharpe Ratio:")
elif port_type == "min_volatility":
@@ -234,6 +235,7 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
key: ns_parser.value * round(value, 5)
for key, value in ef_min_vol.items()
}
val = None
print("Weights that minimize volatility")
elif port_type == "eff_risk":
@@ -243,7 +245,8 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
key: ns_parser.value * round(value, 5)
for key, value in ef_eff_risk.items()
}
print("Weights for maximizing returns at your risk level:")
val = ns_parser.risk_level
print(f"Weights for maximizing returns at risk = {100*val:.1f} %")
elif port_type == "eff_ret":
@@ -252,13 +255,14 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
key: ns_parser.value * round(value, 5)
for key, value in ef_eff_risk.items()
}
print("Weights for minimizing risk at your return:")
val = ns_parser.target_return
print(f"Weights for minimizing risk at target return = {100*val:.1f} %")
else:
raise ValueError("EF Method not found")
if ns_parser.pie:
pie_chart_weights(weights, port_type)
pie_chart_weights(weights, port_type, val)
print("")
ef.portfolio_performance(verbose=True)

View File

@@ -10,7 +10,6 @@ from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn
from gamestonk_terminal.menu import session
from gamestonk_terminal.portfolio_optimization import optimizer_view as po_api
from gamestonk_terminal.portfolio_optimization.optimizer_helper import display_weights
class PortfolioOptimization:
@@ -179,6 +178,7 @@ def menu(tickers: List[str]):
"""Portfolio Optimization Menu"""
if tickers == [""]:
tickers = []
plt.close("all")
po_controller = PortfolioOptimization(tickers)
po_controller.call_help(tickers)