Merge pull request #329 from jmaslek/add_port_opt

Portfolio optimization
This commit is contained in:
jmaslek
2021-04-14 19:11:14 -04:00
committed by GitHub
18 changed files with 1169 additions and 7 deletions

View File

@@ -142,8 +142,13 @@
* [ ] 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)
**NEXT**
* [ ] Decide what features should be present
* [ ] Allow for more custom optimization constrains
---
## FRED

View File

@@ -241,6 +241,21 @@ Command|Description|Brokers
`hold` | view net holdings across all logins
 
## Portfolio Optimization [»](portfolio_optimization/README.md)
command|desciription
------|------
`equal_weight`|Returns equal weights for all selected stocks
`mkt_cap`| Returns weights that are weighted by market cap
`div_yield`|Returns weights that are weighted by dividend yield
`max_sharpe`| Returns portfolio of Efficient Frontier with maximum sharpe ratio
`min_vol`| Returns portfolio on Efficient Frontier with minimum volatility
`eff_risk`| Returns portfolio on Efficient Frontier that maximizes returns at a given risk
`eff_ret`| Returns portfolio on Efficient Frontier that minimizes risk at a given return level
`show_eff`|Plots random portfolios and shows the Efficient Frontier
 
## Cryptocurrency [»](cryptocurrency/README.md)
Command|Description

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__(
@@ -108,6 +110,8 @@ class ComparisonAnalysisController:
print(" performance brief performance comparison")
print(" technical brief technical comparison")
print("")
print(" > po portfolio optimization for selected tickers")
print("")
return
@staticmethod
@@ -315,6 +319,10 @@ class ComparisonAnalysisController:
"""Process technical command"""
finviz_compare_view.screener(other_args, "technical", self.ticker, self.similar)
def call_po(self, _):
"""Open Portfolio Optimization menu with ticker and similar"""
return po_controller.menu([self.ticker] + self.similar)
def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str):
"""Comparison Analysis Menu"""

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

@@ -0,0 +1,169 @@
# Portfolio Optimization
This menu aims to discover optimal portfolios for selected stocks.
Currently we support two methods:
* [Property Weighting](#weighting)
* Equal Weights
* Market Cap Weighting
* Divident Yield Weighting
* [Mean-Variance-Optimization](#eff_front)
* Max sharpe ratio
* Minimum volatility
* Maximum returns at given risk level
* Minimum risk level at a given return
## Procedure
There are three ways to load stocks to be analyzed.
* add
* select
* from ca menu
###add
Adds selected tickers to the menu to be considered
````
usage: add ticker1,ticker2,ticker3,...
````
###select
Clears current list and loads selected ticker
````
usage: select ticker1,ticker2,ticker3,...
````
### ca menu
From the ca menu, the loaded ticker and the selected similar tickers can be loaded by entering the `> po` menu.
From this `po` menu, you can return to `ca` using `ca`, which will keep your loaded stock, but reset the selected stocks from that menu.
Once your stocks are listed, you can select one of the options.
## Property weighted <a name="weighting"></a>
* equal weights
* market cap weighted
* dividend yield weighted
### equal weights
Returns an equally weighted portfolio where the weights are 1/number of stocks.
````
equal_weight [-v --value VALUE] [--pie]
````
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
### market cap weighted
Returns portfolio values that are weighted by their relative Market Cap.
````
mkt_cap [-v --value VALUE] [--pie]
````
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
###dividend yield weighted
Returns portfolio values that are weighted by relative Dividend Yield.
````
div_yield [-v --value VALUE] [--pie]
````
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
## Mean Variance Optimization<a name="eff_front"></a>
These approaches are based off of the efficient frontier approach, which is meant to solve the following optimization problem.
<img src="https://latex.codecogs.com/svg.image?w^TS&space;w" title="w^TS w" />
With constraints:
<img src="https://latex.codecogs.com/svg.image?w^TR&space;>&space;R^*" title="w^TR > R^*" />
\
<img src="https://latex.codecogs.com/svg.image?w_1&plus;w_2&plus;...w_n&space;=&space;1" title="w_1+w_2+...w_n = 1" />
Where S is the covariance matrix between stocks and R is the expected returns. The condition that all weights add up to 1
just implies that you want to have a net long portfolio (with no margin).
A long-short portfolio can have negative weights and usually wants to have everything add up to 0 for a market-neutral strategy.
Currently, we do not allow for changing risk models or adding constraints. If there is something specific, please submit a feature request, or if you can
write it, feel free to add a PR!
All of our current implementations use the [PyPortFolioOpt](#https://pyportfolioopt.readthedocs.io/en/latest/index.html) package.
### max_sharpe
The sharpe ratio is defined as
(Mean Returns - Risk Free Rate)/(Standard Deviation of Returns)
The usage is:
````
max_sharpe [-p PERIOD] [-v --value VALUE] [--pie]
````
* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo.
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
### min_vol
This portfolio minimizes the total volatility, which also means it has the smallest returns among the efficient frontier.
The usage is:
````
min_vol [-p PERIOD] [-v --value VALUE] [--pie]
````
* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo.
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
### eff_risk
This portfolio maximizes the returns at a given risk tolerance
The usage is:
````
eff_risk [-p PERIOD] [-r --risk RISK_LEVEL] [-v --value VALUE] [--pie]
````
* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo.
* -r/--risk Risk tolerance. Default is 0.1 (10%)
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
### eff_ret
This portfolio minimizes the risk at a given return level
The usage is:
````
eff_ret [-p PERIOD] [-r --return] [-v --value VALUE] [--pie]
````
* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo.
* -r/--return. Desired return. Default is 0.1 (10%)
* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights.
* --pie Flag that displays a pie chart of the allocations.
### show_eff
This function plots random portfolios based on their risk and returns and shows the efficient frontier.
The usage is:
````
show_eff [-p PERIOD] [-n N_PORTFOLIOS]
````
* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo.
* -n Number of portfolios to simulate.
##Sample Usage
In this example, we generate weights for a list of 6 stocks using the eff_ret command. This optimization looks to maximize returns
at a given risk level. We start by adding the stocks we want to analyze:
````
select aapl,amzn,msft,f,gm,ge
````
Which shows:
````
Current Tickers: GE, GM, AMZN, AAPL, F, MSFT
````
To perform the optimization, we will set a target return of 25% (.25). Given how stocks performed during COVID,
there is a lot of volatility, so many optimizations may not get low volatility. The module also returns annualized volatility,
so the number is your portfolio volatility * `sqrt(252`. This optimization will be (including a pie chart!). Note we could also supply a different
time period, which changes the expected returns and historical volatility, which changes the optimization. We could also specify a dollar
amount that you wish to allocate using the `-v` flag.
````
eff_ret -r .25 --pie
````
The console will show (numbers will vary based on when this is done).
![console](https://user-images.githubusercontent.com/18151143/114740311-bd429c80-9d17-11eb-90e2-97430781431a.png)
And the pie chart:
![yummypie](https://user-images.githubusercontent.com/18151143/114740289-b9167f00-9d17-11eb-9c29-470785b21d09.png)
Note that since `AAPL` had zero allocation, it was omitted from the chart.

View File

@@ -0,0 +1,226 @@
""" 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, parse_known_args_and_warn
title_maps = {
"max_sharpe": "Maximum Sharpe Portfolio",
"min_volatility": "Minimum Volatility Portfolio",
"eff_risk": "Maximum Return Portfolio at Risk = {:1f} %",
"eff_ret": "Minimum Volatility Portfolio at Target Return = {:.1f} %",
"equal": "Equally Weighted Portfolio",
"marketCap": "MarketCap Weighted Portfolio",
"dividendYield": "Dividend Yield Weighted Portfolio",
}
def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame:
"""
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[:6]
) + " %"
print(pd.DataFrame(weight_df["weight"]))
else:
print(weight_df)
def my_autopct(x):
"""Function for autopct of plt.pie. This results in values not being printed in the pie if they are 'too small'"""
if x > 4:
return f"{x:.2f} %"
else:
return ""
def pie_chart_weights(weights: dict, optimizer: str, value: float):
"""
Show a pie chart of holdings
Parameters
----------
weights: dict
weights to display. Keys are stocks. Values are either weights or values if -v specified
optimzer: str
Optimization technique used for title
"""
plt.close("all")
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)
leg_labels = [
f"{str(a)}: {str(round(100*b/total_size,3))[:4]}%"
for a, b in zip(stocks, sizes)
]
plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
if math.isclose(sum(sizes), 1, rel_tol=0.1):
wedges, _, autotexts = plt.pie(
sizes,
labels=stocks,
autopct=my_autopct,
textprops=dict(color="k"),
explode=[s / (5 * total_size) for s in sizes],
normalize=True,
shadow=True,
)
else:
wedges, _, autotexts = plt.pie(
sizes,
labels=stocks,
autopct="",
textprops=dict(color="k"),
explode=[s / (5 * total_size) for s in sizes],
normalize=True,
shadow=True,
)
for i, a in enumerate(autotexts):
if sizes[i] / total_size > 0.05:
a.set_text(f"{sizes[i]:.2f}")
else:
a.set_text("")
plt.axis("equal")
plt.legend(
wedges,
leg_labels,
title="Stocks",
loc="center left",
bbox_to_anchor=(0.85, 0, 0.5, 1),
)
plt.setp(autotexts, size=8, weight="bold")
if optimizer in ["eff_ret", "eff_risk"]:
plt.title(title_maps[optimizer].format(100 * value))
else:
plt.title(title_maps[optimizer])
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
def parse_from_port_type(
parser_in: argparse.ArgumentParser, port_type: str, other_args: List[str]
):
"""
Parameters
----------
parser_in: ArgumentParser
parser to get data from
port_type: str
Type of optimization that will be done. One of ["max_sharpe","min_vol", "eff_risk", "eff_ret"]
other_args: List[str]
Arguments passed to function
Returns
-------
ns_parser:
Parsed arguments
"""
if port_type in ["max_sharpe", "min_volatility"]:
ns_parser = parse_known_args_and_warn(parser_in, other_args)
if not ns_parser:
return None
return ns_parser
elif port_type == "eff_risk":
parser_in.add_argument(
"-r", "--risk", type=float, dest="risk_level", default=0.1
)
ns_parser = parse_known_args_and_warn(parser_in, other_args)
if not ns_parser:
return None
return ns_parser
elif port_type == "eff_ret":
parser_in.add_argument(
"-r", "--return", type=float, dest="target_return", default=0.1
)
ns_parser = parse_known_args_and_warn(parser_in, other_args)
if not ns_parser:
return None
return ns_parser
else:
print("Incorrect portfolio optimizer type\n")
return None

View File

@@ -0,0 +1,337 @@
""" 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
from gamestonk_terminal.portfolio_optimization.optimizer_helper import (
process_stocks,
prepare_efficient_frontier,
pie_chart_weights,
display_weights,
parse_from_port_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",
]
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_weight",
description="Return equally weighted portfolio holdings",
)
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
values = {}
n_stocks = len(stocks)
for stock in stocks:
values[stock] = ns_parser.value * round(1 / n_stocks, 5)
if ns_parser.pie:
pie_chart_weights(values, "equal", 0)
if n_stocks >= 1:
print("Equal Weight Portfolio: ")
display_weights(values)
print("")
except Exception as e:
print(e)
print("")
def property_weighting(stocks: List[str], property_type: str, other_args: List[str]):
"""
Property weighted portfolio where each weight is the relative fraction.
Parameters
----------
stocks: List[str]
List of tickers to be included in optimization
property_type: str
Property to weight by. Can be anything in yfinance.Ticker().info. Examples:
"marketCap", "dividendYield", etc
Returns
-------
weights: dict
Dictionary of weights where keys are the tickers
"""
parser = argparse.ArgumentParser(
add_help=False,
prog="market_cap_weighted",
description="Return portfolio weights/values that are weighted by marketcap",
)
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",
)
weights = {}
prop = {}
prop_sum = 0
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
for stock in stocks:
stock_prop = yf.Ticker(stock).info[property_type]
if stock_prop is None:
stock_prop = 0
prop[stock] = stock_prop
prop_sum += stock_prop
for k, v in prop.items():
weights[k] = round(v / prop_sum, 5) * ns_parser.value
if ns_parser.pie:
pie_chart_weights(weights, property_type, 0)
if property_type == "marketCap":
print("Market Cap Weighted Portfolio: ")
elif property_type == "dividendYield":
print("Dividend Yield Weighted Portfolio: ")
if len(stocks) >= 1:
display_weights(weights)
print("")
except Exception as e:
print(e)
print("")
return
def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]):
"""
Return a portfolio based on condition in port_type Defaults to 3m of historical data
Parameters
----------
stocks: List[str]
List of the stocks to be included in the weights
port_type: str
Method to be used on ef object (example: max_sharpe, min_volatility)
Returns
-------
weights: dict
Dictionary of weights where keys are the tickers.
"""
parser = argparse.ArgumentParser(add_help=False, prog=port_type)
parser.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_from_port_type(parser, port_type, other_args)
if not ns_parser:
return
if len(stocks) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
period = ns_parser.period
stock_prices = process_stocks(stocks, period)
ef = prepare_efficient_frontier(stock_prices)
if port_type == "max_sharpe":
ef_sharpe = dict(ef.max_sharpe())
weights = {
key: ns_parser.value * round(value, 5)
for key, value in ef_sharpe.items()
}
val = 0
print("Weights that maximize Sharpe Ratio:")
elif port_type == "min_volatility":
ef_min_vol = dict(ef.min_volatility())
weights = {
key: ns_parser.value * round(value, 5)
for key, value in ef_min_vol.items()
}
val = 0
print("Weights that minimize volatility")
elif port_type == "eff_risk":
ef_eff_risk = dict(ef.efficient_risk(ns_parser.risk_level))
weights = {
key: ns_parser.value * round(value, 5)
for key, value in ef_eff_risk.items()
}
val = ns_parser.risk_level
print(f"Weights for maximizing returns at risk = {100*val:.1f} %")
elif port_type == "eff_ret":
ef_eff_risk = dict(ef.efficient_return(ns_parser.target_return))
weights = {
key: ns_parser.value * round(value, 5)
for key, value in ef_eff_risk.items()
}
val = ns_parser.target_return
print(f"Weights for minimizing risk at target return = {100*val:.1f} %")
else:
raise ValueError("EF Method not found")
if ns_parser.pie:
pie_chart_weights(weights, port_type, val)
print("")
ef.portfolio_performance(verbose=True)
print("")
display_weights(weights)
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")
parser.add_argument(
"-p",
"--period",
default="3mo",
dest="period",
help="period to get yfinance data from",
choices=period_choices,
)
parser.add_argument(
"-n", default=300, dest="n_port", help="number of portfolios to simulate"
)
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
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("Efficient Frontier")
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,206 @@
"""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 as po_api
class PortfolioOptimization:
CHOICES = [
"help",
"q",
"quit",
"select",
"add",
"equal_weight",
"mkt_cap",
"div_yield",
"max_sharpe",
"min_vol",
"eff_risk",
"eff_ret",
"show_ef",
"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("")
print(" equal_weight equally weighted portfolio")
print(" mkt_cap marketcap weighted portfolio")
print(" div_yield dividend weighted portfolio")
print(" max_sharpe portfolio with maximum sharpe ratio")
print(" min_vol portfolio with minimum volatility")
print(" eff_risk portfolio that maximizes returns at given risk")
print(" eff_ret portfolio that minimizes risk at given return")
print(" show_ef show the efficient frontier")
print("")
plt.close("all")
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]):
self.add_stocks(self, other_args)
def call_select(self, other_args: List[str]):
self.tickers = []
self.add_stocks(self, other_args)
def call_equal_weight(self, other_args: List[str]):
po_api.equal_weight(self.tickers, other_args)
def call_mkt_cap(self, other_args: List[str]):
po_api.property_weighting(self.tickers, "marketCap", other_args)
def call_div_yield(self, other_args: List[str]):
po_api.property_weighting(self.tickers, "dividendYield", other_args)
def call_max_sharpe(self, other_args: List[str]):
po_api.ef_portfolio(self.tickers, "max_sharpe", other_args)
def call_min_vol(self, other_args: List[str]):
po_api.ef_portfolio(self.tickers, "min_volatility", other_args)
def call_eff_risk(self, other_args: List[str]):
po_api.ef_portfolio(self.tickers, "eff_risk", other_args)
def call_eff_ret(self, other_args: List[str]):
po_api.ef_portfolio(self.tickers, "eff_ret", other_args)
def call_show_ef(self, other_args):
po_api.show_ef(self.tickers, other_args)
print("")
def call_yolo(self, _):
# Easter egg :)
print("DFV YOLO")
print({"GME": 200})
print("")
@staticmethod
def add_stocks(self, other_args: List[str]):
""" Add ticker to current list for optimization"""
parser = argparse.ArgumentParser(
add_help=False,
prog="add",
description="""Add tickers for optimizing.""",
)
parser.add_argument(
"-t",
"--tickers",
dest="add_tickers",
type=lambda s: [str(item).upper() for item in s.split(",")],
default=[],
help="add tickers to optimzation.",
)
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)
self.tickers = list(tickers)
print(
f"\nCurrent Tickers: {('None', ', '.join(self.tickers))[bool(self.tickers)]}"
)
print("")
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:
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](#port_opt)
* go to the portfolio optimization menu
## 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

@@ -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,6 +35,7 @@ class ScreenerController:
"performance",
"technical",
"signals",
"po",
]
def __init__(self):
@@ -69,6 +71,8 @@ class ScreenerController:
print("")
print(" signals view filter signals (e.g. -s top_gainers)")
print("")
print(" > po go to the portfolio optimization menu")
print("")
@staticmethod
def view_available_presets(other_args: List[str]):
@@ -217,6 +221,10 @@ class ScreenerController:
"""Process signals command"""
finviz_view.view_signals(other_args)
def call_po(self, other_args: List[str]):
"""Call the portfolio optimization menu"""
return po_controller.menu(other_args)
def menu():
"""Screener Menu"""

173
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"
@@ -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"
@@ -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 = "5b3abe79834fc75092d1d9e8e3a809252f56eddc170fb61a93c36ffef9328b73"
[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"},
@@ -4359,7 +4523,6 @@ scikit-learn = [
{file = "scikit_learn-0.24.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c658432d8a20e95398f6bb95ff9731ce9dfa343fdf21eea7ec6a7edfacd4b4d9"},
{file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9dfa564ef27e8e674aa1cc74378416d580ac4ede1136c13dd555a87996e13422"},
{file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:9c6097b6a9b2bafc5e0f31f659e6ab5e131383209c30c9e978c5b8abdac5ed2a"},
{file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5580eba7345a4d3b097be2f067cc71a306c44bab19e8717a30361f279c929bea"},
{file = "scikit_learn-0.24.1-cp36-cp36m-win32.whl", hash = "sha256:7b04691eb2f41d2c68dbda8d1bd3cb4ef421bdc43aaa56aeb6c762224552dfb6"},
{file = "scikit_learn-0.24.1-cp36-cp36m-win_amd64.whl", hash = "sha256:1adf483e91007a87171d7ce58c34b058eb5dab01b5fee6052f15841778a8ecd8"},
{file = "scikit_learn-0.24.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:ddb52d088889f5596bc4d1de981f2eca106b58243b6679e4782f3ba5096fd645"},
@@ -4367,7 +4530,6 @@ scikit-learn = [
{file = "scikit_learn-0.24.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0567a2d29ad08af98653300c623bd8477b448fe66ced7198bef4ed195925f082"},
{file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:99349d77f54e11f962d608d94dfda08f0c9e5720d97132233ebdf35be2858b2d"},
{file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:83b21ff053b1ff1c018a2d24db6dd3ea339b1acfbaa4d9c881731f43748d8b3b"},
{file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:abe835a851610f87201819cb315f8d554e1a3e8128912783a31e87264ba5ffb7"},
{file = "scikit_learn-0.24.1-cp37-cp37m-win32.whl", hash = "sha256:c3deb3b19dd9806acf00cf0d400e84562c227723013c33abefbbc3cf906596e9"},
{file = "scikit_learn-0.24.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d54dbaadeb1425b7d6a66bf44bee2bb2b899fe3e8850b8e94cfb9c904dcb46d0"},
{file = "scikit_learn-0.24.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:3c4f07f47c04e81b134424d53c3f5e16dfd7f494e44fd7584ba9ce9de2c5e6c1"},
@@ -4375,7 +4537,6 @@ scikit-learn = [
{file = "scikit_learn-0.24.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4ddd2b6f7449a5d539ff754fa92d75da22de261fd8fdcfb3596799fadf255101"},
{file = "scikit_learn-0.24.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:826b92bf45b8ad80444814e5f4ac032156dd481e48d7da33d611f8fe96d5f08b"},
{file = "scikit_learn-0.24.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:259ec35201e82e2db1ae2496f229e63f46d7f1695ae68eef9350b00dc74ba52f"},
{file = "scikit_learn-0.24.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:9599a3f3bf33f73fed0fe06d1dfa4e6081365a58c1c807acb07271be0dce9733"},
{file = "scikit_learn-0.24.1-cp38-cp38-win32.whl", hash = "sha256:8772b99d683be8f67fcc04789032f1b949022a0e6880ee7b75a7ec97dbbb5d0b"},
{file = "scikit_learn-0.24.1-cp38-cp38-win_amd64.whl", hash = "sha256:ed9d65594948678827f4ff0e7ae23344e2f2b4cabbca057ccaed3118fdc392ca"},
{file = "scikit_learn-0.24.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8aa1b3ac46b80eaa552b637eeadbbce3be5931e4b5002b964698e33a1b589e1e"},
@@ -4383,7 +4544,6 @@ scikit-learn = [
{file = "scikit_learn-0.24.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:087dfede39efb06ab30618f9ab55a0397f29c38d63cd0ab88d12b500b7d65fd7"},
{file = "scikit_learn-0.24.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:895dbf2030aa7337649e36a83a007df3c9811396b4e2fa672a851160f36ce90c"},
{file = "scikit_learn-0.24.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:9a24d1ccec2a34d4cd3f2a1f86409f3f5954cc23d4d2270ba0d03cf018aa4780"},
{file = "scikit_learn-0.24.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:54be0a60a5a35005ad69c75902e0f5c9f699db4547ead427e97ef881c3242e6f"},
{file = "scikit_learn-0.24.1-cp39-cp39-win32.whl", hash = "sha256:fab31f48282ebf54dd69f6663cd2d9800096bad1bb67bbc9c9ac84eb77b41972"},
{file = "scikit_learn-0.24.1-cp39-cp39-win_amd64.whl", hash = "sha256:4562dcf4793e61c5d0f89836d07bc37521c3a1889da8f651e2c326463c4bd697"},
]
@@ -4417,6 +4577,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

@@ -54,6 +54,7 @@ detecta = "^0.0.5"
tradingview-ta = "^3.2.3"
finvizfinance = "^0.9.3"
Pillow = "^8.2.0"
pyportfolioopt = "^1.4.1"
[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})
@@ -242,6 +245,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: