Merge remote-tracking branch 'upstream/main' into main

This commit is contained in:
Hinko Kocevar
2021-04-17 20:04:18 +02:00
24 changed files with 1606 additions and 405 deletions

View File

@@ -257,10 +257,14 @@ These are the ones where a key is necessary:
* Financial Modeling Prep: https://financialmodelingprep.com/developer
* FRED: https://fred.stlouisfed.org/docs/api/api_key.html
* News API: https://newsapi.org
* Tradier: https://developer.tradier.com/getting_started
When these are obtained, don't forget to update [config_terminal.py](/gamestonk_terminal/config_terminal.py).
Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE, GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME, GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY, GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN.
Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE,
GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME,
GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY,
GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN, GT_TRADIER_TOKEN .
Example:
```

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)
@@ -33,7 +34,8 @@
* [x] Top Losers (@didier) - [PR #171](https://github.com/DidierRLopes/GamestonkTerminal/pull/171)
* [x] ARK orders (@aia) - [PR #140](https://github.com/DidierRLopes/GamestonkTerminal/pull/140)
* [x] Analyse sectors/industry performance, overview and valuation from Finviz (@didier) - [PR #315](https://github.com/DidierRLopes/GamestonkTerminal/pull/315)
* [x] Add top ratings updates (@hinxx) - [PR #350](https://github.com/DidierRLopes/GamestonkTerminal/pull/350)
* [x] Add latest and trending news commands (@hinxx) - [PR #347](https://github.com/DidierRLopes/GamestonkTerminal/pull/347)
* [x] Add top ratings updates (@hinxx) - [PR #357](https://github.com/DidierRLopes/GamestonkTerminal/pull/357)
**NEXT**
@@ -111,6 +113,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**
@@ -143,13 +146,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
@@ -190,6 +197,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-)
@@ -79,6 +80,8 @@ Command|Description|Source
`valuation` |valuation of sectors, industry, country |[Finviz](https://finviz.com)
`performance` |performance of sectors, industry, country |[Finviz](https://finviz.com)
`spectrum` |spectrum of sectors, industry, country |[Finviz](https://finviz.com)
`latest` |latest news |[Seeking Alpha](https://seekingalpha.com/)
`trending` |trending news |[Seeking Alpha](https://seekingalpha.com/)
`ratings ` |top ratings updates |[MarketBeat](https://marketbeat.com)
 
@@ -243,16 +246,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
 
@@ -332,7 +338,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/)
 

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

@@ -35,3 +35,6 @@ API_NEWS_TOKEN = os.getenv("GT_API_NEWS_TOKEN") or "REPLACE_ME"
# Robinhood
RH_USERNAME = os.getenv("GT_RH_USERNAME") or "REPLACE_ME"
RH_PASSWORD = os.getenv("GT_RH_PASSWORD") or "REPLACE_ME"
# https://tradier.com/products/market-data-api
TRADIER_TOKEN = os.getenv("GT_TRADIER_TOKEN") or "REPLACE_ME"

View File

@@ -32,6 +32,10 @@ This menu aims to discover new stocks, and the usage of the following commands a
* performance of sectors, industry, country [Finviz]
* [spectrum](#spectrum)
* spectrum of sectors, industry, country [Finviz]
* [latest](#latest)
* latest news [Seeking Alpha]
* [trending](#trending)
* trending news [Seeking Alpha]
* [ratings](#ratings)
* top ratings updates [MarketBeat]
@@ -222,6 +226,32 @@ Spectrum of sectors, industry, country. [Source: Finviz]
![cntry_spec](https://user-images.githubusercontent.com/25267873/113639067-48fd5e80-9670-11eb-95cf-0931845ddd12.png)
## latest <a name="latest"></a>
```
usage: latest [-i N_ID] [-n N_NUM]
```
Latest news articles. [Source: Seeking Alpha]
* -i : article number found on Seeking Alpha website
* -n : number of latest articles being printed. Default 10.
<img width="1208" alt="latest" src="https://user-images.githubusercontent.com/25267873/115089633-926c6a00-9f0a-11eb-9d0e-1eedfd8ba7ce.png">
## trending <a name="trending"></a>
```
usage: trending [-i N_ID] [-n N_NUM]
```
Trending news articles. [Source: Seeking Alpha]
* -i : article number found on Seeking Alpha website
* -n : number of trending articles being printed. Default 10.
<img width="1213" alt="trending" src="https://user-images.githubusercontent.com/25267873/115089640-96988780-9f0a-11eb-9ca7-70a245fa3960.png">
## ratings <a name="ratings"></a>
```
@@ -234,4 +264,3 @@ Top ratings updates. [Source: MarketBeat]
<img width="963" alt="ratings" src="https://user-images.githubusercontent.com/25267873/115095983-4544c400-9f1b-11eb-8869-8ec8a0f8eae0.png">

View File

@@ -48,6 +48,8 @@ class DiscoveryController:
"performance",
"spectrum",
"ratings",
"latest",
"trending",
]
def __init__(self):
@@ -89,6 +91,8 @@ class DiscoveryController:
print(" valuation valuation of sectors, industry, country [Finviz]")
print(" performance performance of sectors, industry, country [Finviz]")
print(" spectrum spectrum of sectors, industry, country [Finviz]")
print(" latest latest news [Seeking Alpha]")
print(" trending trending news [Seeking Alpha]")
print(" ratings top ratings updates [MarketBeat]")
print("")
@@ -190,6 +194,14 @@ class DiscoveryController:
other_args, "spectrum"
)
def call_latest(self, other_args: List[str]):
"""Process latest command"""
seeking_alpha_view.latest_news_view(other_args)
def call_trending(self, other_args: List[str]):
"""Process trending command"""
seeking_alpha_view.trending_news_view(other_args)
def call_ratings(self, other_args: List[str]):
"""Process ratings command"""
marketbeat_view.ratings_view(other_args)

View File

@@ -1,10 +1,12 @@
""" Seeking Alpha Model """
__docformat__ = "numpy"
from typing import List
import requests
from bs4 import BeautifulSoup
import pandas as pd
from pandas.core.frame import DataFrame
from datetime import datetime
from gamestonk_terminal.helper_funcs import get_user_agent
@@ -67,3 +69,141 @@ def get_next_earnings(pages: int) -> DataFrame:
df_earnings = df_earnings.set_index("Date")
return df_earnings
def get_articles_html(url_articles: str) -> str:
"""Wraps HTTP requests.get for testibility
Parameters
----------
url_articles : str
Articles URL
Returns
-------
str
HTML page of articles
"""
articles_html = requests.get(
url_articles, headers={"User-Agent": get_user_agent()}
).text
return articles_html
def get_article_list(num: int) -> List[dict]:
"""Returns a list of latest articles
Parameters
----------
pages : int
Number of articles
Returns
-------
list
Latest articles list
"""
articles: List[dict] = []
page = 1
url_articles = f"https://seekingalpha.com/market-news/{page}"
while len(articles) < num:
text_soup_articles = BeautifulSoup(
get_articles_html(url_articles),
"lxml",
)
for item_row in text_soup_articles.findAll("li", {"class": "item"}):
item = item_row.find("a", {"class": "add-source-assigned"})
if item is None:
continue
article_url = item["href"]
if not article_url.startswith("/news/"):
continue
article_id = article_url.split("/")[2].split("-")[0]
articles.append(
{
"title": item.text,
"publishedAt": datetime.strptime(
item_row["data-last-date"], "%Y-%m-%d %H:%M:%S %z"
).strftime("%Y-%m-%d %H:%M:%S"),
"url": "https://seekingalpha.com" + article_url,
"id": article_id,
}
)
page += 1
url_articles = f"https://seekingalpha.com/market-news/{page}"
return articles
def get_trending_list(num: int) -> list:
"""Returns a list of trending articles
Parameters
----------
pages : int
Number of articles
Returns
-------
list
Trending articles list
"""
articles = list()
url_articles = "https://seekingalpha.com/news/trending_news"
response = requests.get(url_articles, headers={"User-Agent": get_user_agent()})
# Check that the API response was successful
if response.status_code != 200:
print("Invalid response\n")
else:
for item in response.json():
article_url = item["uri"]
if not article_url.startswith("/news/"):
continue
article_id = article_url.split("/")[2].split("-")[0]
articles.append(
{
"title": item["title"],
"publishedAt": item["publish_on"][: item["publish_on"].rfind(".")],
"url": "https://seekingalpha.com" + article_url,
"id": article_id,
}
)
return articles
def get_article_data(article_id: int) -> dict:
"""Returns an article
Parameters
----------
article_id : int
Article ID
Returns
-------
dict
Article data
"""
article_url = f"https://seekingalpha.com/api/v3/news/{article_id}"
response = requests.get(article_url, headers={"User-Agent": get_user_agent()})
jdata = response.json()
content = jdata["data"]["attributes"]["content"].replace("</li>", "</li>\n")
content = BeautifulSoup(content, features="html.parser").get_text()
article = {
"title": jdata["data"]["attributes"]["title"],
"publishedAt": jdata["data"]["attributes"]["lastModified"],
"url": "https://seekingalpha.com" + jdata["data"]["links"]["self"],
"content": content,
}
return article

View File

@@ -65,3 +65,147 @@ def earnings_release_dates_view(other_args: List[str]):
].to_string(index=False, header=False)
)
print("")
def latest_news_view(other_args: List[str]):
"""Prints the latest news article list
Parameters
----------
other_args : List[str]
argparse other args - ["-i", "123123", "-n", "5"]
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="latest",
description="""Latest news articles. [Source: Seeking Alpha]""",
)
parser.add_argument(
"-i",
"--id",
action="store",
dest="n_id",
type=check_positive,
default=-1,
help="article number found on Seeking Alpha website",
)
parser.add_argument(
"-n",
"--num",
action="store",
dest="n_num",
type=check_positive,
default=10,
help="number of latest articles being printed",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
# User wants to see all latest news
if ns_parser.n_id == -1:
articles = seeking_alpha_model.get_article_list(ns_parser.n_num)
for idx, article in enumerate(articles):
print(
article["publishedAt"].replace("T", " ").replace("Z", ""),
"-",
article["id"],
"-",
article["title"],
)
print(article["url"])
print("")
if idx >= ns_parser.n_num - 1:
break
# User wants to access specific article
else:
article = seeking_alpha_model.get_article_data(ns_parser.n_id)
print(
article["publishedAt"][: article["publishedAt"].rfind(":") - 3].replace(
"T", " "
),
" ",
article["title"],
)
print(article["url"])
print("")
print(article["content"])
def trending_news_view(other_args: List[str]):
"""Prints the trending news article list
Parameters
----------
other_args : List[str]
argparse other args - ["i", "123123", "-n", "5"]
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="trending",
description="""Trending news articles. [Source: Seeking Alpha]""",
)
parser.add_argument(
"-i",
"--id",
action="store",
dest="n_id",
type=check_positive,
default=-1,
help="article number found on Seeking Alpha website",
)
parser.add_argument(
"-n",
"--num",
action="store",
dest="n_num",
type=check_positive,
default=10,
help="number of trending articles being printed",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
# User wants to see all trending articles
if ns_parser.n_id == -1:
articles = seeking_alpha_model.get_trending_list(ns_parser.n_num)
for idx, article in enumerate(articles):
print(
article["publishedAt"].replace("T", " ").replace("Z", ""),
"-",
article["id"],
"-",
article["title"],
)
print(article["url"])
print("")
if idx >= ns_parser.n_num - 1:
break
# User wants to access specific article
else:
article = seeking_alpha_model.get_article_data(ns_parser.n_id)
print(
article["publishedAt"][: article["publishedAt"].rfind(":") - 3].replace(
"T", " "
),
" ",
article["title"],
)
print(article["url"])
print("")
print(article["content"])

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,11 +1,13 @@
# OPTIONS DATA
This menu aims to give insight on options. options can cause significant share price movement, and option pricing gives info about market sentiment for a ticker.
This menu aims to give insight on options. options can cause significant share price movement, and option pricing gives
info about market sentiment for a ticker.
* [volume](#volume)
* Show traded options for a given expiry [Yahoo Finance]
* [chains](#chains)
* Display option chains for
## volume <a name="get"></a>
@@ -23,3 +25,26 @@ usage: volume --expiry yyyy-mm-dd
Display volume graph for a date. [Source: Yahoo Finance]
* -e, --expiry : expiration date.
## chains <a name="chains"></a>
Display a table of options chains.
This data requires a Tradier sandbox developer account, which can be made [here](#https://tradier.com/products/market-data-api)
````
usage: chains [--calls] [--puts] [-m MIN] [-M MAX]
````
* --calls : Flag to only display call options
* --puts : Flag to display only put options
* -m : Minimum strike price to show
* -M : Maximum strike price to show
For combines tables, call options are on the left and put options are on the right of the strike
As an example, if I load my ticker and select an expiry, to view the options chain for strikes between $129 and $135, one would use:
````
chains -m 129 -M 135
````
Which outputs:
![im](https://user-images.githubusercontent.com/18151143/115068041-b191da00-9ebf-11eb-941e-4e255787ab94.png)

View File

@@ -0,0 +1,208 @@
"""Helper functions for the options menu"""
__docformat__ = "numpy"
import argparse
from typing import List
import requests
import pandas as pd
import numpy as np
from tabulate import tabulate
from gamestonk_terminal.config_terminal import TRADIER_TOKEN
from gamestonk_terminal.helper_funcs import (
check_non_negative,
parse_known_args_and_warn,
)
option_columns = [
"bid",
"ask",
"strike",
"bidsize",
"asksize",
"volume",
"open_interest",
"option_type",
]
greek_columns = ["delta", "gamma", "theta", "vega", "ask_iv", "bid_iv", "mid_iv"]
df_columns = option_columns + greek_columns
column_map = {"mid_iv": "iv", "open_interest": "oi", "volume": "vol"}
def process_chains(response: requests.models.Response) -> pd.DataFrame:
"""
Function to take in the requests.get and return a DataFrame
Parameters
----------
response: requests.models.Response
This is the respnose from tradier api.
Returns
-------
opt_chain: pd.DataFrame
Dataframe with all available options
"""
json_response = response.json()
options = json_response["options"]["option"]
opt_chain = pd.DataFrame(columns=df_columns)
for idx, option in enumerate(options):
data = [option[col] for col in option_columns]
data += [option["greeks"][col] for col in greek_columns]
opt_chain.loc[idx, :] = data
return opt_chain
def get_option_chains(symbol: str, expiry: str) -> pd.DataFrame:
"""
Parameters
----------
symbol : str
Ticker to get options for
expiry : str
Expiration date in the form of "YYYY-MM-DD"
Returns
-------
chains: pd.DataFrame
Dataframe with options for the gievn Symbol and Expiration date
"""
params = {"symbol": symbol, "expiration": expiry, "greeks": "true"}
headers = {"Authorization": f"Bearer {TRADIER_TOKEN}", "Accept": "application/json"}
response = requests.get(
"https://sandbox.tradier.com/v1/markets/options/chains",
params=params,
headers=headers,
)
if response.status_code == 401:
print("Error in request.get -- check token")
return
chains = process_chains(response)
return chains
def display_chains(symbol: str, expiry: str, other_args: List[str]):
parser = argparse.ArgumentParser(
prog="chains", add_help=False, description="Display options chains"
)
parser.add_argument(
"--calls",
action="store_true",
default=False,
dest="calls_only",
help="Flag to show calls only",
)
parser.add_argument(
"--puts",
action="store_true",
default=False,
dest="puts_only",
help="Flag to show puts only",
)
parser.add_argument(
"-m",
"--min",
dest="min_sp",
type=check_non_negative,
default=-1,
help="minimum strike price to consider.",
)
parser.add_argument(
"-M",
"--max",
dest="max_sp",
type=check_non_negative,
default=-1,
help="maximum strike price to consider.",
)
chains_df = get_option_chains(symbol, expiry)
default_columns = [
"mid_iv",
"delta",
"volume",
"open_interest",
"bid",
"ask",
"strike",
"option_type",
]
chains_df = chains_df[default_columns].rename(columns=column_map)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser.max_sp == -1 and ns_parser.min_sp == -1:
min_strike = np.percentile(chains_df["strike"], 25)
max_strike = np.percentile(chains_df["strike"], 75)
else:
min_strike = ns_parser.min_sp
max_strike = ns_parser.max_sp
chains_df = chains_df[chains_df["strike"] >= min_strike]
chains_df = chains_df[chains_df["strike"] <= max_strike]
calls_df = chains_df[chains_df.option_type == "call"].drop(
columns=["option_type"]
)
puts_df = chains_df[chains_df.option_type == "put"].drop(
columns=["option_type"]
)
if ns_parser.calls_only:
print(
tabulate(
calls_df,
headers=calls_df.columns,
tablefmt="grid",
showindex=False,
floatfmt=".2f",
)
)
return
if ns_parser.puts_only:
print(
tabulate(
puts_df,
headers=puts_df.columns,
tablefmt="grid",
showindex=False,
floatfmt=".2f",
)
)
return
puts_df = puts_df[puts_df.columns[::-1]]
chain_table = calls_df.merge(puts_df, on="strike")
headers = [
col.strip("_x")
if col.endswith("_x")
else col.strip("_y")
if col.endswith("_y")
else col
for col in chain_table.columns
]
print(
tabulate(
chain_table,
headers=headers,
tablefmt="fancy_grid",
showindex=False,
floatfmt=".2f",
)
)
except Exception as e:
print(e)
print("")
return

View File

@@ -9,7 +9,7 @@ import yfinance as yf
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.options import volume_view
from gamestonk_terminal.options import volume_view, chains_view
from gamestonk_terminal.menu import session
@@ -17,7 +17,7 @@ class OptionsController:
"""Options Controller class."""
# Command choices
CHOICES = ["help", "q", "quit", "exp", "voi", "vcalls", "vputs"]
CHOICES = ["help", "q", "quit", "exp", "voi", "vcalls", "vputs", "chains"]
def __init__(self, ticker: str, last_adj_close_price: float):
"""Construct data."""
@@ -93,6 +93,8 @@ class OptionsController:
print(" vcalls calls volume + open interest plot")
print(" vputs puts volume + open interest plot")
print("")
print(" chains display option chains")
print("")
return
def switch(self, an_input: str):
@@ -158,6 +160,9 @@ class OptionsController:
self.options.puts,
)
def call_chains(self, other_args):
chains_view.display_chains(self.ticker, self.expiry_date, other_args)
def menu(ticker: str, last_adj_close_price: float):
""" Options Menu. """

View File

@@ -2,12 +2,12 @@
__docformat__ = "numpy"
from bisect import bisect_left
import seaborn as sns
from typing import List
import argparse
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from gamestonk_terminal.helper_funcs import (
plot_autoscale,

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