Resolving conflicts

This commit is contained in:
Artem Veremey
2021-04-19 10:50:47 -07:00
40 changed files with 3735 additions and 251 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:
```
@@ -359,6 +363,9 @@ Distributed under the MIT License. See [LICENSE](https://github.com/DidierRLopes
[Artem Veremy](https://www.linkedin.com/in/veremey/) - artem@veremey.net
[James Maslek](https://www.linkedin.com/in/james-maslek-b6810186/) - jmaslek11@gmail.com
Feel free to share loss porn, memes or any questions at https://discord.gg/FmKjHKMn
## Acknowledgments
* [VICE article](https://www.vice.com/en/article/qjp9vp/gamestonk-terminal-is-a-diy-meme-stock-version-of-bloomberg-terminal)

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,6 +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 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**
@@ -76,6 +79,8 @@
---
## Due Diligence
* [x] Add dark pools (ATS) vs Non-ATS data over time (@didier) - [PR #363](https://github.com/DidierRLopes/GamestonkTerminal/pull/363)
* [x] Add failure to deliver command (@hinxx) - [PR #366](#https://github.com/DidierRLopes/GamestonkTerminal/pull/366)
**NEXT**
@@ -110,6 +115,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,7 +148,16 @@
* [ ] Add Brokers (td, webull, etc) (@jmaslek)
* [ ] Refactoring
* [ ] Summaries / tear sheets (@jmaslek)
* [ ] Portfolio Optimization (@jmaslek)
___
## 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**
* [ ] Allow for more custom optimization constrains
---
@@ -184,6 +199,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,9 @@ 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)
 
@@ -209,6 +213,8 @@ Command|Description|Source
`sec` |SEC filings |[MarketWatch](https://www.marketwatch.com/)
`short` |short interest |[Quandl](https://www.quandl.com/)
`warnings` |company warnings according to Sean Seah book |[MarketWatch](https://www.marketwatch.com/)
`dp` |dark pools (ATS) vs OTC data [FINRA](https://www.finra.org/#/)
`ftd` |display fails-to-deliver data [SEC](https://www.sec.gov)
 
@@ -241,6 +247,24 @@ Command|Description|Brokers
`hold` | view net holdings across all logins
 
## Portfolio Optimization [»](portfolio_optimization/README.md)
Command|Description
------|------
`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
 
## Cryptocurrency [»](cryptocurrency/README.md)
Command|Description
@@ -316,7 +340,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/)
 
@@ -336,4 +362,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

@@ -16,6 +16,7 @@ from gamestonk_terminal.comparison_analysis import yahoo_finance_api as yf_api
from gamestonk_terminal.comparison_analysis import market_watch_api as mw_api
from gamestonk_terminal.comparison_analysis import finbrain_api as f_api
from gamestonk_terminal.comparison_analysis import finviz_compare_view
from gamestonk_terminal.portfolio_optimization import po_controller
from gamestonk_terminal.menu import session
from prompt_toolkit.completion import NestedCompleter
@@ -43,6 +44,7 @@ class ComparisonAnalysisController:
"ownership",
"performance",
"technical",
"po",
]
def __init__(
@@ -82,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")
@@ -108,6 +108,9 @@ class ComparisonAnalysisController:
print(" performance brief performance comparison")
print(" technical brief technical comparison")
print("")
if self.similar:
print(" > po portfolio optimization for selected tickers")
print("")
return
@staticmethod
@@ -315,6 +318,10 @@ class ComparisonAnalysisController:
"""Process technical command"""
finviz_compare_view.screener(other_args, "technical", self.ticker, self.similar)
def call_po(self, _):
"""Call the portfolio optimization menu with selected tickers"""
return po_controller.menu([self.ticker] + self.similar)
def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str):
"""Comparison Analysis Menu"""

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,12 @@ 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]
## map <a name="map"></a>
@@ -218,3 +224,44 @@ Spectrum of sectors, industry, country. [Source: Finviz]
* -g : Data group (sector, industry or country). Default: Sector.
![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] [-d DATE]
```
Latest news articles. [Source: Seeking Alpha]
* -i : Article ID number.
* -n : Number of articles being printed. Default 10.
* -d : Date of news article.
<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 ID number.
* -n : Number of 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>
```
usage: ratings [-t N_THRESHOLD]
```
Top ratings updates. [Source: MarketBeat]
* -t : Minimum threshold in percentage change between current and target price to show ratings. Default: 100.
<img width="963" alt="ratings" src="https://user-images.githubusercontent.com/25267873/115095983-4544c400-9f1b-11eb-8869-8ec8a0f8eae0.png">

View File

@@ -20,6 +20,7 @@ from gamestonk_terminal.discovery import (
spachero_view,
unusual_whales_view,
yahoo_finance_view,
marketbeat_view,
)
@@ -46,6 +47,9 @@ class DiscoveryController:
"valuation",
"performance",
"spectrum",
"ratings",
"latest",
"trending",
]
def __init__(self):
@@ -87,6 +91,9 @@ 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("")
def switch(self, an_input: str):
@@ -187,6 +194,18 @@ 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)
def menu():
"""Discovery Menu"""

View File

@@ -0,0 +1,93 @@
""" MarketBeat Model """
__docformat__ = "numpy"
from typing import List
import requests
from bs4 import BeautifulSoup
from gamestonk_terminal.helper_funcs import get_user_agent
def get_ratings_html(url_ratings: str) -> str:
"""Wraps HTTP requests.get for testibility
Parameters
----------
url_ratings : str
Ratings URL
Returns
-------
str
HTML page of ratings
"""
ratings_html = requests.get(
url_ratings, headers={"User-Agent": get_user_agent()}
).text
return ratings_html
def get_ratings() -> List[dict]:
"""Returns a list of ratings
Parameters
----------
None
Returns
-------
list
Ratings list
"""
ratings = list()
url_ratings = "https://www.marketbeat.com/ratings/"
text_soup_ratings = BeautifulSoup(
get_ratings_html(url_ratings),
"lxml",
)
for stock_rows in text_soup_ratings.findAll("tr"):
tds = stock_rows.findAll("td")
if len(tds) == 8:
rating = {}
# company
td = tds[0]
company = td.find("div", {"class": "title-area"}).text
ticker = td.find("div", {"class": "ticker-area"}).text
rating["company"] = company
rating["ticker"] = ticker
# action
td = tds[1]
action = td.text
rating["action"] = action
# brokerage
td = tds[2]
brokerage = td.find("a").text
rating["brokerage"] = brokerage
# current price
td = tds[3]
if td.find("span"):
td.span.extract()
current_price = td.text
rating["current_price"] = current_price
# target price
td = tds[4]
target_price = td.text
rating["target_price"] = target_price
# new rating
td = tds[5]
new_rating = td.text
rating["rating"] = new_rating
# impact on price
td = tds[6]
if td.find("a"):
impact = td.find("a").text
else:
impact = td.text
rating["impact"] = impact
ratings.append(rating)
return ratings

View File

@@ -0,0 +1,116 @@
""" MarketBeat View """
__docformat__ = "numpy"
import argparse
import numpy as np
import pandas as pd
from typing import List
from gamestonk_terminal.helper_funcs import (
check_positive,
parse_known_args_and_warn,
)
from gamestonk_terminal.discovery import marketbeat_model
def ratings_view(other_args: List[str]):
"""Prints top ratings updates
Parameters
----------
other_args : List[str]
argparse other args - ["-t", "100"]
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="ratings",
description="""Top ratings updates. [Source: MarketBeat]""",
)
parser.add_argument(
"-t",
"--threshold",
action="store",
dest="n_threshold",
type=check_positive,
default=100,
help="Minimum threshold in percentage change between current and target price to show ratings",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-t")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
try:
ratings = marketbeat_model.get_ratings()
df_ratings = pd.DataFrame(ratings)
df_ratings = df_ratings[df_ratings["target_price"] != ""]
df_ratings["old_target"] = df_ratings["target_price"].apply(
lambda x: x.split(" ➝ ")[0] if "➝" in x else ""
)
df_ratings["new_target"] = df_ratings["target_price"].apply(
lambda x: x.split(" ➝ ")[1] if "➝" in x else x
)
df_ratings["clean_current_price"] = df_ratings["current_price"].apply(
lambda x: "".join([c if c.isdigit() or c == "." else "" for c in str(x)])
)
df_ratings["clean_current_price"] = df_ratings["clean_current_price"].astype(
float
)
df_ratings["clean_target_price"] = df_ratings["new_target"].apply(
lambda x: "".join([c if c.isdigit() or c == "." else "" for c in str(x)])
)
df_ratings["clean_target_price"] = df_ratings["clean_target_price"].astype(
float
)
df_ratings["pct_increase"] = (
100
* (df_ratings["clean_target_price"] - df_ratings["clean_current_price"])
/ df_ratings["clean_current_price"]
)
df_ratings["pct_increase"] = df_ratings["pct_increase"].round(2)
df_ratings["pct_abs"] = np.abs(df_ratings["pct_increase"])
df_ratings["pct_abs"] = df_ratings["pct_abs"].round(2)
df_ratings = df_ratings.sort_values("pct_abs", ascending=False)
df_ratings_top = df_ratings[df_ratings["pct_abs"] > ns_parser.n_threshold][
[
"ticker",
"brokerage",
"rating",
"current_price",
"old_target",
"new_target",
"pct_increase",
]
]
df_ratings_top["pct_increase"] = df_ratings_top["pct_increase"].apply(
lambda x: str(x) + " %"
)
print(
df_ratings_top.rename(
columns={
"ticker": "Ticker",
"brokerage": "Brokerage",
"rating": "Rating",
"current_price": "Current Price",
"old_target": "Old Target",
"new_target": "New Target",
"pct_increase": "Percentage Increase",
}
).to_string(index=False)
)
print("")
except Exception as e:
print(e, "\n")

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,145 @@ 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(start_date: datetime, num: int) -> List[dict]:
"""Returns a list of latest articles
Parameters
----------
start_date : datetime
Starting date
num : 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]
article_date = datetime.strptime(
item_row["data-last-date"], "%Y-%m-%d %H:%M:%S %z"
)
if start_date.date() < article_date.date():
continue
articles.append(
{
"title": item.text,
"publishedAt": article_date.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

@@ -4,9 +4,11 @@ __docformat__ = "numpy"
import argparse
from typing import List
import pandas as pd
from datetime import datetime
from gamestonk_terminal.helper_funcs import (
check_positive,
parse_known_args_and_warn,
valid_date,
)
from gamestonk_terminal.discovery import seeking_alpha_model
@@ -65,3 +67,158 @@ 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 ID number",
)
parser.add_argument(
"-n",
"--num",
action="store",
dest="n_num",
type=check_positive,
default=10,
help="number of articles being printed",
)
parser.add_argument(
"-d",
"--date",
action="store",
dest="n_date",
type=valid_date,
default=datetime.now().strftime("%Y-%m-%d"),
help="starting date",
)
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_date, 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 ID number",
)
parser.add_argument(
"-n",
"--num",
action="store",
dest="n_num",
type=check_positive,
default=10,
help="number of 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

@@ -24,6 +24,10 @@ This menu aims to help in due-diligence of a pre-loaded stock, and the usage of
* short interest [Quandl]
* [warnings](#warnings)
* company warnings according to Sean Seah book [Market Watch]
* [dp](#dp)
* dark pools (ATS) vs OTC data [FINRA]
* [ftd](#ftd)
* fails-to-deliver data [SEC]
## news <a name="news"></a>
@@ -157,3 +161,26 @@ Sean Seah warnings. Check: Consistent historical earnings per share; Consistentl
* -d : print insights into warnings calculation. Default False.
<img width="927" alt="warnings" src="https://user-images.githubusercontent.com/25267873/108609497-2ec03780-73c6-11eb-8577-d5da80dae213.png">
## dp <a name="dp"></a>
```text
usage: dp
```
Display barchart of dark pool (ATS) and OTC (Non ATS) data
![dp](https://user-images.githubusercontent.com/25267873/115130908-7987b580-9feb-11eb-8bca-1999174178d0.png)
## ftd <a name="ftd"></a>
```text
usage: ftd [-n N_NUM]
```
The fails-to-deliver data collected by SEC. Fails to deliver on a given day are a cumulative number of all fails outstanding until that day, plus new fails that occur that day, less fails that settle that day. See <https://www.sec.gov/data/foiadocsfailsdatahtm>. [Source: SEC]
* -n : number of latest fails-to-deliver being printed. Default 20.
![ftd](https://user-images.githubusercontent.com/25267873/115201750-93a9bc80-a0ed-11eb-8f13-68508749259e.png)

View File

@@ -13,6 +13,8 @@ from gamestonk_terminal.due_diligence import market_watch_view as mw_view
from gamestonk_terminal.due_diligence import quandl_view as q_view
from gamestonk_terminal.due_diligence import reddit_view as r_view
from gamestonk_terminal.due_diligence import news_view
from gamestonk_terminal.due_diligence import finra_view
from gamestonk_terminal.due_diligence import sec_view
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair
from gamestonk_terminal.menu import session
@@ -37,6 +39,8 @@ class DueDiligenceController:
"analyst",
"warnings",
"sec",
"dp",
"ftd",
]
def __init__(self, stock: DataFrame, ticker: str, start: str, interval: str):
@@ -97,6 +101,8 @@ class DueDiligenceController:
print(
" warnings company warnings according to Sean Seah book [Market Watch]"
)
print(" dp dark pools (ATS) vs OTC data [FINRA]")
print(" ftd fails-to-deliver data [SEC]")
print("")
def switch(self, an_input: str):
@@ -175,6 +181,14 @@ class DueDiligenceController:
""" Process short command """
q_view.short_interest(other_args, self.ticker, self.start)
def call_dp(self, other_args: List[str]):
""" Process dp command """
finra_view.dark_pool(other_args, self.ticker)
def call_ftd(self, other_args: List[str]):
""" Process ftd command """
sec_view.fails_to_deliver(other_args, self.ticker)
def menu(stock: DataFrame, ticker: str, start: str, interval: str):
"""Due Diligence Menu

View File

@@ -0,0 +1,307 @@
""" FINRA View """
__docformat__ = "numpy"
import argparse
from typing import List, Tuple
import requests
import pandas as pd
from matplotlib import pyplot as plt
from gamestonk_terminal.config_plot import PLOT_DPI
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale
def getFINRAweeks(tier, is_ats) -> List:
"""Get FINRA weeks
Parameters
----------
tier : str
Stock tier between T1, T2, or OTCE
is_ats : bool
ATS data if true, NON-ATS otherwise
Returns
----------
List
List of response data
"""
req_hdr = {"Accept": "application/json", "Content-Type": "application/json"}
req_data = {
"compareFilters": [
{
"compareType": "EQUAL",
"fieldName": "summaryTypeCode",
"fieldValue": "ATS_W_SMBL" if is_ats else "OTC_W_SMBL",
},
{
"compareType": "EQUAL",
"fieldName": "tierIdentifier",
"fieldValue": tier,
},
],
"delimiter": "|",
"fields": ["weekStartDate"],
"limit": 27,
"quoteValues": False,
"sortFields": ["-weekStartDate"],
}
response = requests.post(
"https://api.finra.org/data/group/otcMarket/name/weeklyDownloadDetails",
headers=req_hdr,
json=req_data,
)
return response.json() if response.status_code == 200 else list()
def getFINRAdata(weekStartDate, tier, ticker, is_ats) -> Tuple[int, List]:
"""Get FINRA data
Parameters
----------
weekStartDate : str
Weekly data to get FINRA data
tier : str
Stock tier between T1, T2, or OTCE
ticker : str
Stock ticker to get data from
is_ats : bool
ATS data if true, NON-ATS otherwise
Returns
----------
str
Status code from request
List
List of response data
"""
req_hdr = {"Accept": "application/json", "Content-Type": "application/json"}
l_cmp_filters = [
{
"compareType": "EQUAL",
"fieldName": "weekStartDate",
"fieldValue": weekStartDate,
},
{"compareType": "EQUAL", "fieldName": "tierIdentifier", "fieldValue": tier},
{
"compareType": "EQUAL",
"description": "",
"fieldName": "summaryTypeCode",
"fieldValue": "ATS_W_SMBL" if is_ats else "OTC_W_SMBL",
},
]
if ticker:
l_cmp_filters.append(
{
"compareType": "EQUAL",
"fieldName": "issueSymbolIdentifier",
"fieldValue": ticker,
}
)
req_data = {
"compareFilters": l_cmp_filters,
"delimiter": "|",
"fields": [
"issueSymbolIdentifier",
"totalWeeklyShareQuantity",
"totalWeeklyTradeCount",
"lastUpdateDate",
],
"limit": 5000,
"quoteValues": False,
"sortFields": ["totalWeeklyShareQuantity"],
}
response = requests.post(
"https://api.finra.org/data/group/otcMarket/name/weeklySummary",
headers=req_hdr,
json=req_data,
)
return (
response.status_code,
response.json() if response.status_code == 200 else list(),
)
def getTickerFINRAdata(ticker) -> Tuple[pd.DataFrame, pd.DataFrame]:
"""Get all FINRA data associated with a ticker
Parameters
----------
ticker : str
Stock ticker to get data from
Returns
----------
pd.DataFrame
Dark Pools (ATS) Data
pd.DataFrame
OTC (Non-ATS) Data
"""
tiers = ["T1", "T2", "OTCE"]
l_data = list()
for tier in tiers:
for d_week in getFINRAweeks(tier, is_ats=True):
status_code, response = getFINRAdata(
d_week["weekStartDate"], tier, ticker, True
)
if status_code == 200:
if response:
d_data = response[0]
d_data.update(d_week)
l_data.append(d_data)
else:
break
df_ats = pd.DataFrame(l_data)
if not df_ats.empty:
df_ats = df_ats.sort_values("weekStartDate")
df_ats = df_ats.set_index("weekStartDate")
l_data = list()
for tier in tiers:
for d_week in getFINRAweeks(tier, is_ats=False):
status_code, response = getFINRAdata(
d_week["weekStartDate"], tier, ticker, False
)
if status_code == 200:
if response:
d_data = response[0]
d_data.update(d_week)
l_data.append(d_data)
else:
break
df_otc = pd.DataFrame(l_data)
if not df_otc.empty:
df_otc = df_otc.sort_values("weekStartDate")
df_otc = df_otc.set_index("weekStartDate")
return df_ats, df_otc
def plot_dark_pools(ticker: str, ats: pd.DataFrame, otc: pd.DataFrame):
"""Plots ATS and NON-ATS data
Parameters
----------
ticker : str
Stock ticker to get data from
ats : pd.DataFrame
Dark Pools (ATS) Data
otc : pd.DataFrame
OTC (Non-ATS) Data
"""
_, axData = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
plt.subplot(3, 1, (1, 2))
if not ats.empty and not otc.empty:
plt.bar(
ats.index,
(ats["totalWeeklyShareQuantity"] + otc["totalWeeklyShareQuantity"])
/ 1_000_000,
color="tab:orange",
)
plt.bar(
otc.index, otc["totalWeeklyShareQuantity"] / 1_000_000, color="tab:blue"
)
plt.legend(["ATS", "OTC"])
elif not ats.empty:
plt.bar(
ats.index,
ats["totalWeeklyShareQuantity"] / 1_000_000,
color="tab:orange",
)
plt.legend(["ATS"])
elif not otc.empty:
plt.bar(
otc.index, otc["totalWeeklyShareQuantity"] / 1_000_000, color="tab:blue"
)
plt.legend(["OTC"])
plt.ylabel("Total Weekly Shares [Million]")
plt.grid(b=True, which="major", color="#666666", linestyle="-", alpha=0.2)
plt.title(f"Dark Pools (ATS) vs OTC (Non-ATS) Data for {ticker}")
plt.subplot(313)
if not ats.empty:
plt.plot(
ats.index,
ats["totalWeeklyShareQuantity"] / ats["totalWeeklyTradeCount"],
color="tab:orange",
)
plt.legend(["ATS"])
if not otc.empty:
plt.plot(
otc.index,
otc["totalWeeklyShareQuantity"] / otc["totalWeeklyTradeCount"],
color="tab:blue",
)
plt.legend(["ATS", "OTC"])
else:
plt.plot(
otc.index,
otc["totalWeeklyShareQuantity"] / otc["totalWeeklyTradeCount"],
color="tab:blue",
)
plt.legend(["OTC"])
plt.ylabel("Shares per Trade")
plt.grid(b=True, which="major", color="#666666", linestyle="-", alpha=0.2)
plt.gcf().autofmt_xdate()
plt.xlabel("Weeks")
if gtff.USE_ION:
plt.ion()
plt.show()
def dark_pool(other_args: List[str], ticker: str):
"""Display barchart of dark pool (ATS) and OTC (Non ATS) data
Parameters
----------
other_args : List[str]
argparse other args
ticker : str
Stock ticker
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="dp",
description="Display barchart of dark pool (ATS) and OTC (Non ATS) data [Source: FINRA]",
)
if 1:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
df_ats, df_otc = getTickerFINRAdata(ticker)
if df_ats.empty and df_otc.empty:
print("No ticker data found!")
plot_dark_pools(ticker, df_ats, df_otc)
print("")
"""
except Exception as e:
print(e)
print("")
return
"""

View File

@@ -0,0 +1,108 @@
""" SEC View """
__docformat__ = "numpy"
import argparse
from typing import List
from datetime import datetime
import requests
import pandas as pd
from bs4 import BeautifulSoup
from matplotlib import pyplot as plt
import matplotlib.dates as mdates
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import (
get_user_agent,
check_positive,
parse_known_args_and_warn,
)
def fails_to_deliver(other_args: List[str], ticker: str):
"""Display fails-to-deliver data for a given ticker
Parameters
----------
other_args : List[str]
argparse other args - ["-n", "10"]
ticker : str
Stock ticker
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="ftd",
description="""Prints latest fails-to-deliver data. [Source: SEC]""",
)
parser.add_argument(
"-n",
"--num",
action="store",
dest="n_num",
type=check_positive,
default=20,
help="number of latest fails-to-deliver being printed",
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
url_ftds = "https://www.sec.gov/data/foiadocsfailsdatahtm"
text_soup_ftds = BeautifulSoup(
requests.get(url_ftds, headers={"User-Agent": get_user_agent()}).text,
"lxml",
)
table = text_soup_ftds.find("table", {"class": "list"})
links = table.findAll("a")
link_idx = 0
ftds_data = pd.DataFrame()
while len(ftds_data) < ns_parser.n_num:
if link_idx > len(links):
break
link = links[link_idx]
url = "https://www.sec.gov" + link["href"]
all_ftds = pd.read_csv(
url,
compression="zip",
sep="|",
engine="python",
skipfooter=2,
usecols=[0, 2, 3, 5],
dtype={"QUANTITY (FAILS)": "int"},
)
tmp_ftds = all_ftds[all_ftds["SYMBOL"] == ticker]
del tmp_ftds["PRICE"]
del tmp_ftds["SYMBOL"]
# merge the data from this archive
ftds_data = pd.concat([ftds_data, tmp_ftds], ignore_index=True)
link_idx += 1
# clip away extra rows
ftds_data = ftds_data.sort_values("SETTLEMENT DATE")[-ns_parser.n_num :]
ftds_data["SETTLEMENT DATE"] = ftds_data["SETTLEMENT DATE"].apply(
lambda x: datetime.strptime(str(x), "%Y%m%d")
)
plt.bar(
ftds_data["SETTLEMENT DATE"],
ftds_data["QUANTITY (FAILS)"] / 1000,
)
plt.ylabel("Shares [K]")
plt.title(f"Fails-to-deliver Data for {ticker}")
plt.grid(b=True, which="major", color="#666666", linestyle="-", alpha=0.2)
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y/%m/%d"))
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=7))
plt.gcf().autofmt_xdate()
plt.xlabel("Days")
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
except Exception as e:
print(e, "\n")

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

@@ -60,6 +60,9 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open):
)
print(" pa portfolio analysis, \t\t supports: robinhood, alpaca, ally ")
print(" crypto cryptocurrencies, \t\t uses coingecko api")
print(
" po portfolio optimization, \t\t optimal portfolio weights from pyportfolioopt"
)
if s_ticker:
print(

View File

@@ -1,25 +1,89 @@
# OPTIONS DATA
# OPTIONS
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.
* [volume](#volume)
* Show traded options for a given expiry [Yahoo Finance]
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. The usage of the following commands along with an example will be exploited below:
* [exp](#exp)
* See/set expiry date [Yahoo Finance]
* [voi](#voi)
* Volume + open interest options trading plot [Yahoo Finance]
* [vcalls](#vcalls)
* Calls volume + open interest plot [Yahoo Finance]
* [vputs](#vputs)
* Puts volume + open interest plot [Yahoo Finance]
* [chains](#chains)
* Display option chains [Source: Tradier]
## volume <a name="get"></a>
## exp <a name="exp"></a>
```text
usage: volume -e yyyy-mm-dd
usage: exp [-d {0,1,2,3,4,5,6,7,8,9,10,11}]
```
or
See/set expiry dates. [Source: Yahoo Finance]
* -d : Expiry date index to set.
<img width="955" alt="exp" src="https://user-images.githubusercontent.com/25267873/115161875-f6706900-a097-11eb-8566-bb7b408856a5.png">
## voi <a name="voi"></a>
```text
usage: volume --expiry yyyy-mm-dd
usage: voi [-v MIN_VOL] [-m MIN_SP] [-M MAX_SP]
```
Plots Volume + Open Interest of calls vs puts. [Source: Yahoo Finance]
Display volume graph for a date. [Source: Yahoo Finance]
* -v : Minimum volume (considering open interest) threshold of the plot.
* -m : Minimum strike price to consider in the plot.
* -M : Maximum strike price to consider in the plot.
* -e, --expiry : expiration date.
![voinio](https://user-images.githubusercontent.com/25267873/115161878-f7a19600-a097-11eb-889b-e9cc945f174d.png)
## vcalls <a name="vcalls"></a>
```text
usage: vcalls [-m MIN_SP] [-M MAX_SP]
```
Plots Calls Volume + Open Interest. [Source: Yahoo Finance]
* -m : Minimum strike price to consider in the plot.
* -M : Maximum strike price to consider in the plot.
![vcalls](https://user-images.githubusercontent.com/25267873/115161874-f5d7d280-a097-11eb-93e2-3a2b20292f09.png)
## vputs <a name="vputs"></a>
```text
usage: vputs [-m MIN_SP] [-M MAX_SP]
```
Plots Puts Volume + Open Interest. [Source: Yahoo Finance]
* -m : Minimum strike price to consider in the plot.
* -M : Maximum strike price to consider in the plot.
![vputs](https://user-images.githubusercontent.com/25267873/115161873-f40e0f00-a097-11eb-8334-3d4f14b56766.png)
## chains <a name="chains"></a>
````
usage: chains [--calls] [--puts] [-m MIN_SP] [-M MAX_SP]
````
Display options chains. [Source: Tradier]
* --calls : Flag to show calls only
* --puts : Flag to show puts only
* -m : Minimum strike price to consider.
* -M : Maximum strike price to consider.
<img width="948" alt="chains" src="https://user-images.githubusercontent.com/25267873/115161876-f708ff80-a097-11eb-8073-195979862a45.png">

View File

@@ -0,0 +1,211 @@
"""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 option chains [Source: Tradier]",
)
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",
)
)
print("")
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
from gamestonk_terminal.options import volume_view, chains_view
from gamestonk_terminal.menu import session
@@ -17,16 +17,15 @@ class OptionsController:
"""Options Controller class."""
# Command choices
CHOICES = ["help", "q", "quit", "exp", "volume"]
CHOICES = ["help", "q", "quit", "exp", "voi", "vcalls", "vputs", "chains"]
def __init__(
self,
ticker: str,
):
def __init__(self, ticker: str, last_adj_close_price: float):
"""Construct data."""
self.ticker = ticker
self.raw_data_options = yf.Ticker(self.ticker)
self.expiry_date = self.raw_data_options.options[0]
self.yf_ticker_data = yf.Ticker(self.ticker)
self.expiry_date = self.yf_ticker_data.options[0]
self.options = self.yf_ticker_data.option_chain(self.expiry_date)
self.last_adj_close_price = last_adj_close_price
self.op_parser = argparse.ArgumentParser(add_help=False, prog="op")
self.op_parser.add_argument(
"cmd",
@@ -39,7 +38,7 @@ class OptionsController:
parser = argparse.ArgumentParser(
add_help=False,
prog="exp",
description="""See expiry dates.""",
description="""See/set expiry dates. [Source: Yahoo Finance]""",
)
parser.add_argument(
"-d",
@@ -48,7 +47,7 @@ class OptionsController:
action="store",
type=int,
default=-1,
choices=range(len(self.raw_data_options.options)),
choices=range(len(self.yf_ticker_data.options)),
help=f"Expiry date index for {self.ticker}.",
)
@@ -64,12 +63,13 @@ class OptionsController:
# Print possible expiry dates
if ns_parser.n_date == -1:
print("\nAvailable expiry dates:")
for i, d in enumerate(self.raw_data_options.options):
for i, d in enumerate(self.yf_ticker_data.options):
print(f" {(2-len(str(i)))*' '}{i}. {d}")
# It means an expiry date was correctly selected
else:
self.expiry_date = self.raw_data_options.options[ns_parser.n_date]
self.expiry_date = self.yf_ticker_data.options[ns_parser.n_date]
self.options = self.yf_ticker_data.option_chain(self.expiry_date)
print(f"\nSelected expiry date : {self.expiry_date}")
except Exception as e:
@@ -89,7 +89,11 @@ class OptionsController:
print(f"Selected expiry date: {expiry_date}")
print("")
print(" exp see/set expiry date")
print(" volume plot options trading volume / open interest")
print(" voi volume + open interest options trading plot")
print(" vcalls calls volume + open interest plot")
print(" vputs puts volume + open interest plot")
print("")
print(" chains display option chains")
print("")
return
@@ -121,26 +125,50 @@ class OptionsController:
"""Process Quit command - quit the program."""
return True
def call_volume(self, _):
"""Process volume command."""
volume.volume_graph(
self.raw_data_options,
self.ticker,
self.expiry_date,
volume_percentile_threshold=60,
)
print("")
def call_exp(self, other_args: List[str]):
"""Process exp command."""
self.expiry_dates(self, other_args)
def call_voi(self, other_args: List[str]):
"""Process voi command."""
volume_view.plot_volume_open_interest(
other_args,
self.ticker,
self.expiry_date,
self.last_adj_close_price,
self.options.calls,
self.options.puts,
)
def menu(ticker: str):
"""Options info Menu."""
def call_vcalls(self, other_args: List[str]):
"""Process vcalls command."""
volume_view.plot_calls_volume_open_interest(
other_args,
self.ticker,
self.expiry_date,
self.last_adj_close_price,
self.options.calls,
)
def call_vputs(self, other_args: List[str]):
"""Process vcalls command."""
volume_view.plot_puts_volume_open_interest(
other_args,
self.ticker,
self.expiry_date,
self.last_adj_close_price,
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. """
try:
op_controller = OptionsController(ticker)
op_controller = OptionsController(ticker, last_adj_close_price)
op_controller.call_help(None)
except IndexError:
print("No options found for " + ticker)

View File

@@ -1,176 +0,0 @@
"""options info. [Source: Yahoo Finance]."""
from bisect import bisect_left
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from gamestonk_terminal.helper_funcs import plot_autoscale
from gamestonk_terminal import config_plot as cfgPlot
def volume_graph(raw_data, ticker_name, exp_date, volume_percentile_threshold=50):
"""Docstring make linter hap."""
# SET VOLUME TO BE FILTERED, default = 50
PERCENTILE_THRESHOLD = volume_percentile_threshold
TICKER_NAME = ticker_name
raw_data_options = raw_data
EXP_DATE = exp_date
# current stock price
spot = __get_current_spot(raw_data_options)
calls = __parse_opt_data(raw_data_options, EXP_DATE)
puts = __parse_opt_data(raw_data_options, EXP_DATE, is_calls=False)
calls = __add_max_pain_data(calls, spot)
puts = __add_max_pain_data(puts, spot, is_calls=False)
max_pain = __calc_max_pain(calls, puts)
# Initialize the matplotlib figure
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
# _, ax = plt.subplots(figsize=(12, 10))
# make x axis symmetric
axis_origin = max(abs(max(puts["oi+v"])), abs(max(calls["oi+v"])))
ax.set_xlim(-axis_origin, +axis_origin)
VOLUME_THRESHOLD = np.percentile(calls["oi+v"], PERCENTILE_THRESHOLD)
sns.set_style(style="darkgrid")
g = sns.barplot(
x="oi+v",
y="strike",
data=calls[calls["oi+v"] > VOLUME_THRESHOLD],
label="Calls: Open Interest",
color="lightgreen",
orient="h",
)
g = sns.barplot(
x="volume",
y="strike",
data=calls[calls["oi+v"] > VOLUME_THRESHOLD],
label="Calls: Volume",
color="green",
orient="h",
)
g = sns.barplot(
x="oi+v",
y="strike",
data=puts[puts["oi+v"] < -VOLUME_THRESHOLD],
label="Puts: Open Interest",
color="pink",
orient="h",
)
g = sns.barplot(
x="volume",
y="strike",
data=puts[puts["oi+v"] < -VOLUME_THRESHOLD],
label="Puts: Volume",
color="red",
orient="h",
)
# draw spot line
s = [float(strike.get_text()) for strike in ax.get_yticklabels()]
spot_index = bisect_left(s, spot) # find where the spot is on the graph
spot_line = ax.axhline(spot_index, ls="--", color="dodgerblue", alpha=0.3)
# draw max pain line
max_pain_index = bisect_left(s, max_pain)
max_pain_line = ax.axhline(max_pain_index, ls="-", color="black", alpha=0.3)
max_pain_line.set_linewidth(5)
# ax.axhline(max_pain_index, ls='--')
# format ticklabels without - for puts
g.set_xticks(g.get_xticks())
xlabels = [f"{x:,.0f}".replace("-", "") for x in g.get_xticks()]
g.set_xticklabels(xlabels)
plt.title(
f"{TICKER_NAME.upper()} volumes for {EXP_DATE} (open interest displayed only during market hours)"
)
ax.invert_yaxis()
# ax.spines['left'].set_position('center')
_ = ax.legend()
handles, _ = ax.get_legend_handles_labels()
handles.append(spot_line)
handles.append(max_pain_line)
# create legend labels + add to graph
labels = [
"Calls open interest",
"Calls volume ",
"Puts open interest",
"Puts volume",
"Current stock price",
f"Max pain = {max_pain}",
]
plt.legend(handles=handles[:], labels=labels)
sns.despine(left=True, bottom=True)
plt.show()
def __add_max_pain_data(df, spot, is_calls=True):
# max pain parsing + calculation
df["spot"] = round(spot, 2)
if is_calls:
df["dv"] = spot - df.index
else:
df["dv"] = df.index - spot
df["dv"] = df["dv"].apply(lambda x: max(0, x))
df["dv"] = abs(df["dv"] * df["volume"])
return df
def __calc_max_pain(calls, puts):
df = pd.merge(calls, puts, left_index=True, right_index=True)
df["dv"] = round(df["dv_x"] + df["dv_y"], 2)
max_pain = df["dv"].idxmax()
return max_pain
def __parse_opt_data(raw_data_options, exp_date, is_calls=True):
# get option chain for specific expiration
opt = raw_data_options.option_chain(exp_date)
# PARSE DATA
if is_calls:
option_data = opt.calls
flag = "calls"
else:
option_data = opt.puts
flag = "puts"
data = option_data.pivot_table(
index="strike", values=["volume", "openInterest"], aggfunc="sum"
).reindex()
data["strike"] = data.index
data["type"] = flag
if is_calls:
data["openInterest"] = data["openInterest"]
data["volume"] = data["volume"]
else:
data["openInterest"] = -data["openInterest"]
data["volume"] = -data["volume"]
data["oi+v"] = data["openInterest"] + data["volume"]
return data
def __get_current_spot(raw_data_options):
return raw_data_options.history().tail(1)["Close"].iloc[0]

View File

@@ -0,0 +1,465 @@
""" Volume view """
__docformat__ = "numpy"
from bisect import bisect_left
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,
check_non_negative,
parse_known_args_and_warn,
)
from gamestonk_terminal import config_plot as cfgPlot
from gamestonk_terminal import feature_flags as gtff
def plot_volume_open_interest(
other_args: List[str],
ticker: str,
exp_date: str,
last_adj_close_price: float,
op_calls: pd.DataFrame,
op_puts: pd.DataFrame,
):
"""Options volume and open interest
Parameters
----------
other_args : List[str]
Command line arguments to be processed with argparse
ticker : str
Main ticker to compare income
exp_date : str
Expiry date of the option
last_adj_close_price: float
Last adjusted closing price
op_calls: pd.DataFrame
Option data calls
op_puts: pd.DataFrame
Option data puts
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="voi",
description="""
Plots Volume + Open Interest of calls vs puts. [Source: Yahoo Finance]
""",
)
parser.add_argument(
"-v",
"--minv",
dest="min_vol",
type=check_non_negative,
default=-1,
help="minimum volume (considering open interest) threshold of the plot.",
)
parser.add_argument(
"-m",
"--min",
dest="min_sp",
type=check_non_negative,
default=-1,
help="minimum strike price to consider in the plot.",
)
parser.add_argument(
"-M",
"--max",
dest="max_sp",
type=check_non_negative,
default=-1,
help="maximum strike price to consider in the plot.",
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
df_calls, df_puts, max_pain = get_calls_puts_maxpain(
op_calls, op_puts, last_adj_close_price
)
if (
ns_parser.min_vol == -1
and ns_parser.min_sp == -1
and ns_parser.max_sp == -1
):
# If no argument provided, we use the percentile 50 to get 50% of upper volume data
volume_percentile_threshold = 50
min_vol_calls = np.percentile(df_calls["oi+v"], volume_percentile_threshold)
min_vol_puts = np.percentile(df_puts["oi+v"], volume_percentile_threshold)
df_calls = df_calls[df_calls["oi+v"] > min_vol_calls]
df_puts = df_puts[df_puts["oi+v"] < min_vol_puts]
else:
if ns_parser.min_vol > -1:
df_calls = df_calls[df_calls["oi+v"] > ns_parser.min_vol]
df_puts = df_puts[df_puts["oi+v"] < -ns_parser.min_vol]
if ns_parser.min_sp > -1:
df_calls = df_calls[df_calls["strike"] > ns_parser.min_sp]
df_puts = df_puts[df_puts["strike"] > ns_parser.min_sp]
if ns_parser.max_sp > -1:
df_calls = df_calls[df_calls["strike"] < ns_parser.max_sp]
df_puts = df_puts[df_puts["strike"] < ns_parser.max_sp]
if df_calls.empty and df_puts.empty:
print(
"The filtering applied is too strong, there is no data available for such conditions.\n"
)
return
# Initialize the matplotlib figure
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
# make x axis symmetric
axis_origin = max(abs(max(df_puts["oi+v"])), abs(max(df_calls["oi+v"])))
ax.set_xlim(-axis_origin, +axis_origin)
sns.set_style(style="darkgrid")
g = sns.barplot(
x="oi+v",
y="strike",
data=df_calls,
label="Calls: Open Interest",
color="lightgreen",
orient="h",
)
g = sns.barplot(
x="volume",
y="strike",
data=df_calls,
label="Calls: Volume",
color="green",
orient="h",
)
g = sns.barplot(
x="oi+v",
y="strike",
data=df_puts,
label="Puts: Open Interest",
color="pink",
orient="h",
)
g = sns.barplot(
x="volume",
y="strike",
data=df_puts,
label="Puts: Volume",
color="red",
orient="h",
)
# draw spot line
s = [float(strike.get_text()) for strike in ax.get_yticklabels()]
spot_index = bisect_left(
s, last_adj_close_price
) # find where the spot is on the graph
spot_line = ax.axhline(spot_index, ls="--", color="dodgerblue", alpha=0.3)
# draw max pain line
max_pain_index = bisect_left(s, max_pain)
max_pain_line = ax.axhline(max_pain_index, ls="-", color="black", alpha=0.3)
max_pain_line.set_linewidth(5)
# format ticklabels without - for puts
g.set_xticks(g.get_xticks())
xlabels = [f"{x:,.0f}".replace("-", "") for x in g.get_xticks()]
g.set_xticklabels(xlabels)
plt.title(
f"{ticker} volumes for {exp_date} (open interest displayed only during market hours)"
)
ax.invert_yaxis()
_ = ax.legend()
handles, _ = ax.get_legend_handles_labels()
handles.append(spot_line)
handles.append(max_pain_line)
# create legend labels + add to graph
labels = [
"Calls open interest",
"Calls volume ",
"Puts open interest",
"Puts volume",
"Current stock price",
f"Max pain = {max_pain}",
]
plt.legend(handles=handles[:], labels=labels)
sns.despine(left=True, bottom=True)
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
except Exception as e:
print(e, "\n")
return
def get_calls_puts_maxpain(
op_calls: pd.DataFrame, op_puts: pd.DataFrame, last_adj_close_price: float
):
"""Get calls and puts dataframes, and max pain
Parameters
----------
op_calls: pd.DataFrame
Option data calls
op_puts: pd.DataFrame
Option data puts
last_adj_close_price: float
Last adjusted closing price
Returns
----------
pd.DataFrame
Processed calls dataframe
pd.DataFrame
Processed puts dataframe
float
Max pain
"""
df_calls = pd.DataFrame()
df_puts = pd.DataFrame()
max_pain = 0
if not op_calls.empty:
# Process Calls Data
df_calls = op_calls.pivot_table(
index="strike", values=["volume", "openInterest"], aggfunc="sum"
).reindex()
df_calls["strike"] = df_calls.index
df_calls["type"] = "calls"
df_calls["openInterest"] = df_calls["openInterest"]
df_calls["volume"] = df_calls["volume"]
df_calls["oi+v"] = df_calls["openInterest"] + df_calls["volume"]
df_calls["spot"] = round(last_adj_close_price, 2)
df_calls["dv"] = last_adj_close_price - df_calls.index
df_calls["dv"] = df_calls["dv"].apply(lambda x: max(0, x))
df_calls["dv"] = abs(df_calls["dv"] * df_calls["volume"])
if not op_puts.empty:
# Process Puts Data
df_puts = op_puts.pivot_table(
index="strike", values=["volume", "openInterest"], aggfunc="sum"
).reindex()
df_puts["strike"] = df_puts.index
df_puts["type"] = "puts"
df_puts["openInterest"] = df_puts["openInterest"]
df_puts["volume"] = -df_puts["volume"]
df_puts["openInterest"] = -df_puts["openInterest"]
df_puts["oi+v"] = df_puts["openInterest"] + df_puts["volume"]
df_puts["spot"] = round(last_adj_close_price, 2)
df_puts["dv"] = df_puts.index - last_adj_close_price
df_puts["dv"] = df_puts["dv"].apply(lambda x: max(0, x))
df_puts["dv"] = abs(df_puts["dv"] * df_puts["volume"])
if not op_calls.empty and not op_puts.empty:
# Get max pain
df_opt = pd.merge(df_calls, df_puts, left_index=True, right_index=True)
df_opt["dv"] = round(df_opt["dv_x"] + df_opt["dv_y"], 2)
max_pain = df_opt["dv"].idxmax()
return df_calls, df_puts, max_pain
def plot_calls_volume_open_interest(
other_args: List[str],
ticker: str,
exp_date: str,
last_adj_close_price: float,
op_calls: pd.DataFrame,
):
"""Options volume and open interest
Parameters
----------
other_args : List[str]
Command line arguments to be processed with argparse
ticker : str
Main ticker to compare income
exp_date : str
Expiry date of the option
last_adj_close_price: float
Last adjusted closing price
op_calls: pd.DataFrame
Option data calls
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="vcalls",
description="""
Plots Calls Volume + Open Interest. [Source: Yahoo Finance]
""",
)
parser.add_argument(
"-m",
"--min",
dest="min_sp",
type=check_non_negative,
default=-1,
help="minimum strike price to consider in the plot.",
)
parser.add_argument(
"-M",
"--max",
dest="max_sp",
type=check_non_negative,
default=-1,
help="maximum strike price to consider in the plot.",
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
df_calls, _, _ = get_calls_puts_maxpain(
op_calls, pd.DataFrame(), last_adj_close_price
)
if ns_parser.min_sp == -1:
min_strike = 0.75 * last_adj_close_price
else:
min_strike = ns_parser.min_sp
if ns_parser.max_sp == -1:
max_strike = 1.25 * last_adj_close_price
else:
max_strike = ns_parser.max_sp
plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
plt.axvline(last_adj_close_price, lw=2)
plt.bar(
df_calls["strike"],
df_calls["volume"] + df_calls["openInterest"],
color="lightgreen",
width=0.45,
)
plt.bar(df_calls["strike"], df_calls["volume"], color="green", width=0.45)
plt.title(f"{ticker} calls volumes for {exp_date} ")
plt.legend(["Stock Price", "Open Interest", "Volume"])
plt.xlabel("Strike Price")
plt.ylabel("Volume")
plt.xlim([min_strike, max_strike])
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
except Exception as e:
print(e, "\n")
return
def plot_puts_volume_open_interest(
other_args: List[str],
ticker: str,
exp_date: str,
last_adj_close_price: float,
op_puts: pd.DataFrame,
):
"""Options volume and open interest
Parameters
----------
other_args : List[str]
Command line arguments to be processed with argparse
ticker : str
Main ticker to compare income
exp_date : str
Expiry date of the option
last_adj_close_price: float
Last adjusted closing price
op_puts: pd.DataFrame
Option data puts
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="vputs",
description="""
Plots Puts Volume + Open Interest. [Source: Yahoo Finance]
""",
)
parser.add_argument(
"-m",
"--min",
dest="min_sp",
type=check_non_negative,
default=-1,
help="minimum strike price to consider in the plot.",
)
parser.add_argument(
"-M",
"--max",
dest="max_sp",
type=check_non_negative,
default=-1,
help="maximum strike price to consider in the plot.",
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
_, df_puts, _ = get_calls_puts_maxpain(
pd.DataFrame(), op_puts, last_adj_close_price
)
if ns_parser.min_sp == -1:
min_strike = 0.75 * last_adj_close_price
else:
min_strike = ns_parser.min_sp
if ns_parser.max_sp == -1:
max_strike = 1.25 * last_adj_close_price
else:
max_strike = ns_parser.max_sp
plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
plt.axvline(last_adj_close_price, lw=2)
plt.bar(
df_puts["strike"],
-df_puts["volume"] - df_puts["openInterest"],
color="pink",
width=0.45,
)
plt.bar(df_puts["strike"], -df_puts["volume"], color="red", width=0.45)
plt.title(f"{ticker} puts volumes for {exp_date} ")
plt.legend(["Stock Price", "Open Interest", "Volume"])
plt.xlabel("Strike Price")
plt.ylabel("Volume")
plt.xlim([min_strike, max_strike])
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
except Exception as e:
print(e, "\n")
return

View File

@@ -0,0 +1,224 @@
# Portfolio Optimization
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.
* [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
[Mean Variance Optimization](#Mean_Variance_Optimization)
* [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
### add <a name="add"></a>
```text
add [-t ADD_TICKERS]
```
Add/Select tickers for portfolio to be optimized.
* -t : Tickers to be used in the portfolio to optimize
*
### select <a name="select"></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.
<p align="center">
<img width="200" height="90" src="https://user-images.githubusercontent.com/25267873/115084918-73b5a580-9f01-11eb-8438-6dd427727e7b.png">
</p>
* 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.
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.
### maxsharpe <a name="maxhsarpe"></a>
````
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]
````
This portfolio minimizes the total volatility, which also means it has the smallest returns among the efficient frontier.
* -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
![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]
````
This function plots random portfolios based on their risk and returns and shows the efficient frontier.
* -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.
![ef](https://user-images.githubusercontent.com/25267873/114958281-93de5980-9e5a-11eb-967b-d37ddd4f3e73.png)

View File

@@ -0,0 +1,279 @@
""" Portfolio Optimization Helper Functions"""
__docformat__ = "numpy"
import argparse
from typing import List
import math
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
from pypfopt.efficient_frontier import EfficientFrontier
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
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
----------
list_of_stocks: List[str]
List of tickers to get historical data for
period: str
Period to get data from yfinance
Returns
-------
stock_closes: DataFrame
DataFrame containing daily (adjusted) close prices for each stock in list
"""
stock_prices = yf.download(
list_of_stocks, period=period, progress=False, group_by="ticker"
)
stock_closes = pd.DataFrame(index=stock_prices.index)
for stock in list_of_stocks:
stock_closes[stock] = stock_prices[stock]["Adj Close"]
return stock_closes
def prepare_efficient_frontier(stock_prices: pd.DataFrame):
"""Take in a dataframe of prices and return an efficient frontier object
Parameters
----------
stock_prices : DataFrame
DataFrame where indices are DateTime and columns are stocks
Returns
-------
ef: EfficientFrontier
EfficientFrontier object
"""
mu = expected_returns.mean_historical_return(stock_prices)
S = risk_models.sample_cov(stock_prices)
ef = EfficientFrontier(mu, S)
return ef
def display_weights(weights: dict):
"""
Print weights in a nice format
Parameters
----------
weights: dict
weights to display. Keys are stocks. Values are either weights or values if -v specified
"""
if not weights:
return
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[:4] if s.find(".") == 1 else "" + s[:5]
) + " %"
print(pd.DataFrame(weight_df["weight"]).to_string(header=False))
else:
print(weight_df.to_string(header=False))
def my_autopct(x):
"""Function for autopct of plt.pie. This results in values not being printed in the pie if they are 'too small'"""
if x > 4:
return f"{x:.2f} %"
else:
return ""
def pie_chart_weights(weights: dict, title_opt: str):
"""Show a pie chart of holdings
Parameters
----------
weights: dict
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
"""
if not weights:
return
init_stocks = list(weights.keys())
init_sizes = list(weights.values())
stocks = []
sizes = []
for stock, size in zip(init_stocks, init_sizes):
if size > 0:
stocks.append(stock)
sizes.append(size)
total_size = np.sum(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"),
wedgeprops={"linewidth": 3.0, "edgecolor": "white"},
normalize=True,
)
plt.setp(autotexts, color="white", fontweight="bold")
else:
wedges, _, autotexts = plt.pie(
sizes,
labels=stocks,
autopct="",
textprops=dict(color="k"),
wedgeprops={"linewidth": 3.0, "edgecolor": "white"},
normalize=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}")
else:
a.set_text("")
plt.axis("equal")
leg1 = plt.legend(
wedges,
[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")
plt.gca().set_title(title_opt, pad=20)
if gtff.USE_ION:
plt.ion()
plt.tight_layout()
plt.show()
print("")

View File

@@ -0,0 +1,790 @@
""" Portfolio Optimization Functions"""
__docformat__ = "numpy"
import argparse
from typing import List
import matplotlib.pyplot as plt
import numpy as np
import yfinance as yf
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,
check_non_negative,
)
from gamestonk_terminal.portfolio_optimization.optimizer_helper import (
process_stocks,
prepare_efficient_frontier,
pie_chart_weights,
display_weights,
check_valid_property_type,
)
from gamestonk_terminal.config_plot import PLOT_DPI
from gamestonk_terminal import feature_flags as gtff
period_choices = [
"1d",
"5d",
"1mo",
"3mo",
"6mo",
"1y",
"2y",
"5y",
"10y",
"ytd",
"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]):
"""
Equally weighted portfolio, where weight = 1/# of stocks
Parameters
----------
stocks: List[str]
List of tickers to be included in optimization
Returns
-------
weights : dict
Dictionary of weights where keys are the tickers
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="equal",
description="Returns an equally weighted portfolio",
)
parser.add_argument(
"-v",
"--value",
default=1,
type=float,
dest="value",
help="Amount to allocate to portfolio",
)
parser.add_argument(
"--pie",
action="store_true",
dest="pie",
default=False,
help="Display a pie chart for weights",
)
try:
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 = {}
for stock in stocks:
values[stock] = ns_parser.value * round(1 / len(stocks), 5)
if ns_parser.pie:
pie_chart_weights(values, "Equally Weighted Portfolio")
else:
display_weights(values)
print("")
except Exception as e:
print(e)
print("")
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
other_args : List[str]
Command line arguments to be processed with argparse
Returns
-------
weights: dict
Dictionary of weights where keys are the tickers
"""
parser = argparse.ArgumentParser(
add_help=False,
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",
default=1,
type=float,
dest="value",
help="Amount to allocate to portfolio",
)
parser.add_argument(
"--pie",
action="store_true",
dest="pie",
default=False,
help="Display a pie chart for weights",
)
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[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, "Weighted Portfolio based on " + ns_parser.property
)
else:
display_weights(weights)
print("")
except Exception as e:
print(e)
print("")
return
def max_sharpe(stocks: List[str], other_args: List[str]):
"""
Return a portfolio that maximises the Sharpe Ratio
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="maxsharpe", description="Maximise the Sharpe Ratio"
)
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(
"--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",
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",
)
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.min_volatility())
s_title = f"{sp} Weights that minimize volatility"
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 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)
print("")
return
def show_ef(stocks: List[str], other_args: List[str]):
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",
"--period",
default="3mo",
dest="period",
help="period to get yfinance data from",
choices=period_choices,
)
parser.add_argument(
"-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
if len(stocks) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
stock_prices = process_stocks(stocks, ns_parser.period)
mu = expected_returns.mean_historical_return(stock_prices)
S = risk_models.sample_cov(stock_prices)
ef = EfficientFrontier(mu, S)
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
# Generate random portfolios
n_samples = ns_parser.n_port
w = np.random.dirichlet(np.ones(len(mu)), n_samples)
rets = w.dot(mu)
stds = np.sqrt(np.diag(w @ S @ w.T))
sharpes = rets / stds
ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r")
plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True)
# Find the tangency portfolio
ef.max_sharpe()
ret_sharpe, std_sharpe, _ = ef.portfolio_performance()
ax.scatter(std_sharpe, ret_sharpe, marker="*", s=100, c="r", label="Max Sharpe")
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="-")
plt.minorticks_on()
plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
except Exception as e:
print(e)
print("")

View File

@@ -0,0 +1,243 @@
"""Portfolio Optimization Controller Module"""
__docformat__ = "numpy"
import argparse
from typing import List
import matplotlib.pyplot as plt
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
class PortfolioOptimization:
CHOICES = [
"help",
"q",
"quit",
"select",
"add",
"equal",
"mktcap",
"dividend",
"property",
"maxsharpe",
"minvol",
"effret",
"effrisk",
"maxquadutil",
"ef",
"ca",
"yolo",
]
def __init__(self, tickers: List[str] = []):
"""
Construct Portfolio Optimization
"""
self.po_parser = argparse.ArgumentParser(add_help=False, prog="po")
self.po_parser.add_argument("cmd", choices=self.CHOICES)
self.tickers = list(set(tickers))
# These will allow the ca menu to be re-access
self.ca_ticker = None
self.ca_similar = None
@staticmethod
def print_help(tickers: List[str]):
"""Print help"""
print("\nPortfolio Optimization:")
print(" help show this menu again")
print(" q quit this menu, and shows back to main menu")
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("")
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("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("")
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
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
"""
(known_args, other_args) = self.po_parser.parse_known_args(an_input.split())
return getattr(
self, "call_" + known_args.cmd, lambda: "Command not recognized!"
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help(self.tickers)
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_quit(self, _):
"""Process Quit command - quit the program"""
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(self, other_args: List[str]):
"""Process equal command"""
optimizer_view.equal_weight(self.tickers, 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_dividend(self, other_args: List[str]):
"""Process dividend command"""
other_args.insert(0, "dividendYield")
optimizer_view.property_weighting(self.tickers, other_args)
def call_property(self, other_args: List[str]):
"""Process property command"""
optimizer_view.property_weighting(self.tickers, other_args)
def call_maxsharpe(self, other_args: List[str]):
"""Process maxsharpe command"""
optimizer_view.max_sharpe(self.tickers, other_args)
def call_minvol(self, other_args: List[str]):
"""Process minvol command"""
optimizer_view.min_volatility(self.tickers, other_args)
def call_maxquadutil(self, other_args: List[str]):
"""Process maxquadutil command"""
optimizer_view.max_quadratic_utility(self.tickers, other_args)
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 :)
print("DFV YOLO")
print({"GME": 200})
print("")
@staticmethod
def add_stocks(self, other_args: List[str]):
""" Add ticker or Select tickes for portfolio to be optimized """
parser = argparse.ArgumentParser(
add_help=False,
prog="add/select",
description="""Add/Select tickers for portfolio to be optimized.""",
)
parser.add_argument(
"-t",
"--tickers",
dest="add_tickers",
type=lambda s: [str(item).upper() for item in s.split(",")],
default=[],
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")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
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)
except Exception as e:
print(e)
print("")
def menu(tickers: List[str]):
"""Portfolio Optimization Menu"""
if tickers == [""]:
tickers = []
plt.close("all")
po_controller = PortfolioOptimization(tickers)
po_controller.call_help(tickers)
while True:
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in po_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (po)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (po)> ")
try:
plt.close("all")
process_input = po_controller.switch(an_input)
if process_input is not None:
return process_input
except SystemExit:
print("The command selected doesn't exist\n")
continue

View File

@@ -23,7 +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](portfolio_optimization/README.md)
* portfolio optimization for last screened tickers
## view <a name="view"></a>
@@ -180,3 +181,13 @@ Prints list of available signals. [Source: Finviz]
<img width="937" alt="Captura de ecrã 2021-04-05, às 20 25 13" src="https://user-images.githubusercontent.com/25267873/113616495-0ece9580-964d-11eb-97af-4150f928a170.png">
## po <a name="port_opt"></a>
Goes to the portfolio menu with list of passed stocks. In order to pass the stocks, just type the tickers of interest, no commas.
```
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

@@ -14,6 +14,7 @@ from gamestonk_terminal.helper_funcs import (
)
from gamestonk_terminal.screener import finviz_view
from gamestonk_terminal.screener import yahoo_finance_view
from gamestonk_terminal.portfolio_optimization import po_controller
class ScreenerController:
@@ -34,11 +35,13 @@ class ScreenerController:
"performance",
"technical",
"signals",
"po",
]
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",
@@ -69,6 +72,11 @@ class ScreenerController:
print("")
print(" signals view filter signals (e.g. -s top_gainers)")
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]):
@@ -187,36 +195,42 @@ 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 with selected tickers"""
return po_controller.menu(self.screen_tickers)
def menu():
"""Screener 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>
```

177
poetry.lock generated
View File

@@ -379,6 +379,21 @@ category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "cvxpy"
version = "1.1.12"
description = "A domain-specific language for modeling convex optimization problems in Python."
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
ecos = ">=2"
numpy = ">=1.15"
osqp = ">=0.4.1"
scipy = ">=1.1.0"
scs = ">=1.1.6"
[[package]]
name = "cycler"
version = "0.10.0"
@@ -456,6 +471,18 @@ category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "ecos"
version = "2.0.7.post1"
description = "This is the Python package for ECOS: Embedded Cone Solver. See Github page for more information."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
numpy = ">=1.6"
scipy = ">=0.9"
[[package]]
name = "entrypoints"
version = "0.3"
@@ -1491,6 +1518,19 @@ numpy = ">=1.7"
docs = ["sphinx (==1.2.3)", "sphinxcontrib-napoleon", "sphinx-rtd-theme", "numpydoc"]
tests = ["pytest", "pytest-cov", "pytest-pep8"]
[[package]]
name = "osqp"
version = "0.6.2.post0"
description = "OSQP: The Operator Splitting QP Solver"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
numpy = ">=1.7"
qdldl = "*"
scipy = ">=0.13.2"
[[package]]
name = "overrides"
version = "3.1.0"
@@ -1603,7 +1643,7 @@ name = "patsy"
version = "0.5.1"
description = "A Python package for describing statistical models and for building design matrices."
category = "main"
optional = true
optional = false
python-versions = "*"
[package.dependencies]
@@ -1668,7 +1708,7 @@ name = "pmdarima"
version = "1.8.0"
description = "Python's forecast::auto.arima equivalent"
category = "main"
optional = true
optional = false
python-versions = ">=3.6"
[package.dependencies]
@@ -1930,6 +1970,23 @@ category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "pyportfolioopt"
version = "1.4.1"
description = "Financial portfolio optimization in python"
category = "main"
optional = false
python-versions = ">=3.6.1,<4.0.0"
[package.dependencies]
cvxpy = ">=1.1.10,<2.0.0"
numpy = ">=1.12,<2.0"
pandas = ">=0.19"
scipy = ">=1.3,<2.0"
[package.extras]
optionals = ["cvxopt (>=1.2,!=1.2.5.post1,<2.0)", "scikit-learn (>=0.24.1,<0.25.0)", "matplotlib (>=3.2.0,<4.0.0)"]
[[package]]
name = "pyrsistent"
version = "0.14.11"
@@ -2073,6 +2130,18 @@ python-versions = ">=3.6"
cffi = {version = "*", markers = "implementation_name == \"pypy\""}
py = {version = "*", markers = "implementation_name == \"pypy\""}
[[package]]
name = "qdldl"
version = "0.1.5.post0"
description = "QDLDL, a free LDL factorization routine."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
numpy = ">=1.7"
scipy = ">=0.13.2"
[[package]]
name = "qtconsole"
version = "5.0.3"
@@ -2265,6 +2334,18 @@ Cython = {version = "*", markers = "sys_platform == \"darwin\""}
dataclasses = {version = "*", markers = "python_version < \"3.7\""}
pyobjc-framework-Cocoa = {version = "*", markers = "sys_platform == \"darwin\""}
[[package]]
name = "scs"
version = "2.1.2"
description = "scs: splitting conic solver"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
numpy = ">=1.7"
scipy = ">=0.13.2"
[[package]]
name = "seaborn"
version = "0.11.1"
@@ -2360,7 +2441,7 @@ name = "statsmodels"
version = "0.12.2"
description = "Statistical computations and models for Python"
category = "main"
optional = true
optional = false
python-versions = ">=3.6"
[package.dependencies]
@@ -2379,7 +2460,7 @@ name = "tabulate"
version = "0.8.9"
description = "Pretty-print tabular data"
category = "main"
optional = true
optional = false
python-versions = "*"
[package.extras]
@@ -2837,7 +2918,7 @@ prediction = ["fbprophet", "tensorflow", "pmdarima", "transformers", "flair", "t
[metadata]
lock-version = "1.1"
python-versions = "^3.6.8"
content-hash = "144771641ef753b1e937c0db16496c409905f6f692ef81a03d9ba716d9207bb0"
content-hash = "623e5809320b1fb71c692bdd6cdcb9ea3419f3a5b3592e6c755816bd493769bc"
[metadata.files]
absl-py = [
@@ -3063,6 +3144,20 @@ cssselect = [
{file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"},
{file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"},
]
cvxpy = [
{file = "cvxpy-1.1.12-cp36-cp36m-win_amd64.whl", hash = "sha256:86f8e4aa1a79482431b89aa526249d9fda04c74e04a3ec224d1175d281643d71"},
{file = "cvxpy-1.1.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:90b09a7361b9c614166fa673e1fe9321317df6939135330e1621b9a0bf10e3a8"},
{file = "cvxpy-1.1.12-cp37-cp37m-win_amd64.whl", hash = "sha256:084e5292f2a13133a1cd28d68f5af1df33a5119b0f1aec7cecf834eea3e2d11d"},
{file = "cvxpy-1.1.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:575f6e1c70274008961edff97a7494701faed20b40fbd49327010c0e7d186256"},
{file = "cvxpy-1.1.12-cp38-cp38-win_amd64.whl", hash = "sha256:76d04444cb50ad2840bbc72a625ac246b5eeeeb580ee20c71bec057822667738"},
{file = "cvxpy-1.1.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2de5e0e75316839bc71b5df2c2db0131cee7ef06dac566c6c44dc852c091502b"},
{file = "cvxpy-1.1.12-cp39-cp39-win_amd64.whl", hash = "sha256:9fa4d033ab31855aea090e4b358017b36880390a7d0e6e97ecc5226c77eab489"},
{file = "cvxpy-1.1.12-py3.6-win-amd64.egg", hash = "sha256:d2c28e0eea1b6dc70262c9ed8b63c5c1747d85c28b3ec24cbaab07583b3ad729"},
{file = "cvxpy-1.1.12-py3.7-win-amd64.egg", hash = "sha256:44d0e3d60693831ac70bdc9b2e23faf84401347bfcdef4eb5da5ae22ac57f5ff"},
{file = "cvxpy-1.1.12-py3.8-win-amd64.egg", hash = "sha256:c86f8de9a63ccbd45844a4482df8099824688b5329060f089aad1c3beb939190"},
{file = "cvxpy-1.1.12-py3.9-win-amd64.egg", hash = "sha256:08c720706abd94c25903a4db5dd2171232e72d0969b874a5de9a8181f96357d5"},
{file = "cvxpy-1.1.12.tar.gz", hash = "sha256:b499ebfae4fc645e9523621573ffcb1c5b6854a1b5c0ce1d66a8a9a458605807"},
]
cycler = [
{file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"},
{file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"},
@@ -3126,6 +3221,30 @@ detecta = [
{file = "detecta-0.0.5-py3-none-any.whl", hash = "sha256:dbaf1938e5d785386c904034e2553824e2aa31793e967984ca65983aca06d23e"},
{file = "detecta-0.0.5.tar.gz", hash = "sha256:d2ea7d13dfbbc994d6ce385a7f8dc0a85fe675a8a8e712a64ec56e54c40603ed"},
]
ecos = [
{file = "ecos-2.0.7.post1-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:dd9f01e28fe58894fb394931804884122606fb4e2a59d4514b803e9cd11b7d2b"},
{file = "ecos-2.0.7.post1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:db7433051f6072d4821ebc582e9ff853d7d631ed98770550d248eae70b29dd26"},
{file = "ecos-2.0.7.post1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:96ddc1c4e440820bb343c44785da480a64a9c468ec467b997e40f0c7d3236226"},
{file = "ecos-2.0.7.post1-cp27-cp27m-win32.whl", hash = "sha256:574fa26661d192e48551a30e217296875020fdb7eebf9a072a14d68a0b9de03a"},
{file = "ecos-2.0.7.post1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7c871b7a49a5855e7df0d60a06d0e2148a4b183d612f7db5e155988629c3ec21"},
{file = "ecos-2.0.7.post1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:845455f99cd579ee0cdfbcea675b4e4f7674b563e6a54a225977fc0819cda7e0"},
{file = "ecos-2.0.7.post1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:e002df0f4b6777be68c73756e60f3cf76cb5f2f7d36c4f1a482c5538aac1a287"},
{file = "ecos-2.0.7.post1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:feda86ddd191b1ae34d5ea615743894d1baa800f2dbb552cb7c7095a87037831"},
{file = "ecos-2.0.7.post1-cp34-cp34m-win32.whl", hash = "sha256:6b0829b76ba49f6ebf8b9512673a0b702b756704927cbb106043e1b2273dadea"},
{file = "ecos-2.0.7.post1-cp34-cp34m-win_amd64.whl", hash = "sha256:54f7c480029fbfa738ddb8e538388868b2a17c9189a426d924ca0c36e4cbbbd5"},
{file = "ecos-2.0.7.post1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1f811e7244a58a7037474b3dead85c7ffd524cf631c90584cc544e438f114cf9"},
{file = "ecos-2.0.7.post1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:72657189f71dbde01d1df841d2139e04da17071c8d3270919c8501f239d8c8e4"},
{file = "ecos-2.0.7.post1-cp35-cp35m-win32.whl", hash = "sha256:30e7e9c5ad8a012ba1f69aa6827beb99f208323f678a82fcccc30e4ab8090aad"},
{file = "ecos-2.0.7.post1-cp35-cp35m-win_amd64.whl", hash = "sha256:94dd0f82a18550232e4924e6c42730c46d7cdc03c4e2dc889e98ec97c0f24061"},
{file = "ecos-2.0.7.post1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:361758a3568eb5a3a9b37c00a22b36f9548bbd0cd21f1da904a3627285bb4274"},
{file = "ecos-2.0.7.post1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:831acb6bac205025ffe87002d8f425d2764a70db3d9d053a1f7e0e50bc2a18b9"},
{file = "ecos-2.0.7.post1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:912b17f72476aff33b32e92decd7c02ddb929db28227563b3750948783cff6f4"},
{file = "ecos-2.0.7.post1-cp36-cp36m-win32.whl", hash = "sha256:4fdfee011853cd07d494ef58a9299b1f0ccf0268c375109cb2b60727f746ea74"},
{file = "ecos-2.0.7.post1-cp36-cp36m-win_amd64.whl", hash = "sha256:fb64fb29aef26474f807df4f0c198a6d192291edc9faa3bb05e3bc9b1e2b960a"},
{file = "ecos-2.0.7.post1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4b3068ef023c4f39f6c0d6fd73d27d8a8008f8b14e1f4ddd8d0f4876e841b986"},
{file = "ecos-2.0.7.post1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4f775caf828597d094cddae54f7ed12b4a5aa760f535a92ccabec47741e4d61c"},
{file = "ecos-2.0.7.post1.tar.gz", hash = "sha256:83e90f42b3f32e2a93f255c3cfad2da78dbd859119e93844c45d2fca20bdc758"},
]
entrypoints = [
{file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
{file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
@@ -3810,6 +3929,29 @@ opt-einsum = [
{file = "opt_einsum-3.3.0-py3-none-any.whl", hash = "sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147"},
{file = "opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549"},
]
osqp = [
{file = "osqp-0.6.2.post0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:21047e9ceb4c2224ba15dd95f5125e2dcabd178aa93d8efa407e67216ea4fe62"},
{file = "osqp-0.6.2.post0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:598a7bb1534a0ab69bcaf22d6e6c4ec042a41184f007c01b7fa863b528c6b61d"},
{file = "osqp-0.6.2.post0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:6b745ba55add8be63eaee059858b7c0cf59925074fcc8355c17c3863fad01a95"},
{file = "osqp-0.6.2.post0-cp35-cp35m-win_amd64.whl", hash = "sha256:1104dd56d6bfe5ac5e5e076e1cf9e2be76932cadae4e0cfbdf495f3f43e4f8fa"},
{file = "osqp-0.6.2.post0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6f89feaf7c30dfb5031b370e8c45aed23a60d6471606bad9b7d23506ddf807e6"},
{file = "osqp-0.6.2.post0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:198b66cbe9326ef181f7598856aae20dcc5b7824b119cc119b412206e858bc28"},
{file = "osqp-0.6.2.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9aaecd3ed00a99d063e5bd45ebce965d1ebc8f4754d6c05ab5d31c3577a68513"},
{file = "osqp-0.6.2.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:2d150d880dc5bee015aadac6cff32ab0c2721cd4bb8a3f5b1f0cec819fe70b39"},
{file = "osqp-0.6.2.post0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:20cd6a352694cc182bb79a8e19e4e03b7970ecb4b95d6abf86e3c578a45783a9"},
{file = "osqp-0.6.2.post0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:96e42524dd156f62ac8e35d71831588acb2919b5ba27e0e695c757539708dfa5"},
{file = "osqp-0.6.2.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:d6dd2b70ffcee6089758518620087a5909a10621a59493be8ce0ef2a74502a27"},
{file = "osqp-0.6.2.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:7c9f7e661fe2da984eab6533a56b5366f10a7360a28b8dd18c222fcf6655e247"},
{file = "osqp-0.6.2.post0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b102c26b2017b039032797a388548dcfe11dbd3d06b3294721fa65976fe1065e"},
{file = "osqp-0.6.2.post0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a835746179c81944e51a6a2e0efb0aca66402be351af912bfd682527f15a35a7"},
{file = "osqp-0.6.2.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:57f5026b91bb8f39059576c5ee50c0a1c2705d775a776e2e24be2aa47e63d8b0"},
{file = "osqp-0.6.2.post0-cp38-cp38-win_amd64.whl", hash = "sha256:fb0d77e634362fe59ab2bedba3dde40d5357922e9719bcb4d16a99766cefd5a2"},
{file = "osqp-0.6.2.post0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e21463d97bcab58350a41a975e9e5e4a685087b6357899a58813d2238795315"},
{file = "osqp-0.6.2.post0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:02fdbabfffd72a8118328d117a83998800ca015182053fced9d5e8be6ffd702a"},
{file = "osqp-0.6.2.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:b238b9049a29f6955f08567b285b0734dd578b529a3780d617854ffd91e5e4ed"},
{file = "osqp-0.6.2.post0-cp39-cp39-win_amd64.whl", hash = "sha256:2c6cb14fd1365004d0e7a01f6101e16414a0348db736ee45aac8865fac165e57"},
{file = "osqp-0.6.2.post0.tar.gz", hash = "sha256:5f0695f26a3bef0fae91254bc283fab790dcca0064bfe0f425167f9c9e8b4cbc"},
]
overrides = [
{file = "overrides-3.1.0.tar.gz", hash = "sha256:30f761124579e59884b018758c4d7794914ef02a6c038621123fec49ea7599c6"},
]
@@ -4069,6 +4211,10 @@ pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
pyportfolioopt = [
{file = "PyPortfolioOpt-1.4.1-py3-none-any.whl", hash = "sha256:73f0b4ac2a5d879879a7a91af038b4d7ff82e9998fb2cad7c249302294e8c09e"},
{file = "PyPortfolioOpt-1.4.1.tar.gz", hash = "sha256:e6c8e983db92cd13caf41d87fb4dcd945d37053f28b75dd19b5be699b6b1d19e"},
]
pyrsistent = [
{file = "pyrsistent-0.14.11.tar.gz", hash = "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"},
]
@@ -4212,6 +4358,24 @@ pyzmq = [
{file = "pyzmq-22.0.3-pp37-pypy37_pp73-win32.whl", hash = "sha256:279cc9b51db48bec2db146f38e336049ac5a59e5f12fb3a8ad864e238c1c62e3"},
{file = "pyzmq-22.0.3.tar.gz", hash = "sha256:f7f63ce127980d40f3e6a5fdb87abf17ce1a7c2bd8bf2c7560e1bbce8ab1f92d"},
]
qdldl = [
{file = "qdldl-0.1.5.post0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:787d59b4301608e96bdf32ab3a572d9f41b3ea08581774826720986e18da261e"},
{file = "qdldl-0.1.5.post0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:223dd49f4aa625c727c3053e9f62814fab2f929e0575b34dd1f38e66b536a849"},
{file = "qdldl-0.1.5.post0-cp35-cp35m-win_amd64.whl", hash = "sha256:cd0090d5512a8e386534a755f9ebd8be82087d401746cebbfb9f9faa21073c39"},
{file = "qdldl-0.1.5.post0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3e5e2ee6b45dd655120ca8d6645331c800c494ce765bc0674714ba86e1ebec7d"},
{file = "qdldl-0.1.5.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:a2ab0d4618d5516101fc0ce99c873df61a085c3d7cb4d8347d5eca818795e48f"},
{file = "qdldl-0.1.5.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:ac7a2891628554de6260132fa090c5b00cf32ae083ece4991a43b887ec6f2114"},
{file = "qdldl-0.1.5.post0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:817a226b2450e56c50f61a92b727c69f50a4d043876e5f1bcdff096c5a82bc37"},
{file = "qdldl-0.1.5.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:c10f91dda6a422a6d879668fd293132c8939431fffe64abd9700dce170a3aec4"},
{file = "qdldl-0.1.5.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:e70c57ea65d0bb09708bc9ecafd4fb4b797e64af4da475404e6bcf5a2cc92596"},
{file = "qdldl-0.1.5.post0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:64f531ef2a12cc77be85045cdffdb2fe339244c73fc07aebe49977541bfc7e79"},
{file = "qdldl-0.1.5.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:cff34b89258a3c9b1b398f1c8eeb480a9c4ae2ba14c4ffc8c78cdb123dab11c6"},
{file = "qdldl-0.1.5.post0-cp38-cp38-win_amd64.whl", hash = "sha256:3a7ec584aaddff7036d22013c911132572f3d59c65ec6bf7e32beb01765accc4"},
{file = "qdldl-0.1.5.post0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a7c086ae21a2b2e43b31ac6a5024c79dadf16e7c3152eda9e3263cefe1675e26"},
{file = "qdldl-0.1.5.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:2c09f4b1a1c6f3a0579af004443417e084491e7c844ff9fb73170bb5d43f70b5"},
{file = "qdldl-0.1.5.post0-cp39-cp39-win_amd64.whl", hash = "sha256:640cab781f87c2f1c04ada65615b3d1d136d319c4595176f37e2e3c99149108f"},
{file = "qdldl-0.1.5.post0.tar.gz", hash = "sha256:c392c7427651d8b226423c7aba4a0f2338a1f38a4bbdabac6bc4afd8bc934f06"},
]
qtconsole = [
{file = "qtconsole-5.0.3-py3-none-any.whl", hash = "sha256:4a38053993ca2da058f76f8d75b3d8906efbf9183de516f92f222ac8e37d9614"},
{file = "qtconsole-5.0.3.tar.gz", hash = "sha256:c091a35607d2a2432e004c4a112d241ce908086570cf68594176dd52ccaa212d"},
@@ -4417,6 +4581,9 @@ scipy = [
screeninfo = [
{file = "screeninfo-0.6.7.tar.gz", hash = "sha256:1c4bac1ca329da3f68cbc4d2fbc92256aa9bb8ff8583ee3e14f91f0a7baa69cb"},
]
scs = [
{file = "scs-2.1.2.tar.gz", hash = "sha256:667ed6019bb4e2f925bd9291161d2c61cc0077443094437b703ea905333fd585"},
]
seaborn = [
{file = "seaborn-0.11.1-py3-none-any.whl", hash = "sha256:4e1cce9489449a1c6ff3c567f2113cdb41122f727e27a984950d004a88ef3c5c"},
{file = "seaborn-0.11.1.tar.gz", hash = "sha256:44e78eaed937c5a87fc7a892c329a7cc091060b67ebd1d0d306b446a74ba01ad"},

View File

@@ -30,7 +30,7 @@ papermill = "^2.3.2"
torch = {version = "1.7.1", optional = true}
transformers = {version = "3.5.1", optional = true}
fbprophet = {version = "0.6", optional = true}
pmdarima = {version = "1.8.0", optional = true}
pmdarima = {version = "1.8.0", optional = false}
tensorflow = {version = "2.4.1", optional = true}
flair = {version = "0.7", optional = true}
holidays = "^0.10"
@@ -54,6 +54,9 @@ detecta = "^0.0.5"
tradingview-ta = "^3.2.3"
finvizfinance = "^0.9.3"
Pillow = "^8.2.0"
pyportfolioopt = "^1.4.1"
statsmodels = "^0.12.2"
tabulate = "^0.8.9"
[tool.poetry.dev-dependencies]
pytest = "^6.2.2"

View File

@@ -133,6 +133,7 @@ pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or py
pygments==2.8.1; python_version >= "3.6"
pymeeus==0.5.9; python_version >= "3.5"
pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6")
pyportfolioopt==1.4.1
pyrsistent==0.17.3; python_version >= "3.6"
pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pystan==2.19.1.1

View File

@@ -90,6 +90,7 @@ pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and pyt
pycoingecko==1.4.0
pymeeus==0.4.3; python_version >= "3.5"
pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6"
pyportfolioopt==1.4.1
pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pystan==2.18
python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.5" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0")

View File

@@ -92,6 +92,7 @@ pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and pyt
pycoingecko==1.4.0
pymeeus==0.4.3; python_version >= "3.5"
pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6"
pyportfolioopt==1.4.1
pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pystan==2.19.1.1
python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.5" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0")

View File

@@ -92,6 +92,7 @@ pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or py
pygments==2.8.1; python_version >= "3.6"
pymeeus==0.5.9; python_version >= "3.5"
pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
pyportfolioopt==1.4.1
pyrsistent==0.17.3; python_version >= "3.6"
pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0")

View File

@@ -31,6 +31,7 @@ from gamestonk_terminal.residuals_analysis import ra_controller
from gamestonk_terminal.portfolio import port_controller
from gamestonk_terminal.cryptocurrency import crypto_controller
from gamestonk_terminal.screener import screener_controller
from gamestonk_terminal.portfolio_optimization import po_controller
# import warnings
# warnings.simplefilter("always")
@@ -88,7 +89,9 @@ def main():
"pa",
"crypto",
"ra",
"po",
]
menu_parser.add_argument("opt", choices=choices)
completer = NestedCompleter.from_nested_dict({c: None for c in choices})
@@ -193,26 +196,51 @@ def main():
b_quit = mill.papermill_menu()
elif ns_known_args.opt == "ba":
b_quit = ba_controller.menu(s_ticker, s_start)
b_quit = ba_controller.menu(
s_ticker.split(".")[0] if "." in s_ticker else s_ticker, s_start
)
elif ns_known_args.opt == "res":
b_quit = rm.res_menu(s_ticker, s_start, s_interval)
b_quit = rm.res_menu(
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval,
)
elif ns_known_args.opt == "ca":
b_quit = ca_controller.menu(df_stock, s_ticker, s_start, s_interval)
elif ns_known_args.opt == "fa":
b_quit = fa_controller.menu(s_ticker, s_start, s_interval)
b_quit = fa_controller.menu(
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval,
)
elif ns_known_args.opt == "ta":
b_quit = ta_controller.menu(df_stock, s_ticker, s_start, s_interval)
b_quit = ta_controller.menu(
df_stock,
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval,
)
elif ns_known_args.opt == "dd":
b_quit = dd_controller.menu(df_stock, s_ticker, s_start, s_interval)
b_quit = dd_controller.menu(
df_stock,
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval,
)
elif ns_known_args.opt == "eda":
if s_interval == "1440min":
b_quit = eda_controller.menu(df_stock, s_ticker, s_start, s_interval)
b_quit = eda_controller.menu(
df_stock,
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval,
)
else:
df_stock = yf.download(s_ticker, start=s_start, progress=False)
df_stock = df_stock.rename(
@@ -228,10 +256,17 @@ def main():
df_stock.index.name = "date"
s_interval = "1440min"
b_quit = eda_controller.menu(df_stock, s_ticker, s_start, s_interval)
b_quit = eda_controller.menu(
df_stock,
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval,
)
elif ns_known_args.opt == "op":
b_quit = op_controller.menu(s_ticker)
b_quit = op_controller.menu(
s_ticker, df_stock["5. adjusted close"].values[-1]
)
elif ns_known_args.opt == "fred":
b_quit = fred_controller.menu()
@@ -242,6 +277,9 @@ def main():
elif ns_known_args.opt == "crypto":
b_quit = crypto_controller.menu()
elif ns_known_args.opt == "po":
b_quit = po_controller.menu([s_ticker])
elif ns_known_args.opt == "pred":
if not gtff.ENABLE_PREDICT:
@@ -265,7 +303,12 @@ def main():
continue
if s_interval == "1440min":
b_quit = pred_controller.menu(df_stock, s_ticker, s_start, s_interval)
b_quit = pred_controller.menu(
df_stock,
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval,
)
# If stock data is intradaily, we need to get data again as prediction
# techniques work on daily adjusted data. By default we load data from
# Alpha Vantage because the historical data loaded gives a larger
@@ -283,7 +326,10 @@ def main():
df_stock_pred = df_stock_pred.sort_index(ascending=True)
df_stock_pred = df_stock_pred[s_start:]
b_quit = pred_controller.menu(
df_stock_pred, s_ticker, s_start, interval="1440min"
df_stock_pred,
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval="1440min",
)
except Exception as e:
print(e)
@@ -291,7 +337,12 @@ def main():
return
elif ns_known_args.opt == "ra":
b_quit = ra_controller.menu(df_stock, s_ticker, s_start, s_interval)
b_quit = ra_controller.menu(
df_stock,
s_ticker.split(".")[0] if "." in s_ticker else s_ticker,
s_start,
s_interval,
)
elif ns_known_args.opt == "scr":
b_quit = screener_controller.menu()