diff --git a/ROADMAP.md b/ROADMAP.md
index f5c466f3a9a..073316e0213 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -14,6 +14,7 @@
* [Exploratory Data Analysis](#Exploratory-Data-Analysis)
* [Residual Analysis](#Residual-Analysis)
* [Portfolio Analysis](#Portfolio-Analysis)
+* [Portfolio Optimization](#Portfolio-Optimization)
* [FRED](#FRED)
* [Options](#Options)
* [Credit Analysis](#Credit-Analysis)
@@ -110,6 +111,7 @@
* [x] Add multi-ticker sentiment comparison (@didier) - [PR #250](https://github.com/DidierRLopes/GamestonkTerminal/pull/250)
* [x] Get similar companies from Finviz based on Industry and Sector (and possibly country) from Finviz (@didier) - [PR #323](https://github.com/DidierRLopes/GamestonkTerminal/pull/323)
* [x] Output brief screen (overview, performance, financial, valuation) across similar companies (@didier) - [PR #323](https://github.com/DidierRLopes/GamestonkTerminal/pull/323)
+* [x] Add command to take tickers to portfolio optimization (@jmaslek) - [PR #329](https://github.com/DidierRLopes/GamestonkTerminal/pull/329)
**NEXT**
@@ -142,13 +144,17 @@
* [ ] Add Brokers (td, webull, etc) (@jmaslek)
* [ ] Refactoring
* [ ] Summaries / tear sheets (@jmaslek)
+
___
-##Portfolio Optimization
+
+## Portfolio Optimization
* [x] Basic Optimization through PyPortFolioOpt(@jmaslek) - [PR #329](https://github.com/DidierRLopes/GamestonkTerminal/pull/329)
+* [x] Add command to maximise the quadratic utility(@didier) - [PR #349](https://github.com/DidierRLopes/GamestonkTerminal/pull/349)
**NEXT**
-* [ ] Decide what features should be present
* [ ] Allow for more custom optimization constrains
+
+
---
## FRED
@@ -189,6 +195,7 @@ ___
* [x] Add README with explanation of how presets are stored and can be added by experienced users. (@didier) - [PR #314](https://github.com/DidierRLopes/GamestonkTerminal/pull/314)
* [x] Add screener signals (e.g. top gainers, new highs, most volatile, oversold, major news, ...) from Finviz (@didier) - [PR #314](https://github.com/DidierRLopes/GamestonkTerminal/pull/314)
* [x] Plot screener historical using Yahoo Finance data (@didier) - [PR #319](https://github.com/DidierRLopes/GamestonkTerminal/pull/319)
+* [x] Add command to take tickers to portfolio optimization (@didier) - [PR #349](https://github.com/DidierRLopes/GamestonkTerminal/pull/349)
**NEXT**
diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md
index 2b6d79c7b2e..e616a2ae873 100644
--- a/gamestonk_terminal/README.md
+++ b/gamestonk_terminal/README.md
@@ -9,6 +9,7 @@
* [Due Diligence](#Due-Diligence-)
* [Prediction Techniques](#Prediction-Techniques-)
* [Portfolio Analysis](#Portfolio-Analysis-)
+* [Portfolio Optimization](#Portfolio-Optimization-)
* [Cryptocurrencies](#Cryptocurrencies-)
* [Comparison Analysis](#Comparison-Analysis-)
* [Exploratory Data Analysis](#Exploratory-Data-Analysis-)
@@ -242,16 +243,19 @@ Command|Description|Brokers
## Portfolio Optimization [»](portfolio_optimization/README.md)
-command|desciription
+
+Command|Description
------|------
-`equal_weight`|Returns equal weights for all selected stocks
-`mkt_cap`| Returns weights that are weighted by market cap
-`div_yield`|Returns weights that are weighted by dividend yield
-`max_sharpe`| Returns portfolio of Efficient Frontier with maximum sharpe ratio
-`min_vol`| Returns portfolio on Efficient Frontier with minimum volatility
-`eff_risk`| Returns portfolio on Efficient Frontier that maximizes returns at a given risk
-`eff_ret`| Returns portfolio on Efficient Frontier that minimizes risk at a given return level
-`show_eff`|Plots random portfolios and shows the Efficient Frontier
+`add`| add ticker to optimize
+`select`| overwrite current tickers with new tickers
+`equal`| equally weighted
+`property`| weight according to selected info property (e.g. marketCap)
+`maxsharpe`| optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio)
+`minvol`| optimizes for minimum volatility
+`maxquadutil`| maximises the quadratic utility, given some risk aversion
+`effret`| maximises return for a given target risk
+`effrisk`| minimises risk for a given target return
+`ef`| show the efficient frontier
@@ -331,7 +335,9 @@ Command|Description
Command|Description|Source
------ | --------|----
`exp` | see/set expiry date
-`volume` | plot options trading volume / open interest |[Yahoo Finance](https://finance.yahoo.com/)
+`volume` | volume + open interest options trading plot |[Yahoo Finance](https://finance.yahoo.com/)
+`vcalls` | calls volume + open interest plot |[Yahoo Finance](https://finance.yahoo.com/)
+`vputs` | puts volume + open interest plot |[Yahoo Finance](https://finance.yahoo.com/)
@@ -351,4 +357,4 @@ technical |technical (e.g. Beta, SMA50, 52W Low, RSI, Change)
signals |view filter signals (e.g. -s top_gainers)
-
\ No newline at end of file
+
diff --git a/gamestonk_terminal/comparison_analysis/README.md b/gamestonk_terminal/comparison_analysis/README.md
index f00b902183d..c60dfd305d0 100644
--- a/gamestonk_terminal/comparison_analysis/README.md
+++ b/gamestonk_terminal/comparison_analysis/README.md
@@ -32,6 +32,8 @@ This menu aims to compare a pre-loaded stock with similar companies, and the usa
* brief performance (e.g. Perf Week, Perf YTD, Volatility M) [Finviz]
* [technical](#technical)
* brief technical (e.g. Beta, SMA50, 52W Low, RSI, Change) [Finviz]
+* [> po](portfolio_optimization/README.md)
+ * portfolio optimization for selected tickers
## get
diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py
index f999ed092af..499b6c9cd39 100644
--- a/gamestonk_terminal/comparison_analysis/ca_controller.py
+++ b/gamestonk_terminal/comparison_analysis/ca_controller.py
@@ -84,8 +84,6 @@ class ComparisonAnalysisController:
if self.similar:
print(f"[{self.user}] Similar Companies: {', '.join(self.similar)}")
- # else:
- # print(f"No similar companies [{self.user}]")
print("\nComparison Analysis Mode:")
print(" help show this comparison analysis menu again")
@@ -110,8 +108,9 @@ class ComparisonAnalysisController:
print(" performance brief performance comparison")
print(" technical brief technical comparison")
print("")
- print(" > po portfolio optimization for selected tickers")
- print("")
+ if self.similar:
+ print(" > po portfolio optimization for selected tickers")
+ print("")
return
@staticmethod
@@ -320,7 +319,7 @@ class ComparisonAnalysisController:
finviz_compare_view.screener(other_args, "technical", self.ticker, self.similar)
def call_po(self, _):
- """Open Portfolio Optimization menu with ticker and similar"""
+ """Call the portfolio optimization menu with selected tickers"""
return po_controller.menu([self.ticker] + self.similar)
diff --git a/gamestonk_terminal/helper_funcs.py b/gamestonk_terminal/helper_funcs.py
index 11a3ffbe196..a2e00fd3d35 100644
--- a/gamestonk_terminal/helper_funcs.py
+++ b/gamestonk_terminal/helper_funcs.py
@@ -347,7 +347,7 @@ def patch_pandas_text_adjustment():
def parse_known_args_and_warn(parser, l_args):
parser.add_argument(
- "-h", "--help", action="store_true", dest="help", help="show this help message"
+ "-h", "--help", action="store_true", help="show this help message"
)
(ns_parser, l_unknown_args) = parser.parse_known_args(l_args)
diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md
index bf96cf77629..3e9e44d4603 100644
--- a/gamestonk_terminal/portfolio_optimization/README.md
+++ b/gamestonk_terminal/portfolio_optimization/README.md
@@ -1,169 +1,224 @@
# Portfolio Optimization
-This menu aims to discover optimal portfolios for selected stocks.
+This menu aims to optimize a portfolio of pre-loaded stocks, and the usage of the following commands along with an example will be exploited below.
-Currently we support two methods:
+* [add](#add)
+ * add ticker to optimize
+* [select](#select)
+ * overwrite current tickers with new tickers
+* [equal](#equal)
+ * equally weighted
+* [mktcap](#mktcap)
+ * weighted according to market cap (property marketCap)
+* [dividend](#dividend)
+ * weighted according to dividend yield (property dividendYield)
+* [property](#property)
+ * weight according to selected info property
-* [Property Weighting](#weighting)
- * Equal Weights
- * Market Cap Weighting
- * Divident Yield Weighting
-* [Mean-Variance-Optimization](#eff_front)
- * Max sharpe ratio
- * Minimum volatility
- * Maximum returns at given risk level
- * Minimum risk level at a given return
+[Mean Variance Optimization](#Mean_Variance_Optimization)
-## Procedure
-There are three ways to load stocks to be analyzed.
-* add
-* select
-* from ca menu
-###add
-Adds selected tickers to the menu to be considered
+* [maxsharpe](#maxsharpe)
+ * optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio)
+* [minvol](#minvol)
+ * optimizes for minimum volatility
+* [maxquadutil](#maxquadutil)
+ * maximises the quadratic utility, given some risk aversion
+* [effret](#effret)
+ * maximises return for a given target risk
+* [effrisk](#effrisk)
+ * minimises risk for a given target return
+* [ef](#ef)
+ * show the efficient frontier
-````
-usage: add ticker1,ticker2,ticker3,...
-````
-###select
-Clears current list and loads selected ticker
-````
-usage: select ticker1,ticker2,ticker3,...
-````
-### ca menu
-From the ca menu, the loaded ticker and the selected similar tickers can be loaded by entering the `> po` menu.
+### add
-From this `po` menu, you can return to `ca` using `ca`, which will keep your loaded stock, but reset the selected stocks from that menu.
+```text
+add [-t ADD_TICKERS]
+```
-Once your stocks are listed, you can select one of the options.
-## Property weighted
-* equal weights
-* market cap weighted
-* dividend yield weighted
-### equal weights
-Returns an equally weighted portfolio where the weights are 1/number of stocks.
-````
-equal_weight [-v --value VALUE] [--pie]
-````
-* -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.
+Add/Select tickers for portfolio to be optimized.
-### market cap weighted
-Returns portfolio values that are weighted by their relative Market Cap.
-````
-mkt_cap [-v --value VALUE] [--pie]
-````
-* -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.
+* -t : Tickers to be used in the portfolio to optimize
+*
-###dividend yield weighted
-Returns portfolio values that are weighted by relative Dividend Yield.
-````
-div_yield [-v --value VALUE] [--pie]
-````
-* -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.
+### select
-## Mean Variance Optimization
+```text
+select [-t ADD_TICKERS]
+```
+
+Add/Select tickers for portfolio to be optimized.
+
+* -t : Tickers to be used in the portfolio to optimize
+
+
+### equal
+
+```text
+equal [-v VALUE] [--pie]
+```
+
+Returns an equally weighted portfolio
+
+* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
+* --pie : Display a pie chart for weights
+
+
+
+
+### mktcap
+
+```text
+property -p marketCap [-v VALUE] [--pie]
+```
+
+Returns a portfolio that is weighted based on market cap info
+
+* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
+* --pie : Display a pie chart for weights
+
+
+
+
+### dividend
+
+```text
+property -p dividend [-v VALUE] [--pie]
+```
+
+Returns a portfolio that is weighted based on dividend yield info
+
+* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
+* --pie : Display a pie chart for weights
+
+
+
+
+### property
+
+```text
+property [-p PROPERTY] [-v VALUE] [--pie]
+```
+
+Returns a portfolio that is weighted based on a selected property info
+
+* -p : Property info to weigh. Use one of: previousClose, regularMarketOpen, twoHundredDayAverage, trailingAnnualDividendYield, payoutRatio, volume24Hr, regularMarketDayHigh, navPrice, averageDailyVolume10Day, totalAssets, regularMarketPreviousClose, fiftyDayAverage, trailingAnnualDividendRate, open, toCurrency, averageVolume10days, expireDate, yield, algorithm, dividendRate, exDividendDate, beta, circulatingSupply, regularMarketDayLow, priceHint, currency, trailingPE, regularMarketVolume, lastMarket, maxSupply, openInterest, marketCap, volumeAllCurrencies, strikePrice, averageVolume, priceToSalesTrailing12Months, dayLow, ask, ytdReturn, askSize, volume, fiftyTwoWeekHigh, forwardPE, fromCurrency, fiveYearAvgDividendYield, fiftyTwoWeekLow, bid, dividendYield, bidSize, dayHigh, annualHoldingsTurnover, enterpriseToRevenue, beta3Year, profitMargins, enterpriseToEbitda, 52WeekChange, morningStarRiskRating, forwardEps, revenueQuarterlyGrowth, sharesOutstanding, fundInceptionDate, annualReportExpenseRatio, bookValue, sharesShort, sharesPercentSharesOut, fundFamily, lastFiscalYearEnd, heldPercentInstitutions, netIncomeToCommon, trailingEps, lastDividendValue, SandP52WeekChange, priceToBook, heldPercentInsiders, shortRatio, sharesShortPreviousMonthDate, floatShares, enterpriseValue, threeYearAverageReturn, lastSplitFactor, legalType, lastDividendDate, morningStarOverallRating, earningsQuarterlyGrowth, pegRatio, lastCapGain, shortPercentOfFloat, sharesShortPriorMonth, impliedSharesOutstanding, fiveYearAverageReturn, and regularMarketPrice.
+* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
+* --pie : Display a pie chart for weights
+
+
+
+
+## Mean Variance Optimization
These approaches are based off of the efficient frontier approach, which is meant to solve the following optimization problem.
-
+
+
+
+
+
+### minvol
+
+````
+usage: minvol [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v 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.
-* -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.
-### min_vol
This portfolio minimizes the total volatility, which also means it has the smallest returns among the efficient frontier.
-The usage is:
-````
-min_vol [-p PERIOD] [-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.
-* -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.
-### eff_risk
-This portfolio maximizes the returns at a given risk tolerance
-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. 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.
+* -p : Amount of time to retrieve data from yfinance. Default: 3mo.
+* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
+* --pie : Display a pie chart for weights
-### eff_ret
-This portfolio minimizes the risk at a given return level
-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. 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.
+
+
+
+
+### maxquadutil
+
+````
+usage: maxquadutil [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] [-r RISK_AVERSION] [-n]
+````
+
+Maximises the quadratic utility, given some risk aversion.
+
+* -p : Amount of time to retrieve data from yfinance. Default: 3mo.
+* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
+* --pie : Display a pie chart for weights
+* -r : Risk aversion parameter
+* -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False.
+
+
+
+
+### effret
+
+````
+usage: effret [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] [-t TARGET_RETURN] [-n]
+````
+
+Calculate the 'Markowitz portfolio', minimising volatility for a given target return.
+
+* -p : Amount of time to retrieve data from yfinance. Default: 3mo.
+* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
+* --pie : Display a pie chart for weights
+* -t : The desired return of the resulting portfolio
+* -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False.
+
+
+
+
+### effrisk
+
+````
+usage: effrisk [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] [-t TARGET_VOLATILITY] [-n]
+````
+
+Maximise return for a target risk. The resulting portfolio will have a volatility less than the target (but not guaranteed to be equal).
+
+* -p : Amount of time to retrieve data from yfinance. Default: 3mo.
+* -v : If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
+* --pie : Display a pie chart for weights
+* -t : The desired maximum volatility of the resultingportfolio
+* -n : Whether the portfolio should be market neutral (weights sum to zero). Defaults: False.
+
+
+
+
+
+### ef
+
+````
+usage: ef [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-n N_PORT]
+````
-### show_eff
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. Default 300.
+* -p : 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.
-##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`. This 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).
-
-
-And the pie chart:
-
-
-
-Note that since `AAPL` had zero allocation, it was omitted from the chart.
\ No newline at end of file
+
diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
index 3ca81f396a2..f88ad80463c 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py
@@ -13,21 +13,113 @@ from pypfopt import risk_models
from pypfopt import expected_returns
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
+from gamestonk_terminal.helper_funcs import plot_autoscale
-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",
-}
+l_valid_property_infos = [
+ "previousClose",
+ "regularMarketOpen",
+ "twoHundredDayAverage",
+ "trailingAnnualDividendYield",
+ "payoutRatio",
+ "volume24Hr",
+ "regularMarketDayHigh",
+ "navPrice",
+ "averageDailyVolume10Day",
+ "totalAssets",
+ "regularMarketPreviousClose",
+ "fiftyDayAverage",
+ "trailingAnnualDividendRate",
+ "open",
+ "toCurrency",
+ "averageVolume10days",
+ "expireDate",
+ "yield",
+ "algorithm",
+ "dividendRate",
+ "exDividendDate",
+ "beta",
+ "circulatingSupply",
+ "regularMarketDayLow",
+ "priceHint",
+ "currency",
+ "trailingPE",
+ "regularMarketVolume",
+ "lastMarket",
+ "maxSupply",
+ "openInterest",
+ "marketCap",
+ "volumeAllCurrencies",
+ "strikePrice",
+ "averageVolume",
+ "priceToSalesTrailing12Months",
+ "dayLow",
+ "ask",
+ "ytdReturn",
+ "askSize",
+ "volume",
+ "fiftyTwoWeekHigh",
+ "forwardPE",
+ "fromCurrency",
+ "fiveYearAvgDividendYield",
+ "fiftyTwoWeekLow",
+ "bid",
+ "dividendYield",
+ "bidSize",
+ "dayHigh",
+ "annualHoldingsTurnover",
+ "enterpriseToRevenue",
+ "beta3Year",
+ "profitMargins",
+ "enterpriseToEbitda",
+ "52WeekChange",
+ "morningStarRiskRating",
+ "forwardEps",
+ "revenueQuarterlyGrowth",
+ "sharesOutstanding",
+ "fundInceptionDate",
+ "annualReportExpenseRatio",
+ "bookValue",
+ "sharesShort",
+ "sharesPercentSharesOut",
+ "fundFamily",
+ "lastFiscalYearEnd",
+ "heldPercentInstitutions",
+ "netIncomeToCommon",
+ "trailingEps",
+ "lastDividendValue",
+ "SandP52WeekChange",
+ "priceToBook",
+ "heldPercentInsiders",
+ "shortRatio",
+ "sharesShortPreviousMonthDate",
+ "floatShares",
+ "enterpriseValue",
+ "threeYearAverageReturn",
+ "lastSplitFactor",
+ "legalType",
+ "lastDividendDate",
+ "morningStarOverallRating",
+ "earningsQuarterlyGrowth",
+ "pegRatio",
+ "lastCapGain",
+ "shortPercentOfFloat",
+ "sharesShortPriorMonth",
+ "impliedSharesOutstanding",
+ "fiveYearAverageReturn",
+ "regularMarketPrice",
+]
+
+
+def check_valid_property_type(property: str) -> str:
+ """ Check that the property selected is valid """
+ if property in l_valid_property_infos:
+ return property
+ else:
+ raise argparse.ArgumentTypeError(f"{property} is not a valid info")
def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame:
- """
+ """Get adjusted closing price for each stock in the list
Parameters
----------
@@ -51,8 +143,8 @@ def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFra
def prepare_efficient_frontier(stock_prices: pd.DataFrame):
- """
- Take in a dataframe of prices and return an efficient frontier object
+ """Take in a dataframe of prices and return an efficient frontier object
+
Parameters
----------
stock_prices : DataFrame
@@ -82,11 +174,11 @@ def display_weights(weights: dict):
weight_df = pd.DataFrame.from_dict(data=weights, orient="index", columns=["value"])
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]
+ lambda s: " " + s[:4] if s.find(".") == 1 else "" + s[:5]
) + " %"
- print(pd.DataFrame(weight_df["weight"]))
+ print(pd.DataFrame(weight_df["weight"]).to_string(header=False))
else:
- print(weight_df)
+ print(weight_df.to_string(header=False))
def my_autopct(x):
@@ -97,17 +189,16 @@ def my_autopct(x):
return ""
-def pie_chart_weights(weights: dict, optimizer: str, value: float):
- """
- Show a pie chart of holdings
+def pie_chart_weights(weights: dict, title_opt: str):
+ """Show a pie chart of holdings
+
Parameters
----------
weights: dict
- weights to display. Keys are stocks. Values are either weights or values if -v specified
- optimzer: str
- Optimization technique used for title
+ Weights to display, where keys are tickers, and values are either weights or values if -v specified
+ title: str
+ Title to be used on the plot title
"""
- plt.close("all")
if not weights:
return
@@ -122,31 +213,28 @@ def pie_chart_weights(weights: dict, optimizer: str, value: float):
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)
- ]
plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
+
if math.isclose(sum(sizes), 1, rel_tol=0.1):
wedges, _, autotexts = plt.pie(
sizes,
labels=stocks,
autopct=my_autopct,
textprops=dict(color="k"),
- explode=[s / (5 * total_size) for s in sizes],
+ wedgeprops={"linewidth": 3.0, "edgecolor": "white"},
normalize=True,
- shadow=True,
)
+ plt.setp(autotexts, color="white", fontweight="bold")
else:
wedges, _, autotexts = plt.pie(
sizes,
labels=stocks,
autopct="",
textprops=dict(color="k"),
- explode=[s / (5 * total_size) for s in sizes],
+ wedgeprops={"linewidth": 3.0, "edgecolor": "white"},
normalize=True,
- shadow=True,
)
+ plt.setp(autotexts, color="white", fontweight="bold")
for i, a in enumerate(autotexts):
if sizes[i] / total_size > 0.05:
a.set_text(f"{sizes[i]:.2f}")
@@ -155,72 +243,37 @@ def pie_chart_weights(weights: dict, optimizer: str, value: float):
plt.axis("equal")
- plt.legend(
+ leg1 = plt.legend(
wedges,
- leg_labels,
- title="Stocks",
- loc="center left",
- bbox_to_anchor=(0.85, 0, 0.5, 1),
+ [str(s) for s in stocks],
+ title=" Ticker",
+ loc="upper left",
+ bbox_to_anchor=(0.80, 0, 0.5, 1),
+ frameon=False,
)
+ leg2 = plt.legend(
+ wedges,
+ [
+ f"{' ' if ((100*s/total_size) < 10) else ''}{100*s/total_size:.2f}%"
+ for s in sizes
+ ],
+ title=" ",
+ loc="upper left",
+ handlelength=0,
+ bbox_to_anchor=(0.91, 0, 0.5, 1),
+ frameon=False,
+ )
+ plt.gca().add_artist(leg1)
+ plt.gca().add_artist(leg2)
plt.setp(autotexts, size=8, weight="bold")
- if optimizer in ["eff_ret", "eff_risk"]:
- plt.title(title_maps[optimizer].format(100 * value))
- else:
- plt.title(title_maps[optimizer])
+ plt.gca().set_title(title_opt, pad=20)
if gtff.USE_ION:
plt.ion()
+ plt.tight_layout()
+
plt.show()
print("")
-
-
-def parse_from_port_type(
- parser_in: argparse.ArgumentParser, port_type: str, other_args: List[str]
-):
- """
-
- Parameters
- ----------
- parser_in: ArgumentParser
- parser to get data from
- port_type: str
- Type of optimization that will be done. One of ["max_sharpe","min_vol", "eff_risk", "eff_ret"]
- other_args: List[str]
- Arguments passed to function
- Returns
- -------
-
- ns_parser:
- Parsed arguments
- """
-
- if port_type in ["max_sharpe", "min_volatility"]:
- ns_parser = parse_known_args_and_warn(parser_in, other_args)
- if not ns_parser:
- return None
- return ns_parser
-
- elif port_type == "eff_risk":
- parser_in.add_argument(
- "-r", "--risk", type=float, dest="risk_level", default=0.1
- )
- ns_parser = parse_known_args_and_warn(parser_in, other_args)
- if not ns_parser:
- return None
- return ns_parser
-
- elif port_type == "eff_ret":
- parser_in.add_argument(
- "-r", "--return", type=float, dest="target_return", default=0.1
- )
-
- ns_parser = parse_known_args_and_warn(parser_in, other_args)
- if not ns_parser:
- return None
- return ns_parser
- else:
- print("Incorrect portfolio optimizer type\n")
- return None
diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
index e9d74a76138..6c61666d5bf 100644
--- a/gamestonk_terminal/portfolio_optimization/optimizer_view.py
+++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py
@@ -10,13 +10,17 @@ from pypfopt import plotting
from pypfopt import risk_models
from pypfopt import expected_returns
from pypfopt import EfficientFrontier
-from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale
+from gamestonk_terminal.helper_funcs import (
+ parse_known_args_and_warn,
+ plot_autoscale,
+ check_non_negative,
+)
from gamestonk_terminal.portfolio_optimization.optimizer_helper import (
process_stocks,
prepare_efficient_frontier,
pie_chart_weights,
display_weights,
- parse_from_port_type,
+ check_valid_property_type,
)
from gamestonk_terminal.config_plot import PLOT_DPI
from gamestonk_terminal import feature_flags as gtff
@@ -35,6 +39,20 @@ period_choices = [
"max",
]
+d_period = {
+ "1d": "[1 Day]",
+ "5d": "[5 Days]",
+ "1mo": "[1 Month]",
+ "3mo": "[3 Months]",
+ "6mo": "[6 Months]",
+ "1y": "[1 Year]",
+ "2y": "[2 Years]",
+ "5y": "[5 Years]",
+ "10y": "[10 Years]",
+ "ytd": "[Year-to-Date]",
+ "max": "[All-time]",
+}
+
def equal_weight(stocks: List[str], other_args: List[str]):
"""
@@ -53,8 +71,8 @@ def equal_weight(stocks: List[str], other_args: List[str]):
"""
parser = argparse.ArgumentParser(
add_help=False,
- prog="equal_weight",
- description="Return equally weighted portfolio holdings",
+ prog="equal",
+ description="Returns an equally weighted portfolio",
)
parser.add_argument(
@@ -76,14 +94,17 @@ def equal_weight(stocks: List[str], other_args: List[str]):
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
+
+ if len(stocks) < 2:
+ print("Please have at least 2 loaded tickers to calculate weights.\n")
+ return
+
values = {}
- n_stocks = len(stocks)
for stock in stocks:
- values[stock] = ns_parser.value * round(1 / n_stocks, 5)
+ values[stock] = ns_parser.value * round(1 / len(stocks), 5)
if ns_parser.pie:
- pie_chart_weights(values, "equal", 0)
- if n_stocks >= 1:
- print("Equal Weight Portfolio: ")
+ pie_chart_weights(values, "Equally Weighted Portfolio")
+ else:
display_weights(values)
print("")
@@ -92,16 +113,15 @@ def equal_weight(stocks: List[str], other_args: List[str]):
print("")
-def property_weighting(stocks: List[str], property_type: str, other_args: List[str]):
- """
- Property weighted portfolio where each weight is the relative fraction.
+def property_weighting(stocks: List[str], other_args: List[str]):
+ """Weighted portfolio where each weight is the relative fraction of a specified info property.
+
Parameters
----------
stocks: List[str]
List of tickers to be included in optimization
- property_type: str
- Property to weight by. Can be anything in yfinance.Ticker().info. Examples:
- "marketCap", "dividendYield", etc
+ other_args : List[str]
+ Command line arguments to be processed with argparse
Returns
-------
@@ -110,10 +130,32 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s
"""
parser = argparse.ArgumentParser(
add_help=False,
- prog="market_cap_weighted",
- description="Return portfolio weights/values that are weighted by marketcap",
+ prog="property",
+ description="Returns a portfolio that is weighted based on a selected property info",
+ )
+ parser.add_argument(
+ "-p",
+ "--property",
+ required=True if "-h" not in other_args else False,
+ type=check_valid_property_type,
+ dest="property",
+ help="""Property info to weigh. Use one of:
+ previousClose, regularMarketOpen, twoHundredDayAverage, trailingAnnualDividendYield,
+ payoutRatio, volume24Hr, regularMarketDayHigh, navPrice, averageDailyVolume10Day, totalAssets,
+ regularMarketPreviousClose, fiftyDayAverage, trailingAnnualDividendRate, open, toCurrency, averageVolume10days,
+ expireDate, yield, algorithm, dividendRate, exDividendDate, beta, circulatingSupply, regularMarketDayLow,
+ priceHint, currency, trailingPE, regularMarketVolume, lastMarket, maxSupply, openInterest, marketCap,
+ volumeAllCurrencies, strikePrice, averageVolume, priceToSalesTrailing12Months, dayLow, ask, ytdReturn, askSize,
+ volume, fiftyTwoWeekHigh, forwardPE, fromCurrency, fiveYearAvgDividendYield, fiftyTwoWeekLow, bid, dividendYield,
+ bidSize, dayHigh, annualHoldingsTurnover, enterpriseToRevenue, beta3Year, profitMargins, enterpriseToEbitda,
+ 52WeekChange, morningStarRiskRating, forwardEps, revenueQuarterlyGrowth, sharesOutstanding, fundInceptionDate,
+ annualReportExpenseRatio, bookValue, sharesShort, sharesPercentSharesOut, fundFamily, lastFiscalYearEnd,
+ heldPercentInstitutions, netIncomeToCommon, trailingEps, lastDividendValue, SandP52WeekChange, priceToBook,
+ heldPercentInsiders, shortRatio, sharesShortPreviousMonthDate, floatShares, enterpriseValue,
+ threeYearAverageReturn, lastSplitFactor, legalType, lastDividendDate, morningStarOverallRating,
+ earningsQuarterlyGrowth, pegRatio, lastCapGain, shortPercentOfFloat, sharesShortPriorMonth,
+ impliedSharesOutstanding, fiveYearAverageReturn, and regularMarketPrice.""",
)
-
parser.add_argument(
"-v",
"--value",
@@ -129,33 +171,42 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s
default=False,
help="Display a pie chart for weights",
)
- weights = {}
- prop = {}
- prop_sum = 0
try:
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-p")
+
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
+ if len(stocks) < 2:
+ print("Please have at least 2 loaded tickers to calculate weights.\n")
+ return
+
+ weights = {}
+ prop = {}
+ prop_sum = 0
for stock in stocks:
- stock_prop = yf.Ticker(stock).info[property_type]
+ stock_prop = yf.Ticker(stock).info[ns_parser.property]
if stock_prop is None:
stock_prop = 0
prop[stock] = stock_prop
prop_sum += stock_prop
+
+ if prop_sum == 0:
+ print(f"No {ns_parser.property} was found on list of tickers provided")
+ print("")
+ return
+
for k, v in prop.items():
weights[k] = round(v / prop_sum, 5) * ns_parser.value
if ns_parser.pie:
- pie_chart_weights(weights, property_type, 0)
-
- if property_type == "marketCap":
- print("Market Cap Weighted Portfolio: ")
-
- elif property_type == "dividendYield":
- print("Dividend Yield Weighted Portfolio: ")
-
- if len(stocks) >= 1:
+ pie_chart_weights(
+ weights, "Weighted Portfolio based on " + ns_parser.property
+ )
+ else:
display_weights(weights)
print("")
@@ -165,23 +216,23 @@ def property_weighting(stocks: List[str], property_type: str, other_args: List[s
return
-def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
+def max_sharpe(stocks: List[str], other_args: List[str]):
"""
- Return a portfolio based on condition in port_type Defaults to 3m of historical data
+ Return a portfolio that maximises the Sharpe Ratio
+
Parameters
----------
stocks: List[str]
List of the stocks to be included in the weights
- port_type: str
- Method to be used on ef object (example: max_sharpe, min_volatility)
+
Returns
-------
weights: dict
Dictionary of weights where keys are the tickers.
"""
-
- parser = argparse.ArgumentParser(add_help=False, prog=port_type)
-
+ parser = argparse.ArgumentParser(
+ add_help=False, prog="maxsharpe", description="Maximise the Sharpe Ratio"
+ )
parser.add_argument(
"-p",
"--period",
@@ -190,7 +241,100 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
help="period to get yfinance data from",
choices=period_choices,
)
+ parser.add_argument(
+ "-v",
+ "--value",
+ dest="value",
+ help="Amount to allocate to portfolio",
+ type=float,
+ default=1.0,
+ )
+ parser.add_argument(
+ "--pie",
+ action="store_true",
+ dest="pie",
+ default=False,
+ help="Display a pie chart for weights",
+ )
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-r")
+ parser.add_argument(
+ "-r",
+ "--risk-free-rate",
+ type=float,
+ dest="risk_free_rate",
+ default=0.02,
+ help="""Risk-free rate of borrowing/lending. The period of the risk-free rate
+ should correspond to the frequency of expected returns.""",
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return None
+
+ if len(stocks) < 2:
+ print("Please have at least 2 loaded tickers to calculate weights.\n")
+ return
+
+ period = ns_parser.period
+ stock_prices = process_stocks(stocks, period)
+ ef = prepare_efficient_frontier(stock_prices)
+
+ sp = d_period[ns_parser.period]
+
+ ef_opt = dict(ef.max_sharpe(ns_parser.risk_free_rate))
+ s_title = f"{sp} Weights that maximize Sharpe ratio with risk free level of {ns_parser.risk_free_rate}"
+
+ weights = {
+ key: ns_parser.value * round(value, 5) for key, value in ef_opt.items()
+ }
+
+ if ns_parser.pie:
+ pie_chart_weights(weights, s_title)
+ else:
+ print(s_title)
+ display_weights(weights)
+ print("")
+
+ ef.portfolio_performance(verbose=True)
+ print("")
+
+ except Exception as e:
+ print(e)
+ print("")
+ return
+
+
+def min_volatility(stocks: List[str], other_args: List[str]):
+ """
+ Return a portfolio that optimizes for minimum volatility
+
+ Parameters
+ ----------
+ stocks: List[str]
+ List of the stocks to be included in the weights
+
+ Returns
+ -------
+ weights: dict
+ Dictionary of weights where keys are the tickers.
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="min_volatility",
+ description="Optimizes for minimum volatility",
+ )
+ parser.add_argument(
+ "-p",
+ "--period",
+ default="3mo",
+ dest="period",
+ help="period to get yfinance data from",
+ choices=period_choices,
+ )
parser.add_argument(
"-v",
"--value",
@@ -208,67 +352,363 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
)
try:
- ns_parser = parse_from_port_type(parser, port_type, other_args)
+ ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
- return
+ return None
+
if len(stocks) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
+
period = ns_parser.period
stock_prices = process_stocks(stocks, period)
ef = prepare_efficient_frontier(stock_prices)
- if port_type == "max_sharpe":
+ sp = d_period[ns_parser.period]
- ef_sharpe = dict(ef.max_sharpe())
- weights = {
- key: ns_parser.value * round(value, 5)
- for key, value in ef_sharpe.items()
- }
- val = 0
- print("Weights that maximize Sharpe Ratio:")
+ ef_opt = dict(ef.min_volatility())
+ s_title = f"{sp} Weights that minimize volatility"
- elif port_type == "min_volatility":
-
- ef_min_vol = dict(ef.min_volatility())
- weights = {
- key: ns_parser.value * round(value, 5)
- for key, value in ef_min_vol.items()
- }
- val = 0
- print("Weights that minimize volatility")
-
- elif port_type == "eff_risk":
-
- ef_eff_risk = dict(ef.efficient_risk(ns_parser.risk_level))
- weights = {
- key: ns_parser.value * round(value, 5)
- for key, value in ef_eff_risk.items()
- }
- val = ns_parser.risk_level
- print(f"Weights for maximizing returns at risk = {100*val:.1f} %")
-
- elif port_type == "eff_ret":
-
- ef_eff_risk = dict(ef.efficient_return(ns_parser.target_return))
- weights = {
- key: ns_parser.value * round(value, 5)
- for key, value in ef_eff_risk.items()
- }
- val = ns_parser.target_return
- print(f"Weights for minimizing risk at target return = {100*val:.1f} %")
-
- else:
- raise ValueError("EF Method not found")
+ weights = {
+ key: ns_parser.value * round(value, 5) for key, value in ef_opt.items()
+ }
if ns_parser.pie:
- pie_chart_weights(weights, port_type, val)
+ pie_chart_weights(weights, s_title)
+ else:
+ print(s_title)
+ display_weights(weights)
+ print("")
+ ef.portfolio_performance(verbose=True)
+ print("")
+
+ except Exception as e:
+ print(e)
+ print("")
+ return
+
+
+def max_quadratic_utility(stocks: List[str], other_args: List[str]):
+ """
+ Return a portfolio that maximises the quadratic utility, given some risk aversion
+
+ Parameters
+ ----------
+ stocks: List[str]
+ List of the stocks to be included in the weights
+
+ Returns
+ -------
+ weights: dict
+ Dictionary of weights where keys are the tickers.
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="max_quadratic_utility",
+ description="Maximises the quadratic utility, given some risk aversion",
+ )
+ parser.add_argument(
+ "-p",
+ "--period",
+ default="3mo",
+ dest="period",
+ help="period to get yfinance data from",
+ choices=period_choices,
+ )
+ parser.add_argument(
+ "-v",
+ "--value",
+ dest="value",
+ help="Amount to allocate to portfolio",
+ type=float,
+ default=1.0,
+ )
+ parser.add_argument(
+ "-n",
+ "--market-neutral",
+ action="store_true",
+ default=False,
+ dest="market_neutral",
+ help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False.
+ Requires negative lower weight bound.""",
+ )
+ if "-n" not in other_args:
+ parser.add_argument(
+ "--pie",
+ action="store_true",
+ dest="pie",
+ default=False,
+ help="Display a pie chart for weights. Only if neutral flag is left False.",
+ )
+
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-r")
+
+ parser.add_argument(
+ "-r",
+ "--risk-aversion",
+ type=float,
+ dest="risk_aversion",
+ default=1,
+ help="risk aversion parameter",
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return None
+
+ if len(stocks) < 2:
+ print("Please have at least 2 loaded tickers to calculate weights.\n")
+ return
+
+ period = ns_parser.period
+ stock_prices = process_stocks(stocks, period)
+ ef = prepare_efficient_frontier(stock_prices)
+
+ sp = d_period[ns_parser.period]
+
+ ef_opt = dict(
+ ef.max_quadratic_utility(ns_parser.risk_aversion, ns_parser.market_neutral)
+ )
+ s_title = f"{sp} Weights that maximise the quadratic utility with risk aversion of {ns_parser.risk_aversion}"
+
+ weights = {
+ key: ns_parser.value * round(value, 5) for key, value in ef_opt.items()
+ }
+
+ if not ns_parser.market_neutral:
+ if ns_parser.pie:
+ pie_chart_weights(weights, s_title)
+ ef.portfolio_performance(verbose=True)
+ print("")
+ return
+
+ print(s_title)
+ display_weights(weights)
print("")
ef.portfolio_performance(verbose=True)
print("")
+
+ except Exception as e:
+ print(e)
+ print("")
+ return
+
+
+def efficient_risk(stocks: List[str], other_args: List[str]):
+ """
+ Return a portfolio that maximises return for a target risk. The resulting portfolio will have
+ a volatility less than the target (but not guaranteed to be equal)
+
+ Parameters
+ ----------
+ stocks: List[str]
+ List of the stocks to be included in the weights
+
+ Returns
+ -------
+ weights: dict
+ Dictionary of weights where keys are the tickers.
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="effrisk",
+ description="""Maximise return for a target risk. The resulting portfolio will have
+ a volatility less than the target (but not guaranteed to be equal)""",
+ )
+ parser.add_argument(
+ "-p",
+ "--period",
+ default="3mo",
+ dest="period",
+ help="period to get yfinance data from",
+ choices=period_choices,
+ )
+ parser.add_argument(
+ "-v",
+ "--value",
+ dest="value",
+ help="Amount to allocate to portfolio",
+ type=float,
+ default=1.0,
+ )
+ parser.add_argument(
+ "-n",
+ "--market-neutral",
+ action="store_true",
+ default=False,
+ dest="market_neutral",
+ help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False.
+ Requires negative lower weight bound.""",
+ )
+ if "-n" not in other_args:
+ parser.add_argument(
+ "--pie",
+ action="store_true",
+ dest="pie",
+ default=False,
+ help="Display a pie chart for weights. Only if neutral flag is left False.",
+ )
+
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-t")
+ parser.add_argument(
+ "-t",
+ "--target-volatility",
+ type=float,
+ dest="target_volatility",
+ default=0.1,
+ help="The desired maximum volatility of the resulting portfolio",
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return None
+
+ if len(stocks) < 2:
+ print("Please have at least 2 loaded tickers to calculate weights.\n")
+ return
+
+ period = ns_parser.period
+ stock_prices = process_stocks(stocks, period)
+ ef = prepare_efficient_frontier(stock_prices)
+
+ sp = d_period[ns_parser.period]
+
+ ef_opt = dict(
+ ef.efficient_risk(ns_parser.target_volatility, ns_parser.market_neutral)
+ )
+ s_title = f"{sp} Weights that maximize return with a maximum volatility of {ns_parser.target_volatility}"
+
+ weights = {
+ key: ns_parser.value * round(value, 5) for key, value in ef_opt.items()
+ }
+
+ if not ns_parser.market_neutral:
+ if ns_parser.pie:
+ pie_chart_weights(weights, s_title)
+ ef.portfolio_performance(verbose=True)
+ print("")
+ return
+
+ print(s_title)
display_weights(weights)
print("")
+ ef.portfolio_performance(verbose=True)
+ print("")
+
+ except Exception as e:
+ print(e)
+ print("")
+ return
+
+
+def efficient_return(stocks: List[str], other_args: List[str]):
+ """
+ Return a portfolio that minimises volatility for a given target return ('Markowitz portfolio')
+
+ Parameters
+ ----------
+ stocks: List[str]
+ List of the stocks to be included in the weights
+
+ Returns
+ -------
+ weights: dict
+ Dictionary of weights where keys are the tickers.
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="effret",
+ description="Calculate the 'Markowitz portfolio', minimising volatility for a given target return",
+ )
+ parser.add_argument(
+ "-p",
+ "--period",
+ default="3mo",
+ dest="period",
+ help="period to get yfinance data from",
+ choices=period_choices,
+ )
+ parser.add_argument(
+ "-v",
+ "--value",
+ dest="value",
+ help="Amount to allocate to portfolio",
+ type=float,
+ default=1.0,
+ )
+ parser.add_argument(
+ "-n",
+ "--market-neutral",
+ action="store_true",
+ default=False,
+ dest="market_neutral",
+ help="""whether the portfolio should be market neutral (weights sum to zero), defaults to False.
+ Requires negative lower weight bound.""",
+ )
+ if "-n" not in other_args:
+ parser.add_argument(
+ "--pie",
+ action="store_true",
+ dest="pie",
+ default=False,
+ help="Display a pie chart for weights. Only if neutral flag is left False.",
+ )
+
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-t")
+ parser.add_argument(
+ "-t",
+ "--target-return",
+ type=float,
+ dest="target_return",
+ default=0.1,
+ help="the desired return of the resulting portfolio",
+ )
+
+ try:
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return None
+
+ if len(stocks) < 2:
+ print("Please have at least 2 loaded tickers to calculate weights.\n")
+ return
+
+ period = ns_parser.period
+ stock_prices = process_stocks(stocks, period)
+ ef = prepare_efficient_frontier(stock_prices)
+
+ sp = d_period[ns_parser.period]
+
+ ef_opt = dict(
+ ef.efficient_return(ns_parser.target_return, ns_parser.market_neutral)
+ )
+ s_title = f"{sp} Weights that minimise volatility for a given target return of {ns_parser.target_return}"
+
+ weights = {
+ key: ns_parser.value * round(value, 5) for key, value in ef_opt.items()
+ }
+
+ if not ns_parser.market_neutral:
+ if ns_parser.pie:
+ pie_chart_weights(weights, s_title)
+ ef.portfolio_performance(verbose=True)
+ print("")
+ return
+
+ print(s_title)
+ display_weights(weights)
+ print("")
+ ef.portfolio_performance(verbose=True)
+ print("")
except Exception as e:
print(e)
@@ -277,7 +717,12 @@ def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
def show_ef(stocks: List[str], other_args: List[str]):
- parser = argparse.ArgumentParser(add_help=False, prog="ef")
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="ef",
+ description="""This function plots random portfolios based
+ on their risk and returns and shows the efficient frontier.""",
+ )
parser.add_argument(
"-p",
@@ -288,10 +733,18 @@ def show_ef(stocks: List[str], other_args: List[str]):
choices=period_choices,
)
parser.add_argument(
- "-n", default=300, dest="n_port", help="number of portfolios to simulate"
+ "-n",
+ "--number-portfolios",
+ default=300,
+ type=check_non_negative,
+ dest="n_port",
+ help="number of portfolios to simulate",
)
try:
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
@@ -319,7 +772,7 @@ def show_ef(stocks: List[str], other_args: List[str]):
ret_sharpe, std_sharpe, _ = ef.portfolio_performance()
ax.scatter(std_sharpe, ret_sharpe, marker="*", s=100, c="r", label="Max Sharpe")
- ax.set_title("Efficient Frontier")
+ ax.set_title(f"Efficient Frontier simulating {ns_parser.n_port} portfolios")
ax.legend()
plt.tight_layout()
plt.grid(b=True, which="major", color="#666666", linestyle="-")
diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py
index df2f8a55fa6..c66ca63e846 100644
--- a/gamestonk_terminal/portfolio_optimization/po_controller.py
+++ b/gamestonk_terminal/portfolio_optimization/po_controller.py
@@ -9,7 +9,7 @@ from prompt_toolkit.completion import NestedCompleter
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 import optimizer_view
class PortfolioOptimization:
@@ -20,14 +20,17 @@ class PortfolioOptimization:
"quit",
"select",
"add",
- "equal_weight",
- "mkt_cap",
- "div_yield",
- "max_sharpe",
- "min_vol",
- "eff_risk",
- "eff_ret",
- "show_ef",
+ "equal",
+ "mktcap",
+ "dividend",
+ "property",
+ "maxsharpe",
+ "minvol",
+ "effret",
+ "effrisk",
+ "maxquadutil",
+ "ef",
+ "ca",
"yolo",
]
@@ -52,21 +55,33 @@ class PortfolioOptimization:
print(" quit quit to abandon program")
print(f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}")
print("")
- print(" add add ticker to optimize")
- print(" select overwrite current tickers with new tickers")
+ print(" add add ticker to optimize")
+ print(" select overwrite current tickers with new tickers")
print("")
print("Optimization:")
+ print(" equal equally weighted")
+ print(" mktcap weighted according to market cap (property marketCap)")
+ print(
+ " dividend weighted according to dividend yield (property dividendYield)"
+ )
+ print(" property weight according to selected info property")
print("")
- print(" equal_weight equally weighted portfolio")
- print(" mkt_cap marketcap weighted portfolio")
- print(" div_yield dividend weighted portfolio")
- print(" max_sharpe portfolio with maximum sharpe ratio")
- print(" min_vol portfolio with minimum volatility")
- print(" eff_risk portfolio that maximizes returns at given risk")
- print(" eff_ret portfolio that minimizes risk at given return")
- print(" show_ef show the efficient frontier")
+ print("Mean Variance Optimization:")
+ print(
+ " maxsharpe optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio)"
+ )
+ print(" minvol optimizes for minimum volatility")
+ print(
+ " maxquadutil maximises the quadratic utility, given some risk aversion"
+ )
+ print(" effret maximises return for a given target risk")
+ print(" effrisk minimises risk for a given target return")
print("")
- plt.close("all")
+ print(" ef show the efficient frontier")
+ print("")
+ if tickers:
+ print(" > ca comparison analysis for selected tickers")
+ print("")
def switch(self, an_input: str):
"""Process and dispatch input
@@ -97,36 +112,55 @@ class PortfolioOptimization:
return True
def call_add(self, other_args: List[str]):
+ """Process add command"""
self.add_stocks(self, other_args)
def call_select(self, other_args: List[str]):
+ """Process select command"""
self.tickers = []
self.add_stocks(self, other_args)
- def call_equal_weight(self, other_args: List[str]):
- po_api.equal_weight(self.tickers, other_args)
+ def call_equal(self, other_args: List[str]):
+ """Process equal command"""
+ optimizer_view.equal_weight(self.tickers, other_args)
- def call_mkt_cap(self, other_args: List[str]):
- po_api.property_weighting(self.tickers, "marketCap", other_args)
+ def call_mktcap(self, other_args: List[str]):
+ """Process mktcap command"""
+ other_args.insert(0, "marketCap")
+ optimizer_view.property_weighting(self.tickers, other_args)
- def call_div_yield(self, other_args: List[str]):
- po_api.property_weighting(self.tickers, "dividendYield", other_args)
+ def call_dividend(self, other_args: List[str]):
+ """Process dividend command"""
+ other_args.insert(0, "dividendYield")
+ optimizer_view.property_weighting(self.tickers, other_args)
- def call_max_sharpe(self, other_args: List[str]):
- po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
+ def call_property(self, other_args: List[str]):
+ """Process property command"""
+ optimizer_view.property_weighting(self.tickers, other_args)
- def call_min_vol(self, other_args: List[str]):
- po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
+ def call_maxsharpe(self, other_args: List[str]):
+ """Process maxsharpe command"""
+ optimizer_view.max_sharpe(self.tickers, other_args)
- def call_eff_risk(self, other_args: List[str]):
- po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
+ def call_minvol(self, other_args: List[str]):
+ """Process minvol command"""
+ optimizer_view.min_volatility(self.tickers, other_args)
- def call_eff_ret(self, other_args: List[str]):
- po_api.ef_portfolio(self.tickers, "eff_ret", other_args)
+ def call_maxquadutil(self, other_args: List[str]):
+ """Process maxquadutil command"""
+ optimizer_view.max_quadratic_utility(self.tickers, other_args)
- def call_show_ef(self, other_args):
- po_api.show_ef(self.tickers, other_args)
- print("")
+ def call_effrisk(self, other_args: List[str]):
+ """Process effrisk command"""
+ optimizer_view.efficient_risk(self.tickers, other_args)
+
+ def call_effret(self, other_args: List[str]):
+ """Process effret command"""
+ optimizer_view.efficient_return(self.tickers, other_args)
+
+ def call_ef(self, other_args):
+ """Process ef command"""
+ optimizer_view.show_ef(self.tickers, other_args)
def call_yolo(self, _):
# Easter egg :)
@@ -136,11 +170,11 @@ class PortfolioOptimization:
@staticmethod
def add_stocks(self, other_args: List[str]):
- """ Add ticker to current list for optimization"""
+ """ Add ticker or Select tickes for portfolio to be optimized """
parser = argparse.ArgumentParser(
add_help=False,
- prog="add",
- description="""Add tickers for optimizing.""",
+ prog="add/select",
+ description="""Add/Select tickers for portfolio to be optimized.""",
)
parser.add_argument(
"-t",
@@ -148,10 +182,9 @@ class PortfolioOptimization:
dest="add_tickers",
type=lambda s: [str(item).upper() for item in s.split(",")],
default=[],
- help="add tickers to optimzation.",
+ help="tickers to be used in the portfolio to optimize.",
)
try:
-
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-t")
@@ -162,12 +195,14 @@ class PortfolioOptimization:
tickers = set(self.tickers)
for ticker in ns_parser.add_tickers:
tickers.add(ticker)
+
+ if self.tickers:
+ print(
+ f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}"
+ )
+
self.tickers = list(tickers)
- print(
- f"\nCurrent Tickers: {('None', ', '.join(self.tickers))[bool(self.tickers)]}"
- )
- print("")
except Exception as e:
print(e)
@@ -196,6 +231,8 @@ def menu(tickers: List[str]):
an_input = input(f"{get_flair()} (po)> ")
try:
+ plt.close("all")
+
process_input = po_controller.switch(an_input)
if process_input is not None:
diff --git a/gamestonk_terminal/screener/README.md b/gamestonk_terminal/screener/README.md
index 6271a7684db..3135fb3f981 100644
--- a/gamestonk_terminal/screener/README.md
+++ b/gamestonk_terminal/screener/README.md
@@ -23,8 +23,8 @@ This menu aims to filter stocks based on pre-specified preset filters, and the u
* contains [Oversold (-s) signal example](#signal-oversold)
* [signals](#signals)
* view filter signals (e.g. -s top_gainers) [Finviz]
-* [po](#port_opt)
- * go to the portfolio optimization menu
+* [> po](portfolio_optimization/README.md)
+ * portfolio optimization for last screened tickers
## view
@@ -190,4 +190,4 @@ usage: po ticker1 ticker2 ...
````
example: po aapl msft tsla gme
-````
\ No newline at end of file
+````
diff --git a/gamestonk_terminal/screener/finviz_view.py b/gamestonk_terminal/screener/finviz_view.py
index 4ca3bb96857..3c331be9649 100644
--- a/gamestonk_terminal/screener/finviz_view.py
+++ b/gamestonk_terminal/screener/finviz_view.py
@@ -214,12 +214,16 @@ def screener(other_args: List[str], loaded_preset: str, data_type: str):
if isinstance(df_screen, pd.DataFrame):
print(df_screen.to_string())
- print("")
+ print("")
+ return list(df_screen["Ticker"].values)
+ else:
+ print("")
+ return []
except Exception as e:
print(e)
print("")
- return
+ return []
def view_signals(other_args: List[str]):
diff --git a/gamestonk_terminal/screener/screener_controller.py b/gamestonk_terminal/screener/screener_controller.py
index 0fe364750b0..7edc438c501 100644
--- a/gamestonk_terminal/screener/screener_controller.py
+++ b/gamestonk_terminal/screener/screener_controller.py
@@ -41,6 +41,7 @@ class ScreenerController:
def __init__(self):
"""Constructor"""
self.preset = "template"
+ self.screen_tickers = []
self.scr_parser = argparse.ArgumentParser(add_help=False, prog="scr")
self.scr_parser.add_argument(
"cmd",
@@ -71,8 +72,11 @@ class ScreenerController:
print("")
print(" signals view filter signals (e.g. -s top_gainers)")
print("")
- print(" > po go to the portfolio optimization menu")
- print("")
+ if self.screen_tickers:
+ print(f"Last screened tickers: {', '.join(self.screen_tickers)}")
+ print("")
+ print(" > po portfolio optimization for last screened tickers")
+ print("")
@staticmethod
def view_available_presets(other_args: List[str]):
@@ -191,39 +195,41 @@ class ScreenerController:
def call_historical(self, other_args: List[str]):
"""Process historical command"""
- yahoo_finance_view.historical(other_args, self.preset)
+ self.screen_tickers = yahoo_finance_view.historical(other_args, self.preset)
def call_overview(self, other_args: List[str]):
"""Process overview command"""
- finviz_view.screener(other_args, self.preset, "overview")
+ self.screen_tickers = finviz_view.screener(other_args, self.preset, "overview")
def call_valuation(self, other_args: List[str]):
"""Process valuation command"""
- finviz_view.screener(other_args, self.preset, "valuation")
+ self.screen_tickers = finviz_view.screener(other_args, self.preset, "valuation")
def call_financial(self, other_args: List[str]):
"""Process financial command"""
- finviz_view.screener(other_args, self.preset, "financial")
+ self.screen_tickers = finviz_view.screener(other_args, self.preset, "financial")
def call_ownership(self, other_args: List[str]):
"""Process ownership command"""
- finviz_view.screener(other_args, self.preset, "ownership")
+ self.screen_tickers = finviz_view.screener(other_args, self.preset, "ownership")
def call_performance(self, other_args: List[str]):
"""Process performance command"""
- finviz_view.screener(other_args, self.preset, "performance")
+ self.screen_tickers = finviz_view.screener(
+ other_args, self.preset, "performance"
+ )
def call_technical(self, other_args: List[str]):
"""Process technical command"""
- finviz_view.screener(other_args, self.preset, "technical")
+ self.screen_tickers = finviz_view.screener(other_args, self.preset, "technical")
def call_signals(self, other_args: List[str]):
"""Process signals command"""
finviz_view.view_signals(other_args)
def call_po(self, other_args: List[str]):
- """Call the portfolio optimization menu"""
- return po_controller.menu(other_args)
+ """Call the portfolio optimization menu with selected tickers"""
+ return po_controller.menu(self.screen_tickers)
def menu():
diff --git a/gamestonk_terminal/screener/yahoo_finance_view.py b/gamestonk_terminal/screener/yahoo_finance_view.py
index 6ad1c6e93bb..2a05ddbeec1 100644
--- a/gamestonk_terminal/screener/yahoo_finance_view.py
+++ b/gamestonk_terminal/screener/yahoo_finance_view.py
@@ -1,5 +1,6 @@
import argparse
from typing import List
+import random
from pandas.plotting import register_matplotlib_converters
import matplotlib.pyplot as plt
import datetime
@@ -119,10 +120,12 @@ def historical(other_args: List[str], preset_loaded: str):
if len(l_stocks) > 10:
print(
- "We limit stocks shown to 10, as after this the plot becomes too noisy. "
- "Also, we ran out of different colors."
+ "\nThe limit of stocks to compare with are 10. Hence, 10 random similar stocks will be displayed.",
+ "\nThe selected list will be:",
)
- l_stocks = l_stocks[:10]
+ random.shuffle(l_stocks)
+ l_stocks = sorted(l_stocks[:10])
+ print(", ".join(l_stocks))
while l_stocks:
l_parsed_stocks = list()
@@ -179,8 +182,11 @@ def historical(other_args: List[str], preset_loaded: str):
plt.show()
print("")
+ return l_parsed_stocks
except SystemExit:
print("Similar companies need to be provided", "\n")
+ return []
except Exception as e:
print(e, "\n")
+ return []
diff --git a/gamestonk_terminal/technical_analysis/README.md b/gamestonk_terminal/technical_analysis/README.md
index c35ebb6bdaa..0e5c511d850 100644
--- a/gamestonk_terminal/technical_analysis/README.md
+++ b/gamestonk_terminal/technical_analysis/README.md
@@ -43,7 +43,7 @@ This menu aims to perform a technical analysis on a pre-loaded ticker chart, and
* [obv](#obv)
- on balance volume
-**S/O to https://github.com/twopirllc/pandas-ta** _Owing to this library, it is fairly easy to add other technical indicators. So, let me know if there's any that you would like. Personally I use mostly these ones, hence why I didn't add more._
+**S/O to https://github.com/twopirllc/pandas-ta** _Owing to this library, it is fairly easy to add other technical indicators. So, let us know if there's any other that you would like._
## view
```