Merge pull request #349 from DidierRLopes/picky-po

Big-ish PO refactoring
This commit is contained in:
DidierRLopes
2021-04-17 01:29:51 +01:00
committed by GitHub
14 changed files with 1026 additions and 398 deletions

View File

@@ -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**

View File

@@ -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)
 
 

View File

@@ -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 <a name="get"></a>

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 <a name="add"></a>
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 <a name="weighting"></a>
* 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 <a name="select"></a>
## Mean Variance Optimization<a name="eff_front"></a>
```text
select [-t ADD_TICKERS]
```
Add/Select tickers for portfolio to be optimized.
* -t : Tickers to be used in the portfolio to optimize
### equal <a name="equal"></a>
```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
![equal](https://user-images.githubusercontent.com/25267873/114958292-9771e080-9e5a-11eb-9de6-1c2dfbe1e27b.png)
### mktcap <a name="mktcap"></a>
```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
![mktCap](https://user-images.githubusercontent.com/25267873/115084151-57fdcf80-9f00-11eb-885c-bfe93de0eb6b.png)
### dividend <a name="dividend"></a>
```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
<img width="990" alt="Captura de ecrã 2021-04-16, às 22 11 41" src="https://user-images.githubusercontent.com/25267873/115084405-c5a9fb80-9f00-11eb-8feb-a5076fab9896.png">
### property <a name="property"></a>
```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
![marketCap](https://user-images.githubusercontent.com/25267873/114958294-980a7700-9e5a-11eb-9db4-d06e6a1e8981.png)
## Mean Variance Optimization <a name="Mean_Variance_Optimization"></a>
These approaches are based off of the efficient frontier approach, which is meant to solve the following optimization problem.
<img src="https://latex.codecogs.com/svg.image?w^TS&space;w" title="w^TS w" />
<p align="center">
<img width="200" height="90" src="https://user-images.githubusercontent.com/25267873/115084918-73b5a580-9f01-11eb-8438-6dd427727e7b.png">
</p>
With constraints:
* Where S is the covariance matrix between stocks and R is the expected returns.
* The condition that all weights add up to 1 just implies that you want to have a net long portfolio (with no margin).
* A long-short portfolio can have negative weights and usually wants to have everything add up to 0 for a market-neutral strategy.
<img src="https://latex.codecogs.com/svg.image?w^TR&space;>&space;R^*" title="w^TR > R^*" />
Currently, we do not allow for changing risk models or adding constraints.
If there is something specific, please submit a feature request, or if you can write it, feel free to add a PR!
All of these commands use [PyPortFolioOpt](#https://pyportfolioopt.readthedocs.io/en/latest/index.html) package.
\
<img src="https://latex.codecogs.com/svg.image?w_1&plus;w_2&plus;...w_n&space;=&space;1" title="w_1+w_2+...w_n = 1" />
Where S is the covariance matrix between stocks and R is the expected returns. The condition that all weights add up to 1
just implies that you want to have a net long portfolio (with no margin).
A long-short portfolio can have negative weights and usually wants to have everything add up to 0 for a market-neutral strategy.
### maxsharpe <a name="maxhsarpe"></a>
Currently, we do not allow for changing risk models or adding constraints. If there is something specific, please submit a feature request, or if you can
write it, feel free to add a PR!
All of our current implementations use the [PyPortFolioOpt](#https://pyportfolioopt.readthedocs.io/en/latest/index.html) package.
### max_sharpe
The sharpe ratio is defined as
(Mean Returns - Risk Free Rate)/(Standard Deviation of Returns)
The usage is:
````
max_sharpe [-p PERIOD] [-v --value VALUE] [--pie]
usage: max_sharpe [-p {1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max}] [-v VALUE] [--pie] [-r RISK_FREE_RATE]
````
Maximise the Sharpe Ratio. The result is also referred to as the tangency portfolio, as it is the portfolio for which the capital market line is tangent to the efficient frontier. This is a convex optimization problem after making a certain variable substitution. See Cornuejols and Tutuncu (2006) <http://web.math.ku.dk/~rolf/CT_FinOpt.pdf> for more. The sharpe ratio is defined as (Mean Returns - Risk Free Rate)/(Standard Deviation of Returns).
* -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
<img width="997" alt="maxsharpe" src="https://user-images.githubusercontent.com/25267873/114958291-9771e080-9e5a-11eb-824d-f9d1e5b22444.png">
### minvol <a name="minvol"></a>
````
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.
![min_vol](https://user-images.githubusercontent.com/25267873/114958290-96d94a00-9e5a-11eb-9b7c-deba8a8e7249.png)
<img width="995" alt="min_vol2" src="https://user-images.githubusercontent.com/25267873/114958289-96d94a00-9e5a-11eb-8c50-c4f834f0d8e1.png">
### maxquadutil <a name="maxquadutil"></a>
````
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.
<img width="1004" alt="maxquadutil" src="https://user-images.githubusercontent.com/25267873/114958288-9640b380-9e5a-11eb-8b4e-ac61b11db638.png">
### effret <a name="effret"></a>
````
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.
<img width="990" alt="effret" src="https://user-images.githubusercontent.com/25267873/114958286-9640b380-9e5a-11eb-986a-0ddf359121ae.png">
### effrisk <a name="effrisk"></a>
````
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.
![effrisk](https://user-images.githubusercontent.com/25267873/114958284-95a81d00-9e5a-11eb-8626-988505db8890.png)
<img width="1002" alt="effrisk2" src="https://user-images.githubusercontent.com/25267873/114958277-92ad2c80-9e5a-11eb-9e7e-ba60a4ec698e.png">
### 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).
![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 omitted from the chart.
![ef](https://user-images.githubusercontent.com/25267873/114958281-93de5980-9e5a-11eb-967b-d37ddd4f3e73.png)

View File

@@ -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

View File

@@ -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="-")

View File

@@ -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:

View File

@@ -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 <a name="view"></a>
@@ -190,4 +190,4 @@ usage: po ticker1 ticker2 ...
````
example: po aapl msft tsla gme
````
````

View File

@@ -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]):

View File

@@ -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():

View File

@@ -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 []

View File

@@ -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 <a name="view"></a>
```